Merge "Wipe device or profile if max failed attempt reached"
diff --git a/Android.mk b/Android.mk
index 21bd76b..fbcea90 100644
--- a/Android.mk
+++ b/Android.mk
@@ -107,6 +107,7 @@
core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
core/java/android/app/backup/IRestoreObserver.aidl \
core/java/android/app/backup/IRestoreSession.aidl \
+ core/java/android/app/backup/ISelectBackupTransportCallback.aidl \
core/java/android/app/usage/IStorageStatsManager.aidl \
core/java/android/app/usage/IUsageStatsManager.aidl \
core/java/android/bluetooth/IBluetooth.aidl \
@@ -346,6 +347,7 @@
core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
core/java/com/android/internal/backup/IBackupTransport.aidl \
core/java/com/android/internal/backup/IObbBackupService.aidl \
+ core/java/com/android/internal/font/IFontManager.aidl \
core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl \
core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl \
core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl \
@@ -423,10 +425,12 @@
media/java/android/media/projection/IMediaProjectionManager.aidl \
media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl \
media/java/android/media/session/IActiveSessionsListener.aidl \
- media/java/android/media/session/ISessionController.aidl \
- media/java/android/media/session/ISessionControllerCallback.aidl \
+ media/java/android/media/session/IOnMediaKeyListener.aidl \
+ media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl \
media/java/android/media/session/ISession.aidl \
media/java/android/media/session/ISessionCallback.aidl \
+ media/java/android/media/session/ISessionController.aidl \
+ media/java/android/media/session/ISessionControllerCallback.aidl \
media/java/android/media/session/ISessionManager.aidl \
media/java/android/media/tv/ITvInputClient.aidl \
media/java/android/media/tv/ITvInputHardware.aidl \
@@ -535,6 +539,7 @@
framework-protos \
android.hardware.thermal@1.0-java-constants \
android.hardware.health@1.0-java-constants \
+ android.hardware.usb@1.0-java-constants \
LOCAL_PROTOC_OPTIMIZE_TYPE := stream
LOCAL_PROTOC_FLAGS := \
diff --git a/api/current.txt b/api/current.txt
index 637cdac..57a1b66 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -203,6 +203,8 @@
public static final class R.attr {
ctor public R.attr();
+ field public static final int __removed0 = 16844097; // 0x1010541
+ field public static final int __removed1 = 16844099; // 0x1010543
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -406,6 +408,7 @@
field public static final int colorForeground = 16842800; // 0x1010030
field public static final int colorForegroundInverse = 16843270; // 0x1010206
field public static final int colorLongPressedHighlight = 16843662; // 0x101038e
+ field public static final int colorMode = 16844108; // 0x101054c
field public static final int colorMultiSelectHighlight = 16843665; // 0x1010391
field public static final int colorPressedHighlight = 16843661; // 0x101038d
field public static final int colorPrimary = 16843827; // 0x1010433
@@ -758,7 +761,6 @@
field public static final int keyboardLayout = 16843691; // 0x10103ab
field public static final int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
- field public static final int keyboardNavigationSection = 16844097; // 0x1010541
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
field public static final int label = 16842753; // 0x1010001
@@ -908,7 +910,6 @@
field public static final int nextFocusLeft = 16842977; // 0x10100e1
field public static final int nextFocusRight = 16842978; // 0x10100e2
field public static final int nextFocusUp = 16842979; // 0x10100e3
- field public static final int nextSectionForward = 16844099; // 0x1010543
field public static final int noHistory = 16843309; // 0x101022d
field public static final int normalScreens = 16843397; // 0x1010285
field public static final int notificationTimeout = 16843651; // 0x1010383
@@ -1169,6 +1170,7 @@
field public static final int spinnerStyle = 16842881; // 0x1010081
field public static final int spinnersShown = 16843595; // 0x101034b
field public static final int splitMotionEvents = 16843503; // 0x10102ef
+ field public static final int splitName = 16844107; // 0x101054b
field public static final int splitTrack = 16843852; // 0x101044c
field public static final int spotShadowAlpha = 16843967; // 0x10104bf
field public static final int src = 16843033; // 0x1010119
@@ -1817,6 +1819,7 @@
field public static final int tabs = 16908307; // 0x1020013
field public static final int text1 = 16908308; // 0x1020014
field public static final int text2 = 16908309; // 0x1020015
+ field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int undo = 16908338; // 0x1020032
@@ -3536,7 +3539,6 @@
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
method public int getTaskId();
- method public android.text.TextAssistant getTextAssistant();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3686,7 +3688,6 @@
method public final void setResult(int, android.content.Intent);
method public final deprecated void setSecondaryProgress(int);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
- method public void setTextAssistant(android.text.TextAssistant);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -5016,6 +5017,7 @@
method public android.graphics.drawable.Icon getLargeIcon();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
+ method public long getTimeout();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5244,6 +5246,7 @@
method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public android.app.Notification.Builder setTimeout(long);
method public android.app.Notification.Builder setUsesChronometer(boolean);
method public android.app.Notification.Builder setVibrate(long[]);
method public android.app.Notification.Builder setVisibility(int);
@@ -5583,6 +5586,18 @@
field public static final int STYLE_SPINNER = 0; // 0x0
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
+ method public int describeContents();
+ method public android.app.PendingIntent getUserAction();
+ method public java.lang.CharSequence getUserActionTitle();
+ method public java.lang.CharSequence getUserMessage();
+ method public void showAsDialog(android.app.Activity);
+ method public void showAsNotification(android.content.Context);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
+ }
+
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
method public android.app.RemoteAction clone();
@@ -6108,16 +6123,18 @@
method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
- method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
+ method public deprecated java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
method public boolean getAutoTimeRequired();
method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
method public boolean getCameraDisabled(android.content.ComponentName);
- method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
+ method public deprecated java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
+ method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String);
+ method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -6162,7 +6179,7 @@
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
method public boolean isBackupServiceEnabled(android.content.ComponentName);
- method public boolean isCallerApplicationRestrictionsManagingPackage();
+ method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isLockTaskPermitted(java.lang.String);
method public boolean isManagedProfile(android.content.ComponentName);
@@ -6190,14 +6207,15 @@
method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
- method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public deprecated void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public void setAutoTimeRequired(android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean);
method public void setCameraDisabled(android.content.ComponentName, boolean);
- method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
+ method public deprecated void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+ method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
@@ -6245,6 +6263,7 @@
method public void uninstallCaCert(android.content.ComponentName, byte[]);
method public void wipeData(int);
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";
field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
@@ -6254,6 +6273,8 @@
field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+ field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+ field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4
@@ -6261,6 +6282,7 @@
field public static final int ENCRYPTION_STATUS_INACTIVE = 1; // 0x1
field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+ field public static final java.lang.String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
field public static final java.lang.String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
field public static final java.lang.String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
@@ -8476,6 +8498,7 @@
field public static final java.lang.String DOWNLOAD_SERVICE = "download";
field public static final java.lang.String DROPBOX_SERVICE = "dropbox";
field public static final java.lang.String FINGERPRINT_SERVICE = "fingerprint";
+ field public static final java.lang.String FONT_SERVICE = "font";
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
@@ -8848,6 +8871,7 @@
field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
+ field public static final java.lang.String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE";
field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
@@ -9562,7 +9586,10 @@
method public int describeContents();
method public void dump(android.util.Printer, java.lang.String);
method public final int getThemeResource();
- field public static final int CONFIG_COLORIMETRY = 16384; // 0x4000
+ field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
+ field public static final int COLOR_MODE_HDR = 2; // 0x2
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1
+ field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
field public static final int CONFIG_DENSITY = 4096; // 0x1000
field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
field public static final int CONFIG_KEYBOARD = 16; // 0x10
@@ -9623,6 +9650,7 @@
field public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11; // 0xb
field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc
field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1
+ field public int colorMode;
field public int configChanges;
field public int documentLaunchMode;
field public int flags;
@@ -9747,6 +9775,7 @@
field public boolean enabled;
field public boolean exported;
field public java.lang.String processName;
+ field public java.lang.String splitName;
}
public class ConfigurationInfo implements android.os.Parcelable {
@@ -10162,6 +10191,7 @@
field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+ field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded";
field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet";
field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch";
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
@@ -10597,16 +10627,16 @@
method public void setToDefaults();
method public int updateFrom(android.content.res.Configuration);
method public void writeToParcel(android.os.Parcel, int);
- field public static final int COLORIMETRY_HDR_MASK = 12; // 0xc
- field public static final int COLORIMETRY_HDR_NO = 4; // 0x4
- field public static final int COLORIMETRY_HDR_SHIFT = 2; // 0x2
- field public static final int COLORIMETRY_HDR_UNDEFINED = 0; // 0x0
- field public static final int COLORIMETRY_HDR_YES = 8; // 0x8
- field public static final int COLORIMETRY_UNDEFINED = 0; // 0x0
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 1; // 0x1
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 2; // 0x2
+ field public static final int COLOR_MODE_HDR_MASK = 12; // 0xc
+ field public static final int COLOR_MODE_HDR_NO = 4; // 0x4
+ field public static final int COLOR_MODE_HDR_SHIFT = 2; // 0x2
+ field public static final int COLOR_MODE_HDR_UNDEFINED = 0; // 0x0
+ field public static final int COLOR_MODE_HDR_YES = 8; // 0x8
+ field public static final int COLOR_MODE_UNDEFINED = 0; // 0x0
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 1; // 0x1
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2
field public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
@@ -10672,7 +10702,7 @@
field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0
field public static final int UI_MODE_TYPE_VR_HEADSET = 7; // 0x7
field public static final int UI_MODE_TYPE_WATCH = 6; // 0x6
- field public int colorimetry;
+ field public int colorMode;
field public int densityDpi;
field public float fontScale;
field public int hardKeyboardHidden;
@@ -13213,6 +13243,7 @@
}
public class Typeface {
+ method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
method public static android.graphics.Typeface create(java.lang.String, int);
method public static android.graphics.Typeface create(android.graphics.Typeface, int);
method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13233,6 +13264,14 @@
field public static final android.graphics.Typeface SERIF;
}
+ public static abstract interface Typeface.FontRequestCallback {
+ method public abstract void onTypefaceRequestFailed(int);
+ method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
+ field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1
+ field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
+ field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
+ }
+
public class Xfermode {
ctor public Xfermode();
}
@@ -13639,6 +13678,23 @@
method public void addLevel(int, int, android.graphics.drawable.Drawable);
}
+ public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+ ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+ method public void draw(android.graphics.Canvas);
+ method public android.graphics.drawable.Drawable getBackground();
+ method public android.graphics.drawable.Drawable getForeground();
+ method public android.graphics.Path getIconMask();
+ method public int getOpacity();
+ method public void invalidateDrawable(android.graphics.drawable.Drawable);
+ method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setOpacity(int);
+ method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+ field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f;
+ field public static final float MASK_SIZE = 100.0f;
+ }
+
public class NinePatchDrawable extends android.graphics.drawable.Drawable {
ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
@@ -13787,6 +13843,19 @@
}
+package android.graphics.fonts {
+
+ public final class FontRequest implements android.os.Parcelable {
+ ctor public FontRequest(java.lang.String, java.lang.String);
+ method public int describeContents();
+ method public java.lang.String getProviderAuthority();
+ method public java.lang.String getQuery();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
+ }
+
+}
+
package android.graphics.pdf {
public class PdfDocument {
@@ -14114,9 +14183,41 @@
method public float getZ();
}
+ public final class HardwareBuffer implements android.os.Parcelable {
+ method public static android.hardware.HardwareBuffer create(int, int, int, int, long);
+ method public int describeContents();
+ method public void destroy();
+ method public int getFormat();
+ method public int getHeight();
+ method public int getLayers();
+ method public long getUsage();
+ method public int getWidth();
+ method public boolean isDestroyed();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
+ field public static final int RGBA_8888 = 1; // 0x1
+ field public static final int RGBA_FP16 = 5; // 0x5
+ field public static final int RGBX_8888 = 2; // 0x2
+ field public static final int RGB_565 = 4; // 0x4
+ field public static final int RGB_888 = 3; // 0x3
+ field public static final long USAGE0_CPU_READ = 2L; // 0x2L
+ field public static final long USAGE0_CPU_READ_OFTEN = 6L; // 0x6L
+ field public static final long USAGE0_CPU_WRITE = 32L; // 0x20L
+ field public static final long USAGE0_CPU_WRITE_OFTEN = 96L; // 0x60L
+ field public static final long USAGE0_GPU_COLOR_OUTPUT = 2048L; // 0x800L
+ field public static final long USAGE0_GPU_CUBEMAP = 8192L; // 0x2000L
+ field public static final long USAGE0_GPU_DATA_BUFFER = 16384L; // 0x4000L
+ field public static final long USAGE0_GPU_SAMPLED_IMAGE = 1024L; // 0x400L
+ field public static final long USAGE0_GPU_STORAGE_IMAGE = 3072L; // 0xc00L
+ field public static final long USAGE0_PROTECTED_CONTENT = 262144L; // 0x40000L
+ field public static final long USAGE0_SENSOR_DIRECT_DATA = 536870912L; // 0x20000000L
+ field public static final long USAGE0_VIDEO_ENCODE = 2097152L; // 0x200000L
+ }
+
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
+ method public int getHighestDirectReportRateLevel();
method public int getId();
method public int getMaxDelay();
method public float getMaximumRange();
@@ -14130,6 +14231,7 @@
method public java.lang.String getVendor();
method public int getVersion();
method public boolean isAdditionalInfoSupported();
+ method public boolean isDirectChannelTypeSupported(int);
method public boolean isDynamicSensor();
method public boolean isWakeUpSensor();
field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
@@ -14207,6 +14309,17 @@
field public final int type;
}
+ public final class SensorDirectChannel implements java.lang.AutoCloseable {
+ method public void close();
+ method public boolean isValid();
+ field public static final int RATE_FAST = 2; // 0x2
+ field public static final int RATE_NORMAL = 1; // 0x1
+ field public static final int RATE_STOP = 0; // 0x0
+ field public static final int RATE_VERY_FAST = 3; // 0x3
+ field public static final int TYPE_ASHMEM = 1; // 0x1
+ field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2
+ }
+
public class SensorEvent {
field public int accuracy;
field public android.hardware.Sensor sensor;
@@ -14238,6 +14351,9 @@
public abstract class SensorManager {
method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+ method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer);
method public boolean flush(android.hardware.SensorEventListener);
method public static float getAltitude(float, float);
method public static void getAngleChange(float[], float[], float[]);
@@ -14367,7 +14483,7 @@
method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void close();
- method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
+ method public abstract void finalizeOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CameraDevice getDevice();
method public abstract android.view.Surface getInputSurface();
method public abstract boolean isReprocessable();
@@ -15008,10 +15124,12 @@
ctor public OutputConfiguration(android.view.Surface);
ctor public OutputConfiguration(int, android.view.Surface);
ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>);
+ method public void addSurface(android.view.Surface);
method public int describeContents();
+ method public void enableSurfaceSharing();
method public android.view.Surface getSurface();
method public int getSurfaceGroupId();
- method public void setDeferredSurface(android.view.Surface);
+ method public java.util.List<android.view.Surface> getSurfaces();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff
@@ -17561,6 +17679,15 @@
method public boolean isTransitionalDifferent();
}
+ public final class ListFormatter {
+ method public java.lang.String format(java.lang.Object...);
+ method public java.lang.String format(java.util.Collection<?>);
+ method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+ method public static android.icu.text.ListFormatter getInstance();
+ method public java.lang.String getPatternForNumItems(int);
+ }
+
public abstract class LocaleDisplayNames {
method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -17570,6 +17697,8 @@
method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
method public abstract android.icu.util.ULocale getLocale();
+ method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+ method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
method public abstract java.lang.String keyDisplayName(java.lang.String);
method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -17589,9 +17718,19 @@
enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
}
+ public static class LocaleDisplayNames.UiListItem {
+ ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+ field public final android.icu.util.ULocale minimized;
+ field public final android.icu.util.ULocale modified;
+ field public final java.lang.String nameInDisplayLocale;
+ field public final java.lang.String nameInSelf;
+ }
+
public class MeasureFormat extends android.icu.text.UFormat {
method public final boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+ method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
method public final java.lang.String formatMeasures(android.icu.util.Measure...);
method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -18060,6 +18199,14 @@
method public void setUpperCaseFirst(boolean);
}
+ public final class ScientificNumberFormatter {
+ method public java.lang.String format(java.lang.Object);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+ }
+
public abstract class SearchIterator {
ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
method public final int first();
@@ -18835,6 +18982,34 @@
method public long getToDate();
}
+ public final class EthiopicCalendar extends android.icu.util.CECalendar {
+ ctor public EthiopicCalendar();
+ ctor public EthiopicCalendar(android.icu.util.TimeZone);
+ ctor public EthiopicCalendar(java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.ULocale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+ ctor public EthiopicCalendar(int, int, int);
+ ctor public EthiopicCalendar(java.util.Date);
+ ctor public EthiopicCalendar(int, int, int, int, int, int);
+ method protected deprecated int handleGetExtendedYear();
+ method public boolean isAmeteAlemEra();
+ method public void setAmeteAlemEra(boolean);
+ field public static final int GENBOT = 8; // 0x8
+ field public static final int HAMLE = 10; // 0xa
+ field public static final int HEDAR = 2; // 0x2
+ field public static final int MEGABIT = 6; // 0x6
+ field public static final int MESKEREM = 0; // 0x0
+ field public static final int MIAZIA = 7; // 0x7
+ field public static final int NEHASSE = 11; // 0xb
+ field public static final int PAGUMEN = 12; // 0xc
+ field public static final int SENE = 9; // 0x9
+ field public static final int TAHSAS = 3; // 0x3
+ field public static final int TEKEMT = 1; // 0x1
+ field public static final int TER = 4; // 0x4
+ field public static final int YEKATIT = 5; // 0x5
+ }
+
public abstract interface Freezable<T> implements java.lang.Cloneable {
method public abstract T cloneAsThawed();
method public abstract T freeze();
@@ -19363,6 +19538,35 @@
enum_constant public static final android.icu.util.ULocale.Category FORMAT;
}
+ public final class UniversalTimeScale {
+ method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+ method public static long from(long, int);
+ method public static long getTimeScaleValue(int, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+ method public static long toLong(long, int);
+ field public static final int DB2_TIME = 8; // 0x8
+ field public static final int DOTNET_DATE_TIME = 4; // 0x4
+ field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+ field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+ field public static final int EXCEL_TIME = 7; // 0x7
+ field public static final int FROM_MAX_VALUE = 3; // 0x3
+ field public static final int FROM_MIN_VALUE = 2; // 0x2
+ field public static final int ICU4C_TIME = 2; // 0x2
+ field public static final int JAVA_TIME = 0; // 0x0
+ field public static final int MAC_OLD_TIME = 5; // 0x5
+ field public static final int MAC_TIME = 6; // 0x6
+ field public static final int MAX_SCALE = 10; // 0xa
+ field public static final int TO_MAX_VALUE = 5; // 0x5
+ field public static final int TO_MIN_VALUE = 4; // 0x4
+ field public static final int UNITS_VALUE = 0; // 0x0
+ field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+ field public static final int UNIX_TIME = 1; // 0x1
+ field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+ }
+
public abstract interface ValueIterator {
method public abstract boolean next(android.icu.util.ValueIterator.Element);
method public abstract void reset();
@@ -23536,6 +23740,7 @@
field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC";
field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER";
field public static final java.lang.String TYPE_PAL = "TYPE_PAL";
+ field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW";
field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM";
field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB";
field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB";
@@ -23576,8 +23781,14 @@
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+ field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
+ field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration";
+ field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri";
+ field public static final java.lang.String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = "preview_last_playback_position";
+ field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
+ field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight";
field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
@@ -23706,6 +23917,7 @@
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_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 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
@@ -30422,14 +30634,22 @@
}
public class StorageManager {
+ method public long getCacheQuotaBytes();
+ method public long getCacheSizeBytes();
+ method public long getExternalCacheQuotaBytes();
+ method public long getExternalCacheSizeBytes();
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
+ method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException;
+ method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+ method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException;
+ method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
@@ -30557,6 +30777,7 @@
method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
method public int getOrder();
+ method public android.preference.PreferenceGroup getParent();
method protected boolean getPersistedBoolean(boolean);
method protected float getPersistedFloat(float);
method protected int getPersistedInt(int);
@@ -32885,7 +33106,6 @@
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
- method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException;
@@ -32899,6 +33119,16 @@
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
}
+ public class FontsContract {
+ }
+
+ public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ ctor public FontsContract.Columns();
+ field public static final java.lang.String STYLE = "font_style";
+ field public static final java.lang.String TTC_INDEX = "font_ttc_index";
+ field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
+ }
+
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";
@@ -35667,6 +35897,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+ method public final void unsnoozeNotification(java.lang.String);
method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -35682,6 +35913,7 @@
method public final int getCurrentInterruptionFilter();
method public final int getCurrentListenerHints();
method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
+ method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public android.os.IBinder onBind(android.content.Intent);
method public void onInterruptionFilterChanged(int);
method public void onListenerConnected();
@@ -35700,8 +35932,6 @@
method public final void setNotificationsShown(java.lang.String[]);
method public final void snoozeNotification(java.lang.String, java.lang.String);
method public final void snoozeNotification(java.lang.String, long);
- method public final void snoozeNotification(java.lang.String);
- method public final void unsnoozeNotification(java.lang.String);
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -35726,9 +35956,9 @@
field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
field public static final int REASON_SNOOZED = 18; // 0x12
+ field public static final int REASON_TIMEOUT = 19; // 0x13
field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
field public static final int REASON_USER_STOPPED = 6; // 0x6
- field public static final int REASON_USER_SWITCH = 19; // 0x13
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -35736,6 +35966,7 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
+ method public boolean canShowBadge();
method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
@@ -35777,7 +36008,6 @@
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
- method public android.app.NotificationChannel getNotificationChannel();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
@@ -36193,6 +36423,7 @@
method public abstract int getMaxBufferSize();
method public abstract boolean hasFinished();
method public abstract boolean hasStarted();
+ method public default void rangeStart(int, int, int);
method public abstract int start(int, int, int);
}
@@ -36345,6 +36576,7 @@
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
@@ -37695,7 +37927,7 @@
field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
field public static final java.lang.String ACTION_CONFIGURE_PHONE_ACCOUNT = "android.telecom.action.CONFIGURE_PHONE_ACCOUNT";
field public static final java.lang.String ACTION_DEFAULT_DIALER_CHANGED = "android.telecom.action.DEFAULT_DIALER_CHANGED";
- field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+ field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
@@ -37798,7 +38030,8 @@
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
- field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
@@ -37890,9 +38123,13 @@
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+ field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+ field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+ field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+ field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -38431,6 +38668,7 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isNetworkRoaming();
method public boolean isSmsCapable();
@@ -39454,6 +39692,65 @@
method public android.text.Editable newEditable(java.lang.CharSequence);
}
+ public final class FontConfig implements android.os.Parcelable {
+ ctor public FontConfig();
+ ctor public FontConfig(android.text.FontConfig);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Alias> getAliases();
+ method public java.util.List<android.text.FontConfig.Family> getFamilies();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
+ }
+
+ public static final class FontConfig.Alias implements android.os.Parcelable {
+ ctor public FontConfig.Alias(java.lang.String, java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getName();
+ method public java.lang.String getToName();
+ method public int getWeight();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Alias> CREATOR;
+ }
+
+ public static final class FontConfig.Axis implements android.os.Parcelable {
+ ctor public FontConfig.Axis(int, float);
+ method public int describeContents();
+ method public float getStyleValue();
+ method public int getTag();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Axis> CREATOR;
+ }
+
+ public static final class FontConfig.Family implements android.os.Parcelable {
+ ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String);
+ ctor public FontConfig.Family(android.text.FontConfig.Family);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Font> getFonts();
+ method public java.lang.String getLanguage();
+ method public java.lang.String getName();
+ method public java.lang.String getVariant();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR;
+ }
+
+ public static final class FontConfig.Font implements android.os.Parcelable {
+ ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean);
+ ctor public FontConfig.Font(android.text.FontConfig.Font);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Axis> getAxes();
+ method public android.os.ParcelFileDescriptor getFd();
+ method public java.lang.String getFontName();
+ method public int getTtcIndex();
+ method public int getWeight();
+ method public boolean isItalic();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Font> CREATOR;
+ }
+
+ public final class FontManager {
+ method public android.text.FontConfig getSystemFonts();
+ }
+
public abstract interface GetChars implements java.lang.CharSequence {
method public abstract void getChars(int, int, char[], int);
}
@@ -39809,22 +40106,6 @@
method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
}
- public abstract interface TextAssistant {
- method public abstract void addLinks(android.text.Spannable, int);
- method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
- public class TextClassification {
- ctor public TextClassification();
- method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
- }
-
- public final class TextClassificationManager implements android.text.TextAssistant {
- method public void addLinks(android.text.Spannable, int);
- method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
- method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
public abstract interface TextDirectionHeuristic {
method public abstract boolean isRtl(char[], int, int);
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -39840,13 +40121,6 @@
field public static final android.text.TextDirectionHeuristic RTL;
}
- public final class TextLanguage {
- ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
- method public int getEndIndex();
- method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
- method public int getStartIndex();
- }
-
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -39859,13 +40133,6 @@
field public int linkColor;
}
- public class TextSelection {
- ctor public TextSelection();
- method public int getSelectionEndIndex();
- method public int getSelectionStartIndex();
- method public android.text.TextClassification getTextClassification();
- }
-
public class TextUtils {
method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -42119,7 +42386,9 @@
method public android.view.Display.Mode[] getSupportedModes();
method public deprecated float[] getSupportedRefreshRates();
method public deprecated int getWidth();
+ method public boolean isHdr();
method public boolean isValid();
+ method public boolean isWideColorGamut();
field public static final int DEFAULT_DISPLAY = 0; // 0x0
field public static final int FLAG_PRESENTATION = 8; // 0x8
field public static final int FLAG_PRIVATE = 4; // 0x4
@@ -42188,7 +42457,7 @@
method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]);
method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int);
method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int);
- method public android.view.View findNextKeyboardNavigationGroup(int, android.view.View, android.view.View, int);
+ method public android.view.View findNextKeyboardNavigationCluster(android.view.View, android.view.View, int);
method public static android.view.FocusFinder getInstance();
}
@@ -43486,7 +43755,7 @@
method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
method public void addFocusables(java.util.ArrayList<android.view.View>, int);
method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
- method public void addKeyboardNavigationGroups(int, java.util.Collection<android.view.View>, int);
+ method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
method public void addTouchables(java.util.ArrayList<android.view.View>);
@@ -43608,6 +43877,7 @@
method public float getElevation();
method public boolean getFilterTouchesWhenObscured();
method public boolean getFitsSystemWindows();
+ method public int getFocusable();
method public java.util.ArrayList<android.view.View> getFocusables(int);
method public void getFocusedRect(android.graphics.Rect);
method public android.graphics.drawable.Drawable getForeground();
@@ -43650,7 +43920,6 @@
method public int getNextFocusLeftId();
method public int getNextFocusRightId();
method public int getNextFocusUpId();
- method public int getNextSectionForwardId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
method public android.view.ViewOutlineProvider getOutlineProvider();
method public int getOverScrollMode();
@@ -43756,7 +44025,6 @@
method public boolean isInLayout();
method public boolean isInTouchMode();
method public final boolean isKeyboardNavigationCluster();
- method public final boolean isKeyboardNavigationSection();
method public boolean isLaidOut();
method public boolean isLayoutDirectionResolved();
method public boolean isLayoutRequested();
@@ -43779,7 +44047,7 @@
method public boolean isVerticalFadingEdgeEnabled();
method public boolean isVerticalScrollBarEnabled();
method public void jumpDrawablesToCurrentState();
- method public android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public void layout(int, int, int, int);
method public final void measure(int, int);
method protected static int[] mergeDrawableStates(int[], int[]);
@@ -43914,6 +44182,7 @@
method public void setFilterTouchesWhenObscured(boolean);
method public void setFitsSystemWindows(boolean);
method public void setFocusable(boolean);
+ method public void setFocusable(int);
method public void setFocusableInTouchMode(boolean);
method public void setFocusedByDefault(boolean);
method public void setForeground(android.graphics.drawable.Drawable);
@@ -43929,7 +44198,6 @@
method public void setImportantForAccessibility(int);
method public void setKeepScreenOn(boolean);
method public void setKeyboardNavigationCluster(boolean);
- method public void setKeyboardNavigationSection(boolean);
method public void setLabelFor(int);
method public void setLayerPaint(android.graphics.Paint);
method public void setLayerType(int, android.graphics.Paint);
@@ -43947,7 +44215,6 @@
method public void setNextFocusLeftId(int);
method public void setNextFocusRightId(int);
method public void setNextFocusUpId(int);
- method public void setNextSectionForwardId(int);
method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener);
method public void setOnClickListener(android.view.View.OnClickListener);
method public void setOnContextClickListener(android.view.View.OnContextClickListener);
@@ -44054,8 +44321,10 @@
field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
+ field public static final int FOCUSABLE = 1; // 0x1
field public static final int FOCUSABLES_ALL = 0; // 0x0
field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
+ field public static final int FOCUSABLE_AUTO = 16; // 0x10
field protected static final int[] FOCUSED_SELECTED_STATE_SET;
field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
field protected static final int[] FOCUSED_STATE_SET;
@@ -44074,8 +44343,6 @@
field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
- field public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; // 0x1
- field public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; // 0x2
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
field public static final int LAYER_TYPE_NONE = 0; // 0x0
field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
@@ -44087,6 +44354,7 @@
field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field public static final int NOT_FOCUSABLE = 0; // 0x0
field public static final int NO_ID = -1; // 0xffffffff
field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
@@ -44594,7 +44862,7 @@
method public abstract boolean isLayoutRequested();
method public abstract boolean isTextAlignmentResolved();
method public abstract boolean isTextDirectionResolved();
- method public abstract android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
method public abstract boolean onNestedPreFling(android.view.View, float, float);
@@ -44791,6 +45059,7 @@
method public boolean getAllowReturnTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
method public final android.view.Window.Callback getCallback();
+ method public int getColorMode();
method public final android.view.Window getContainer();
method public android.transition.Scene getContentScene();
method public final android.content.Context getContext();
@@ -44847,6 +45116,7 @@
method public abstract void setChildDrawable(int, android.graphics.drawable.Drawable);
method public abstract void setChildInt(int, int);
method public void setClipToOutline(boolean);
+ method public void setColorMode(int);
method public void setContainer(android.view.Window);
method public abstract void setContentView(int);
method public abstract void setContentView(android.view.View);
@@ -45045,8 +45315,10 @@
method public final int copyFrom(android.view.WindowManager.LayoutParams);
method public java.lang.String debug(java.lang.String);
method public int describeContents();
+ method public int getColorMode();
method public final java.lang.CharSequence getTitle();
method public static boolean mayUseInputMethod(int);
+ method public void setColorMode(int);
method public final void setTitle(java.lang.CharSequence);
method public void writeToParcel(android.os.Parcel, int);
field public static final int ALPHA_CHANGED = 128; // 0x80
@@ -46381,6 +46653,83 @@
}
+package android.view.textclassifier {
+
+ public abstract interface LinksInfo {
+ method public abstract boolean apply(java.lang.CharSequence);
+ }
+
+ public final class TextClassificationManager {
+ method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+ method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+ }
+
+ public final class TextClassificationResult {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getLabel();
+ method public android.view.View.OnClickListener getOnClickListener();
+ method public java.lang.String getText();
+ }
+
+ public static final class TextClassificationResult.Builder {
+ ctor public TextClassificationResult.Builder();
+ method public android.view.textclassifier.TextClassificationResult build();
+ method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+ method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+ method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+ }
+
+ public abstract interface TextClassifier {
+ method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+ method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+ method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+ field public static final android.view.textclassifier.TextClassifier NO_OP;
+ field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_OTHER = "other";
+ field public static final java.lang.String TYPE_PHONE = "phone";
+ }
+
+ public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+ }
+
+ public final class TextLanguage {
+ method public float getConfidenceScore(java.util.Locale);
+ method public int getEndIndex();
+ method public java.util.Locale getLanguage(int);
+ method public int getLanguageCount();
+ method public int getStartIndex();
+ }
+
+ public static final class TextLanguage.Builder {
+ ctor public TextLanguage.Builder(int, int);
+ method public android.view.textclassifier.TextLanguage build();
+ method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+ }
+
+ public final class TextSelection {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public int getSelectionEndIndex();
+ method public int getSelectionStartIndex();
+ }
+
+ public static final class TextSelection.Builder {
+ ctor public TextSelection.Builder(int, int);
+ method public android.view.textclassifier.TextSelection build();
+ method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+ }
+
+}
+
package android.view.textservice {
public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -49470,7 +49819,7 @@
method public float getShadowRadius();
method public final boolean getShowSoftInputOnFocus();
method public java.lang.CharSequence getText();
- method public android.text.TextAssistant getTextAssistant();
+ method public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method public java.util.Locale getTextLocale();
method public android.os.LocaleList getTextLocales();
@@ -49586,7 +49935,7 @@
method public final void setText(int, android.widget.TextView.BufferType);
method public void setTextAppearance(int);
method public deprecated void setTextAppearance(android.content.Context, int);
- method public void setTextAssistant(android.text.TextAssistant);
+ method public void setTextClassifier(android.view.textclassifier.TextClassifier);
method public void setTextColor(int);
method public void setTextColor(android.content.res.ColorStateList);
method public void setTextIsSelectable(boolean);
@@ -61729,6 +62078,9 @@
method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+ method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -61739,7 +62091,11 @@
method public static final <T> java.util.List<T> emptyList();
method public static <T> java.util.ListIterator<T> emptyListIterator();
method public static final <K, V> java.util.Map<K, V> emptyMap();
+ method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+ method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
method public static final <T> java.util.Set<T> emptySet();
+ method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+ method public static <E> java.util.SortedSet<E> emptySortedSet();
method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
method public static <T> void fill(java.util.List<? super T>, T);
method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -61768,12 +62124,16 @@
method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+ method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+ method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+ method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+ method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/api/system-current.txt b/api/system-current.txt
index bafc365..8d902d1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -217,6 +217,7 @@
field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
field public static final java.lang.String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
+ field public static final java.lang.String SET_MEDIA_KEY_LISTENER = "android.permission.SET_MEDIA_KEY_LISTENER";
field public static final java.lang.String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
field public static final java.lang.String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
field public static final deprecated java.lang.String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS";
@@ -224,6 +225,7 @@
field public static final java.lang.String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY";
field public static final java.lang.String SET_TIME = "android.permission.SET_TIME";
field public static final java.lang.String SET_TIME_ZONE = "android.permission.SET_TIME_ZONE";
+ field public static final java.lang.String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
field public static final java.lang.String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
field public static final java.lang.String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
field public static final java.lang.String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
@@ -312,6 +314,8 @@
public static final class R.attr {
ctor public R.attr();
+ field public static final int __removed0 = 16844097; // 0x1010541
+ field public static final int __removed1 = 16844099; // 0x1010543
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -515,6 +519,7 @@
field public static final int colorForeground = 16842800; // 0x1010030
field public static final int colorForegroundInverse = 16843270; // 0x1010206
field public static final int colorLongPressedHighlight = 16843662; // 0x101038e
+ field public static final int colorMode = 16844108; // 0x101054c
field public static final int colorMultiSelectHighlight = 16843665; // 0x1010391
field public static final int colorPressedHighlight = 16843661; // 0x101038d
field public static final int colorPrimary = 16843827; // 0x1010433
@@ -867,7 +872,6 @@
field public static final int keyboardLayout = 16843691; // 0x10103ab
field public static final int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
- field public static final int keyboardNavigationSection = 16844097; // 0x1010541
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
field public static final int label = 16842753; // 0x1010001
@@ -1017,7 +1021,6 @@
field public static final int nextFocusLeft = 16842977; // 0x10100e1
field public static final int nextFocusRight = 16842978; // 0x10100e2
field public static final int nextFocusUp = 16842979; // 0x10100e3
- field public static final int nextSectionForward = 16844099; // 0x1010543
field public static final int noHistory = 16843309; // 0x101022d
field public static final int normalScreens = 16843397; // 0x1010285
field public static final int notificationTimeout = 16843651; // 0x1010383
@@ -1282,6 +1285,7 @@
field public static final int spinnerStyle = 16842881; // 0x1010081
field public static final int spinnersShown = 16843595; // 0x101034b
field public static final int splitMotionEvents = 16843503; // 0x10102ef
+ field public static final int splitName = 16844107; // 0x101054b
field public static final int splitTrack = 16843852; // 0x101044c
field public static final int spotShadowAlpha = 16843967; // 0x10104bf
field public static final int src = 16843033; // 0x1010119
@@ -1930,6 +1934,7 @@
field public static final int tabs = 16908307; // 0x1020013
field public static final int text1 = 16908308; // 0x1020014
field public static final int text2 = 16908309; // 0x1020015
+ field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int undo = 16908338; // 0x1020032
@@ -3655,7 +3660,6 @@
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
method public int getTaskId();
- method public android.text.TextAssistant getTextAssistant();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3807,7 +3811,6 @@
method public final void setResult(int, android.content.Intent);
method public final deprecated void setSecondaryProgress(int);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
- method public void setTextAssistant(android.text.TextAssistant);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -5174,6 +5177,7 @@
method public static java.lang.Class<? extends android.app.Notification.Style> getNotificationStyleClass(java.lang.String);
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
+ method public long getTimeout();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5404,6 +5408,7 @@
method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public android.app.Notification.Builder setTimeout(long);
method public android.app.Notification.Builder setUsesChronometer(boolean);
method public android.app.Notification.Builder setVibrate(long[]);
method public android.app.Notification.Builder setVisibility(int);
@@ -5504,9 +5509,11 @@
ctor public Notification.TvExtender();
ctor public Notification.TvExtender(android.app.Notification);
method public android.app.Notification.Builder extend(android.app.Notification.Builder);
+ method public java.lang.String getChannel();
method public android.app.PendingIntent getContentIntent();
method public android.app.PendingIntent getDeleteIntent();
method public boolean isAvailableOnTv();
+ method public android.app.Notification.TvExtender setChannel(java.lang.String);
method public android.app.Notification.TvExtender setContentIntent(android.app.PendingIntent);
method public android.app.Notification.TvExtender setDeleteIntent(android.app.PendingIntent);
}
@@ -5770,6 +5777,18 @@
field public static final int STYLE_SPINNER = 0; // 0x0
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
+ method public int describeContents();
+ method public android.app.PendingIntent getUserAction();
+ method public java.lang.CharSequence getUserActionTitle();
+ method public java.lang.CharSequence getUserMessage();
+ method public void showAsDialog(android.app.Activity);
+ method public void showAsNotification(android.content.Context);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
+ }
+
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
method public android.app.RemoteAction clone();
@@ -6300,16 +6319,18 @@
method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
- method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
+ method public deprecated java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
method public boolean getAutoTimeRequired();
method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
method public boolean getCameraDisabled(android.content.ComponentName);
- method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
+ method public deprecated java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
+ method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String);
+ method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
method public deprecated java.lang.String getDeviceInitializerApp();
method public deprecated android.content.ComponentName getDeviceInitializerComponent();
method public java.lang.String getDeviceOwner();
@@ -6365,7 +6386,7 @@
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
method public boolean isBackupServiceEnabled(android.content.ComponentName);
- method public boolean isCallerApplicationRestrictionsManagingPackage();
+ method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
method public boolean isDeviceManaged();
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isDeviceProvisioned();
@@ -6400,14 +6421,15 @@
method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
- method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public deprecated void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public void setAutoTimeRequired(android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean);
method public void setCameraDisabled(android.content.ComponentName, boolean);
- method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
+ method public deprecated void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+ method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setDeviceProvisioningConfigApplied();
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
@@ -6456,6 +6478,7 @@
method public void uninstallCaCert(android.content.ComponentName, byte[]);
method public void wipeData(int);
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";
field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
@@ -6468,6 +6491,8 @@
field public static final java.lang.String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+ field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+ field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4
@@ -6475,6 +6500,7 @@
field public static final int ENCRYPTION_STATUS_INACTIVE = 1; // 0x1
field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+ field public static final java.lang.String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
field public static final java.lang.String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
field public static final java.lang.String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
@@ -6773,15 +6799,18 @@
method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver);
method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver, int);
method public int requestRestore(android.app.backup.RestoreObserver);
- method public java.lang.String selectBackupTransport(java.lang.String);
+ method public deprecated java.lang.String selectBackupTransport(java.lang.String);
+ method public void selectBackupTransport(android.content.ComponentName, android.app.backup.SelectBackupTransportCallback);
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_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
+ field public static final int ERROR_TRANSPORT_INVALID = -2; // 0xfffffffe
field public static final int ERROR_TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16
field public static final int ERROR_TRANSPORT_QUOTA_EXCEEDED = -1005; // 0xfffffc13
+ field public static final int ERROR_TRANSPORT_UNAVAILABLE = -1; // 0xffffffff
field public static final int FLAG_NON_INCREMENTAL_BACKUP = 1; // 0x1
field public static final java.lang.String PACKAGE_MANAGER_SENTINEL = "@pm@";
field public static final int SUCCESS = 0; // 0x0
@@ -6896,6 +6925,12 @@
field public long token;
}
+ public abstract class SelectBackupTransportCallback {
+ ctor public SelectBackupTransportCallback();
+ method public void onFailure(int);
+ method public void onSuccess(java.lang.String);
+ }
+
public class SharedPreferencesBackupHelper extends android.app.backup.FileBackupHelperBase implements android.app.backup.BackupHelper {
ctor public SharedPreferencesBackupHelper(android.content.Context, java.lang.String...);
method public void performBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor);
@@ -8852,6 +8887,7 @@
field public static final java.lang.String DOWNLOAD_SERVICE = "download";
field public static final java.lang.String DROPBOX_SERVICE = "dropbox";
field public static final java.lang.String FINGERPRINT_SERVICE = "fingerprint";
+ field public static final java.lang.String FONT_SERVICE = "font";
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
@@ -9234,6 +9270,7 @@
field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
+ field public static final java.lang.String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE";
field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
@@ -9963,7 +10000,10 @@
method public int describeContents();
method public void dump(android.util.Printer, java.lang.String);
method public final int getThemeResource();
- field public static final int CONFIG_COLORIMETRY = 16384; // 0x4000
+ field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
+ field public static final int COLOR_MODE_HDR = 2; // 0x2
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1
+ field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
field public static final int CONFIG_DENSITY = 4096; // 0x1000
field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
field public static final int CONFIG_KEYBOARD = 16; // 0x10
@@ -10024,6 +10064,7 @@
field public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11; // 0xb
field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc
field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1
+ field public int colorMode;
field public int configChanges;
field public int documentLaunchMode;
field public int flags;
@@ -10149,6 +10190,7 @@
field public boolean enabled;
field public boolean exported;
field public java.lang.String processName;
+ field public java.lang.String splitName;
}
public class ConfigurationInfo implements android.os.Parcelable {
@@ -10179,7 +10221,8 @@
public final class EphemeralResolveInfo implements android.os.Parcelable {
ctor public deprecated EphemeralResolveInfo(android.net.Uri, java.lang.String, java.util.List<android.content.IntentFilter>);
- ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>);
+ ctor public deprecated EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>);
+ ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>, int);
ctor public EphemeralResolveInfo(java.lang.String, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>);
method public int describeContents();
method public byte[] getDigestBytes();
@@ -10187,6 +10230,7 @@
method public deprecated java.util.List<android.content.IntentFilter> getFilters();
method public java.util.List<android.content.pm.EphemeralIntentFilter> getIntentFilters();
method public java.lang.String getPackageName();
+ method public int getVersionCode();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.pm.EphemeralResolveInfo> CREATOR;
field public static final java.lang.String SHA_ALGORITHM = "SHA-256";
@@ -10240,6 +10284,15 @@
field public java.lang.String targetPackage;
}
+ public final class IntentFilterVerificationInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.util.Set<java.lang.String> getDomains();
+ method public java.lang.String getPackageName();
+ method public int getStatus();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.IntentFilterVerificationInfo> CREATOR;
+ }
+
public class LabeledIntent extends android.content.Intent {
ctor public LabeledIntent(android.content.Intent, java.lang.String, int, int);
ctor public LabeledIntent(android.content.Intent, java.lang.String, java.lang.CharSequence, int);
@@ -10505,6 +10558,7 @@
method public abstract android.content.pm.ActivityInfo getActivityInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.graphics.drawable.Drawable getActivityLogo(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.graphics.drawable.Drawable getActivityLogo(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
method public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
method public abstract android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationBanner(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -10517,12 +10571,15 @@
method public abstract android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
+ method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
+ method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int);
method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public abstract java.lang.String getNameForUid(int);
@@ -10578,7 +10635,9 @@
method public abstract void setApplicationCategoryHint(java.lang.String, int);
method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
+ method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
method public abstract void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
method public abstract void verifyPendingInstall(int, int);
@@ -10614,6 +10673,7 @@
field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+ field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded";
field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet";
field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch";
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
@@ -10739,6 +10799,11 @@
field public static final int INSTALL_REASON_POLICY = 1; // 0x1
field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
field public static final int INSTALL_SUCCEEDED = 1; // 0x1
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; // 0x2
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; // 0x4
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; // 0x1
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; // 0x3
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; // 0x0
field public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; // 0xffffffff
field public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
field public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
@@ -11120,16 +11185,16 @@
method public void setToDefaults();
method public int updateFrom(android.content.res.Configuration);
method public void writeToParcel(android.os.Parcel, int);
- field public static final int COLORIMETRY_HDR_MASK = 12; // 0xc
- field public static final int COLORIMETRY_HDR_NO = 4; // 0x4
- field public static final int COLORIMETRY_HDR_SHIFT = 2; // 0x2
- field public static final int COLORIMETRY_HDR_UNDEFINED = 0; // 0x0
- field public static final int COLORIMETRY_HDR_YES = 8; // 0x8
- field public static final int COLORIMETRY_UNDEFINED = 0; // 0x0
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 1; // 0x1
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 2; // 0x2
+ field public static final int COLOR_MODE_HDR_MASK = 12; // 0xc
+ field public static final int COLOR_MODE_HDR_NO = 4; // 0x4
+ field public static final int COLOR_MODE_HDR_SHIFT = 2; // 0x2
+ field public static final int COLOR_MODE_HDR_UNDEFINED = 0; // 0x0
+ field public static final int COLOR_MODE_HDR_YES = 8; // 0x8
+ field public static final int COLOR_MODE_UNDEFINED = 0; // 0x0
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 1; // 0x1
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2
field public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
@@ -11195,7 +11260,7 @@
field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0
field public static final int UI_MODE_TYPE_VR_HEADSET = 7; // 0x7
field public static final int UI_MODE_TYPE_WATCH = 6; // 0x6
- field public int colorimetry;
+ field public int colorMode;
field public int densityDpi;
field public float fontScale;
field public int hardKeyboardHidden;
@@ -13736,6 +13801,7 @@
}
public class Typeface {
+ method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
method public static android.graphics.Typeface create(java.lang.String, int);
method public static android.graphics.Typeface create(android.graphics.Typeface, int);
method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13756,6 +13822,14 @@
field public static final android.graphics.Typeface SERIF;
}
+ public static abstract interface Typeface.FontRequestCallback {
+ method public abstract void onTypefaceRequestFailed(int);
+ method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
+ field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1
+ field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
+ field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
+ }
+
public class Xfermode {
ctor public Xfermode();
}
@@ -14162,6 +14236,23 @@
method public void addLevel(int, int, android.graphics.drawable.Drawable);
}
+ public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+ ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+ method public void draw(android.graphics.Canvas);
+ method public android.graphics.drawable.Drawable getBackground();
+ method public android.graphics.drawable.Drawable getForeground();
+ method public android.graphics.Path getIconMask();
+ method public int getOpacity();
+ method public void invalidateDrawable(android.graphics.drawable.Drawable);
+ method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setOpacity(int);
+ method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+ field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f;
+ field public static final float MASK_SIZE = 100.0f;
+ }
+
public class NinePatchDrawable extends android.graphics.drawable.Drawable {
ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
@@ -14310,6 +14401,19 @@
}
+package android.graphics.fonts {
+
+ public final class FontRequest implements android.os.Parcelable {
+ ctor public FontRequest(java.lang.String, java.lang.String);
+ method public int describeContents();
+ method public java.lang.String getProviderAuthority();
+ method public java.lang.String getQuery();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
+ }
+
+}
+
package android.graphics.pdf {
public class PdfDocument {
@@ -14637,9 +14741,41 @@
method public float getZ();
}
+ public final class HardwareBuffer implements android.os.Parcelable {
+ method public static android.hardware.HardwareBuffer create(int, int, int, int, long);
+ method public int describeContents();
+ method public void destroy();
+ method public int getFormat();
+ method public int getHeight();
+ method public int getLayers();
+ method public long getUsage();
+ method public int getWidth();
+ method public boolean isDestroyed();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
+ field public static final int RGBA_8888 = 1; // 0x1
+ field public static final int RGBA_FP16 = 5; // 0x5
+ field public static final int RGBX_8888 = 2; // 0x2
+ field public static final int RGB_565 = 4; // 0x4
+ field public static final int RGB_888 = 3; // 0x3
+ field public static final long USAGE0_CPU_READ = 2L; // 0x2L
+ field public static final long USAGE0_CPU_READ_OFTEN = 6L; // 0x6L
+ field public static final long USAGE0_CPU_WRITE = 32L; // 0x20L
+ field public static final long USAGE0_CPU_WRITE_OFTEN = 96L; // 0x60L
+ field public static final long USAGE0_GPU_COLOR_OUTPUT = 2048L; // 0x800L
+ field public static final long USAGE0_GPU_CUBEMAP = 8192L; // 0x2000L
+ field public static final long USAGE0_GPU_DATA_BUFFER = 16384L; // 0x4000L
+ field public static final long USAGE0_GPU_SAMPLED_IMAGE = 1024L; // 0x400L
+ field public static final long USAGE0_GPU_STORAGE_IMAGE = 3072L; // 0xc00L
+ field public static final long USAGE0_PROTECTED_CONTENT = 262144L; // 0x40000L
+ field public static final long USAGE0_SENSOR_DIRECT_DATA = 536870912L; // 0x20000000L
+ field public static final long USAGE0_VIDEO_ENCODE = 2097152L; // 0x200000L
+ }
+
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
+ method public int getHighestDirectReportRateLevel();
method public int getId();
method public int getMaxDelay();
method public float getMaximumRange();
@@ -14655,6 +14791,7 @@
method public int getVersion();
method public boolean isAdditionalInfoSupported();
method public boolean isDataInjectionSupported();
+ method public boolean isDirectChannelTypeSupported(int);
method public boolean isDynamicSensor();
method public boolean isWakeUpSensor();
field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
@@ -14736,6 +14873,17 @@
field public final int type;
}
+ public final class SensorDirectChannel implements java.lang.AutoCloseable {
+ method public void close();
+ method public boolean isValid();
+ field public static final int RATE_FAST = 2; // 0x2
+ field public static final int RATE_NORMAL = 1; // 0x1
+ field public static final int RATE_STOP = 0; // 0x0
+ field public static final int RATE_VERY_FAST = 3; // 0x3
+ field public static final int TYPE_ASHMEM = 1; // 0x1
+ field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2
+ }
+
public class SensorEvent {
field public int accuracy;
field public android.hardware.Sensor sensor;
@@ -14767,6 +14915,9 @@
public abstract class SensorManager {
method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+ method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer);
method public boolean flush(android.hardware.SensorEventListener);
method public static float getAltitude(float, float);
method public static void getAngleChange(float[], float[], float[]);
@@ -14898,7 +15049,7 @@
method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void close();
- method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
+ method public abstract void finalizeOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CameraDevice getDevice();
method public abstract android.view.Surface getInputSurface();
method public abstract boolean isReprocessable();
@@ -15541,11 +15692,13 @@
ctor public OutputConfiguration(android.view.Surface, int);
ctor public OutputConfiguration(int, android.view.Surface, int);
ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>);
+ method public void addSurface(android.view.Surface);
method public int describeContents();
+ method public void enableSurfaceSharing();
method public int getRotation();
method public android.view.Surface getSurface();
method public int getSurfaceGroupId();
- method public void setDeferredSurface(android.view.Surface);
+ method public java.util.List<android.view.Surface> getSurfaces();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
field public static final int ROTATION_0 = 0; // 0x0
@@ -18806,6 +18959,15 @@
method public boolean isTransitionalDifferent();
}
+ public final class ListFormatter {
+ method public java.lang.String format(java.lang.Object...);
+ method public java.lang.String format(java.util.Collection<?>);
+ method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+ method public static android.icu.text.ListFormatter getInstance();
+ method public java.lang.String getPatternForNumItems(int);
+ }
+
public abstract class LocaleDisplayNames {
method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -18815,6 +18977,8 @@
method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
method public abstract android.icu.util.ULocale getLocale();
+ method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+ method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
method public abstract java.lang.String keyDisplayName(java.lang.String);
method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -18834,9 +18998,19 @@
enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
}
+ public static class LocaleDisplayNames.UiListItem {
+ ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+ field public final android.icu.util.ULocale minimized;
+ field public final android.icu.util.ULocale modified;
+ field public final java.lang.String nameInDisplayLocale;
+ field public final java.lang.String nameInSelf;
+ }
+
public class MeasureFormat extends android.icu.text.UFormat {
method public final boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+ method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
method public final java.lang.String formatMeasures(android.icu.util.Measure...);
method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -19305,6 +19479,14 @@
method public void setUpperCaseFirst(boolean);
}
+ public final class ScientificNumberFormatter {
+ method public java.lang.String format(java.lang.Object);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+ }
+
public abstract class SearchIterator {
ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
method public final int first();
@@ -20080,6 +20262,34 @@
method public long getToDate();
}
+ public final class EthiopicCalendar extends android.icu.util.CECalendar {
+ ctor public EthiopicCalendar();
+ ctor public EthiopicCalendar(android.icu.util.TimeZone);
+ ctor public EthiopicCalendar(java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.ULocale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+ ctor public EthiopicCalendar(int, int, int);
+ ctor public EthiopicCalendar(java.util.Date);
+ ctor public EthiopicCalendar(int, int, int, int, int, int);
+ method protected deprecated int handleGetExtendedYear();
+ method public boolean isAmeteAlemEra();
+ method public void setAmeteAlemEra(boolean);
+ field public static final int GENBOT = 8; // 0x8
+ field public static final int HAMLE = 10; // 0xa
+ field public static final int HEDAR = 2; // 0x2
+ field public static final int MEGABIT = 6; // 0x6
+ field public static final int MESKEREM = 0; // 0x0
+ field public static final int MIAZIA = 7; // 0x7
+ field public static final int NEHASSE = 11; // 0xb
+ field public static final int PAGUMEN = 12; // 0xc
+ field public static final int SENE = 9; // 0x9
+ field public static final int TAHSAS = 3; // 0x3
+ field public static final int TEKEMT = 1; // 0x1
+ field public static final int TER = 4; // 0x4
+ field public static final int YEKATIT = 5; // 0x5
+ }
+
public abstract interface Freezable<T> implements java.lang.Cloneable {
method public abstract T cloneAsThawed();
method public abstract T freeze();
@@ -20608,6 +20818,35 @@
enum_constant public static final android.icu.util.ULocale.Category FORMAT;
}
+ public final class UniversalTimeScale {
+ method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+ method public static long from(long, int);
+ method public static long getTimeScaleValue(int, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+ method public static long toLong(long, int);
+ field public static final int DB2_TIME = 8; // 0x8
+ field public static final int DOTNET_DATE_TIME = 4; // 0x4
+ field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+ field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+ field public static final int EXCEL_TIME = 7; // 0x7
+ field public static final int FROM_MAX_VALUE = 3; // 0x3
+ field public static final int FROM_MIN_VALUE = 2; // 0x2
+ field public static final int ICU4C_TIME = 2; // 0x2
+ field public static final int JAVA_TIME = 0; // 0x0
+ field public static final int MAC_OLD_TIME = 5; // 0x5
+ field public static final int MAC_TIME = 6; // 0x6
+ field public static final int MAX_SCALE = 10; // 0xa
+ field public static final int TO_MAX_VALUE = 5; // 0x5
+ field public static final int TO_MIN_VALUE = 4; // 0x4
+ field public static final int UNITS_VALUE = 0; // 0x0
+ field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+ field public static final int UNIX_TIME = 1; // 0x1
+ field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+ }
+
public abstract interface ValueIterator {
method public abstract boolean next(android.icu.util.ValueIterator.Element);
method public abstract void reset();
@@ -25001,12 +25240,22 @@
method public void addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler);
method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
method public void removeOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
+ method public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, android.os.Handler);
+ method public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler);
}
public static abstract interface MediaSessionManager.OnActiveSessionsChangedListener {
method public abstract void onActiveSessionsChanged(java.util.List<android.media.session.MediaController>);
}
+ public static abstract interface MediaSessionManager.OnMediaKeyListener {
+ method public abstract boolean onMediaKey(android.view.KeyEvent);
+ }
+
+ public static abstract interface MediaSessionManager.OnVolumeKeyLongPressListener {
+ method public abstract void onVolumeKeyLongPress(android.view.KeyEvent);
+ }
+
public final class PlaybackState implements android.os.Parcelable {
method public int describeContents();
method public long getActions();
@@ -25202,6 +25451,7 @@
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SERVICE_ID = "service_id";
field public static final java.lang.String COLUMN_SERVICE_TYPE = "service_type";
+ field public static final java.lang.String COLUMN_TRANSIENT = "transient";
field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
field public static final java.lang.String COLUMN_TYPE = "type";
field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
@@ -25233,6 +25483,7 @@
field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC";
field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER";
field public static final java.lang.String TYPE_PAL = "TYPE_PAL";
+ field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW";
field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM";
field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB";
field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB";
@@ -25273,8 +25524,14 @@
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+ field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
+ field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration";
+ field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri";
+ field public static final java.lang.String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = "preview_last_playback_position";
+ field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
+ field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight";
field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
@@ -25284,6 +25541,7 @@
field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
field public static final java.lang.String COLUMN_TITLE = "title";
+ field public static final java.lang.String COLUMN_TRANSIENT = "transient";
field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height";
field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width";
@@ -25483,6 +25741,7 @@
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_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 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
@@ -25738,6 +25997,47 @@
}
+package android.metrics {
+
+ public class LogMaker {
+ ctor public LogMaker(int);
+ ctor public LogMaker(java.lang.Object[]);
+ method public android.metrics.LogMaker addTaggedData(int, java.lang.Object);
+ method public void deserialize(java.lang.Object[]);
+ method public int getCategory();
+ method public long getCounterBucket();
+ method public java.lang.String getCounterName();
+ method public int getCounterValue();
+ method public java.lang.String getPackageName();
+ method public int getSubtype();
+ method public java.lang.Object getTaggedData(int);
+ method public long getTimestamp();
+ method public int getType();
+ method public boolean isLongCounterBucket();
+ method public boolean isValidValue(java.lang.Object);
+ method public java.lang.Object[] serialize();
+ method public android.metrics.LogMaker setCategory(int);
+ method public android.metrics.LogMaker setCounterBucket(int);
+ method public android.metrics.LogMaker setCounterBucket(long);
+ method public android.metrics.LogMaker setCounterName(java.lang.String);
+ method public android.metrics.LogMaker setCounterValue(int);
+ method public android.metrics.LogMaker setPackageName(java.lang.String);
+ method public android.metrics.LogMaker setSubtype(int);
+ method public android.metrics.LogMaker setTimestamp(long);
+ method public android.metrics.LogMaker setType(int);
+ }
+
+ public class MetricsReader {
+ ctor public MetricsReader();
+ method public void checkpoint();
+ method public boolean hasNext();
+ method public android.metrics.LogMaker next();
+ method public void read(long);
+ method public void reset();
+ }
+
+}
+
package android.mtp {
public final class MtpConstants {
@@ -33154,14 +33454,22 @@
}
public class StorageManager {
+ method public long getCacheQuotaBytes();
+ method public long getCacheSizeBytes();
+ method public long getExternalCacheQuotaBytes();
+ method public long getExternalCacheSizeBytes();
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
+ method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException;
+ method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+ method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException;
+ method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
@@ -33302,6 +33610,7 @@
method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
method public int getOrder();
+ method public android.preference.PreferenceGroup getParent();
method protected boolean getPersistedBoolean(boolean);
method protected float getPersistedFloat(float);
method protected int getPersistedInt(int);
@@ -35686,7 +35995,6 @@
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
- method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException;
@@ -35700,6 +36008,16 @@
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
}
+ public class FontsContract {
+ }
+
+ public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ ctor public FontsContract.Columns();
+ field public static final java.lang.String STYLE = "font_style";
+ field public static final java.lang.String TTC_INDEX = "font_ttc_index";
+ field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
+ }
+
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";
@@ -38115,6 +38433,18 @@
package android.security.keystore {
+ public abstract class AttestationUtils {
+ method public static java.security.cert.X509Certificate[] attestDeviceIds(android.content.Context, int[], byte[]) throws android.security.keystore.DeviceIdAttestationException;
+ field public static final int ID_TYPE_IMEI = 2; // 0x2
+ field public static final int ID_TYPE_MEID = 3; // 0x3
+ field public static final int ID_TYPE_SERIAL = 1; // 0x1
+ }
+
+ public class DeviceIdAttestationException extends java.lang.Exception {
+ ctor public DeviceIdAttestationException(java.lang.String);
+ ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+ }
+
public class KeyExpiredException extends java.security.InvalidKeyException {
ctor public KeyExpiredException();
ctor public KeyExpiredException(java.lang.String);
@@ -38583,6 +38913,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+ method public final void unsnoozeNotification(java.lang.String);
method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -38600,6 +38931,7 @@
method public final int getCurrentInterruptionFilter();
method public final int getCurrentListenerHints();
method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
+ method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public android.os.IBinder onBind(android.content.Intent);
method public void onInterruptionFilterChanged(int);
method public void onListenerConnected();
@@ -38620,9 +38952,7 @@
method public final void setOnNotificationPostedTrim(int);
method public final void snoozeNotification(java.lang.String, java.lang.String);
method public final void snoozeNotification(java.lang.String, long);
- method public final void snoozeNotification(java.lang.String);
method public void unregisterAsSystemService() throws android.os.RemoteException;
- method public final void unsnoozeNotification(java.lang.String);
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -38647,9 +38977,9 @@
field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
field public static final int REASON_SNOOZED = 18; // 0x12
+ field public static final int REASON_TIMEOUT = 19; // 0x13
field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
field public static final int REASON_USER_STOPPED = 6; // 0x6
- field public static final int REASON_USER_SWITCH = 19; // 0x13
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -38659,6 +38989,7 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
+ method public boolean canShowBadge();
method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
@@ -38700,7 +39031,6 @@
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
- method public android.app.NotificationChannel getNotificationChannel();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
@@ -39161,6 +39491,7 @@
method public abstract int getMaxBufferSize();
method public abstract boolean hasFinished();
method public abstract boolean hasStarted();
+ method public default void rangeStart(int, int, int);
method public abstract int start(int, int, int);
}
@@ -39313,6 +39644,7 @@
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
@@ -40878,7 +41210,7 @@
field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
field public static final java.lang.String ACTION_CONFIGURE_PHONE_ACCOUNT = "android.telecom.action.CONFIGURE_PHONE_ACCOUNT";
field public static final java.lang.String ACTION_DEFAULT_DIALER_CHANGED = "android.telecom.action.DEFAULT_DIALER_CHANGED";
- field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+ field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final java.lang.String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED";
field public static final java.lang.String ACTION_PHONE_ACCOUNT_UNREGISTERED = "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED";
field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
@@ -40988,7 +41320,8 @@
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
- field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
@@ -41080,9 +41413,13 @@
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+ field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+ field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+ field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+ field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -41665,6 +42002,7 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isDataConnectivityPossible();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isIdle();
@@ -42467,12 +42805,15 @@
method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public int getComponentEnabledSetting(android.content.ComponentName);
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
+ method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
method public java.lang.String getInstallerPackageName(java.lang.String);
method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
+ method public int getIntentVerificationStatusAsUser(java.lang.String, int);
method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public java.lang.String getNameForUid(int);
@@ -42526,7 +42867,9 @@
method public void setApplicationCategoryHint(java.lang.String, int);
method public void setApplicationEnabledSetting(java.lang.String, int, int);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
+ method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
method public void verifyPendingInstall(int, int);
@@ -42730,6 +43073,65 @@
method public android.text.Editable newEditable(java.lang.CharSequence);
}
+ public final class FontConfig implements android.os.Parcelable {
+ ctor public FontConfig();
+ ctor public FontConfig(android.text.FontConfig);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Alias> getAliases();
+ method public java.util.List<android.text.FontConfig.Family> getFamilies();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
+ }
+
+ public static final class FontConfig.Alias implements android.os.Parcelable {
+ ctor public FontConfig.Alias(java.lang.String, java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getName();
+ method public java.lang.String getToName();
+ method public int getWeight();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Alias> CREATOR;
+ }
+
+ public static final class FontConfig.Axis implements android.os.Parcelable {
+ ctor public FontConfig.Axis(int, float);
+ method public int describeContents();
+ method public float getStyleValue();
+ method public int getTag();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Axis> CREATOR;
+ }
+
+ public static final class FontConfig.Family implements android.os.Parcelable {
+ ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String);
+ ctor public FontConfig.Family(android.text.FontConfig.Family);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Font> getFonts();
+ method public java.lang.String getLanguage();
+ method public java.lang.String getName();
+ method public java.lang.String getVariant();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR;
+ }
+
+ public static final class FontConfig.Font implements android.os.Parcelable {
+ ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean);
+ ctor public FontConfig.Font(android.text.FontConfig.Font);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Axis> getAxes();
+ method public android.os.ParcelFileDescriptor getFd();
+ method public java.lang.String getFontName();
+ method public int getTtcIndex();
+ method public int getWeight();
+ method public boolean isItalic();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Font> CREATOR;
+ }
+
+ public final class FontManager {
+ method public android.text.FontConfig getSystemFonts();
+ }
+
public abstract interface GetChars implements java.lang.CharSequence {
method public abstract void getChars(int, int, char[], int);
}
@@ -43085,22 +43487,6 @@
method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
}
- public abstract interface TextAssistant {
- method public abstract void addLinks(android.text.Spannable, int);
- method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
- public class TextClassification {
- ctor public TextClassification();
- method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
- }
-
- public final class TextClassificationManager implements android.text.TextAssistant {
- method public void addLinks(android.text.Spannable, int);
- method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
- method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
public abstract interface TextDirectionHeuristic {
method public abstract boolean isRtl(char[], int, int);
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -43116,13 +43502,6 @@
field public static final android.text.TextDirectionHeuristic RTL;
}
- public final class TextLanguage {
- ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
- method public int getEndIndex();
- method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
- method public int getStartIndex();
- }
-
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -43135,13 +43514,6 @@
field public int linkColor;
}
- public class TextSelection {
- ctor public TextSelection();
- method public int getSelectionEndIndex();
- method public int getSelectionStartIndex();
- method public android.text.TextClassification getTextClassification();
- }
-
public class TextUtils {
method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -44695,6 +45067,7 @@
method public static int getTagCode(java.lang.String);
method public static java.lang.String getTagName(int);
method public static void readEvents(int[], java.util.Collection<android.util.EventLog.Event>) throws java.io.IOException;
+ method public static void readEventsOnWrapping(int[], long, java.util.Collection<android.util.EventLog.Event>) throws java.io.IOException;
method public static int writeEvent(int, int);
method public static int writeEvent(int, long);
method public static int writeEvent(int, float);
@@ -45395,7 +45768,9 @@
method public android.view.Display.Mode[] getSupportedModes();
method public deprecated float[] getSupportedRefreshRates();
method public deprecated int getWidth();
+ method public boolean isHdr();
method public boolean isValid();
+ method public boolean isWideColorGamut();
field public static final int DEFAULT_DISPLAY = 0; // 0x0
field public static final int FLAG_PRESENTATION = 8; // 0x8
field public static final int FLAG_PRIVATE = 4; // 0x4
@@ -45464,7 +45839,7 @@
method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]);
method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int);
method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int);
- method public android.view.View findNextKeyboardNavigationGroup(int, android.view.View, android.view.View, int);
+ method public android.view.View findNextKeyboardNavigationCluster(android.view.View, android.view.View, int);
method public static android.view.FocusFinder getInstance();
}
@@ -46762,7 +47137,7 @@
method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
method public void addFocusables(java.util.ArrayList<android.view.View>, int);
method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
- method public void addKeyboardNavigationGroups(int, java.util.Collection<android.view.View>, int);
+ method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
method public void addTouchables(java.util.ArrayList<android.view.View>);
@@ -46884,6 +47259,7 @@
method public float getElevation();
method public boolean getFilterTouchesWhenObscured();
method public boolean getFitsSystemWindows();
+ method public int getFocusable();
method public java.util.ArrayList<android.view.View> getFocusables(int);
method public void getFocusedRect(android.graphics.Rect);
method public android.graphics.drawable.Drawable getForeground();
@@ -46926,7 +47302,6 @@
method public int getNextFocusLeftId();
method public int getNextFocusRightId();
method public int getNextFocusUpId();
- method public int getNextSectionForwardId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
method public android.view.ViewOutlineProvider getOutlineProvider();
method public int getOverScrollMode();
@@ -47032,7 +47407,6 @@
method public boolean isInLayout();
method public boolean isInTouchMode();
method public final boolean isKeyboardNavigationCluster();
- method public final boolean isKeyboardNavigationSection();
method public boolean isLaidOut();
method public boolean isLayoutDirectionResolved();
method public boolean isLayoutRequested();
@@ -47055,7 +47429,7 @@
method public boolean isVerticalFadingEdgeEnabled();
method public boolean isVerticalScrollBarEnabled();
method public void jumpDrawablesToCurrentState();
- method public android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public void layout(int, int, int, int);
method public final void measure(int, int);
method protected static int[] mergeDrawableStates(int[], int[]);
@@ -47190,6 +47564,7 @@
method public void setFilterTouchesWhenObscured(boolean);
method public void setFitsSystemWindows(boolean);
method public void setFocusable(boolean);
+ method public void setFocusable(int);
method public void setFocusableInTouchMode(boolean);
method public void setFocusedByDefault(boolean);
method public void setForeground(android.graphics.drawable.Drawable);
@@ -47205,7 +47580,6 @@
method public void setImportantForAccessibility(int);
method public void setKeepScreenOn(boolean);
method public void setKeyboardNavigationCluster(boolean);
- method public void setKeyboardNavigationSection(boolean);
method public void setLabelFor(int);
method public void setLayerPaint(android.graphics.Paint);
method public void setLayerType(int, android.graphics.Paint);
@@ -47223,7 +47597,6 @@
method public void setNextFocusLeftId(int);
method public void setNextFocusRightId(int);
method public void setNextFocusUpId(int);
- method public void setNextSectionForwardId(int);
method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener);
method public void setOnClickListener(android.view.View.OnClickListener);
method public void setOnContextClickListener(android.view.View.OnContextClickListener);
@@ -47330,8 +47703,10 @@
field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
+ field public static final int FOCUSABLE = 1; // 0x1
field public static final int FOCUSABLES_ALL = 0; // 0x0
field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
+ field public static final int FOCUSABLE_AUTO = 16; // 0x10
field protected static final int[] FOCUSED_SELECTED_STATE_SET;
field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
field protected static final int[] FOCUSED_STATE_SET;
@@ -47350,8 +47725,6 @@
field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
- field public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; // 0x1
- field public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; // 0x2
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
field public static final int LAYER_TYPE_NONE = 0; // 0x0
field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
@@ -47363,6 +47736,7 @@
field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field public static final int NOT_FOCUSABLE = 0; // 0x0
field public static final int NO_ID = -1; // 0xffffffff
field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
@@ -47870,7 +48244,7 @@
method public abstract boolean isLayoutRequested();
method public abstract boolean isTextAlignmentResolved();
method public abstract boolean isTextDirectionResolved();
- method public abstract android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
method public abstract boolean onNestedPreFling(android.view.View, float, float);
@@ -48067,6 +48441,7 @@
method public boolean getAllowReturnTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
method public final android.view.Window.Callback getCallback();
+ method public int getColorMode();
method public final android.view.Window getContainer();
method public android.transition.Scene getContentScene();
method public final android.content.Context getContext();
@@ -48123,6 +48498,7 @@
method public abstract void setChildDrawable(int, android.graphics.drawable.Drawable);
method public abstract void setChildInt(int, int);
method public void setClipToOutline(boolean);
+ method public void setColorMode(int);
method public void setContainer(android.view.Window);
method public abstract void setContentView(int);
method public abstract void setContentView(android.view.View);
@@ -48322,9 +48698,11 @@
method public final int copyFrom(android.view.WindowManager.LayoutParams);
method public java.lang.String debug(java.lang.String);
method public int describeContents();
+ method public int getColorMode();
method public final java.lang.CharSequence getTitle();
method public final long getUserActivityTimeout();
method public static boolean mayUseInputMethod(int);
+ method public void setColorMode(int);
method public final void setTitle(java.lang.CharSequence);
method public final void setUserActivityTimeout(long);
method public void writeToParcel(android.os.Parcel, int);
@@ -49660,6 +50038,83 @@
}
+package android.view.textclassifier {
+
+ public abstract interface LinksInfo {
+ method public abstract boolean apply(java.lang.CharSequence);
+ }
+
+ public final class TextClassificationManager {
+ method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+ method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+ }
+
+ public final class TextClassificationResult {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getLabel();
+ method public android.view.View.OnClickListener getOnClickListener();
+ method public java.lang.String getText();
+ }
+
+ public static final class TextClassificationResult.Builder {
+ ctor public TextClassificationResult.Builder();
+ method public android.view.textclassifier.TextClassificationResult build();
+ method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+ method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+ method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+ }
+
+ public abstract interface TextClassifier {
+ method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+ method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+ method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+ field public static final android.view.textclassifier.TextClassifier NO_OP;
+ field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_OTHER = "other";
+ field public static final java.lang.String TYPE_PHONE = "phone";
+ }
+
+ public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+ }
+
+ public final class TextLanguage {
+ method public float getConfidenceScore(java.util.Locale);
+ method public int getEndIndex();
+ method public java.util.Locale getLanguage(int);
+ method public int getLanguageCount();
+ method public int getStartIndex();
+ }
+
+ public static final class TextLanguage.Builder {
+ ctor public TextLanguage.Builder(int, int);
+ method public android.view.textclassifier.TextLanguage build();
+ method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+ }
+
+ public final class TextSelection {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public int getSelectionEndIndex();
+ method public int getSelectionStartIndex();
+ }
+
+ public static final class TextSelection.Builder {
+ ctor public TextSelection.Builder(int, int);
+ method public android.view.textclassifier.TextSelection build();
+ method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+ }
+
+}
+
package android.view.textservice {
public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -50597,6 +51052,7 @@
method public java.lang.String getErrorString(android.content.Context, int);
method public int getPackageId(android.content.res.Resources, java.lang.String);
method public void invokeDrawGlFunctor(android.view.View, long, boolean);
+ method public boolean isMultiProcessEnabled();
method public boolean isTraceTagEnabled();
method public void setOnTraceEnabledChangeListener(android.webkit.WebViewDelegate.OnTraceEnabledChangeListener);
}
@@ -53109,7 +53565,7 @@
method public float getShadowRadius();
method public final boolean getShowSoftInputOnFocus();
method public java.lang.CharSequence getText();
- method public android.text.TextAssistant getTextAssistant();
+ method public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method public java.util.Locale getTextLocale();
method public android.os.LocaleList getTextLocales();
@@ -53225,7 +53681,7 @@
method public final void setText(int, android.widget.TextView.BufferType);
method public void setTextAppearance(int);
method public deprecated void setTextAppearance(android.content.Context, int);
- method public void setTextAssistant(android.text.TextAssistant);
+ method public void setTextClassifier(android.view.textclassifier.TextClassifier);
method public void setTextColor(int);
method public void setTextColor(android.content.res.ColorStateList);
method public void setTextIsSelectable(boolean);
@@ -65368,6 +65824,9 @@
method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+ method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -65378,7 +65837,11 @@
method public static final <T> java.util.List<T> emptyList();
method public static <T> java.util.ListIterator<T> emptyListIterator();
method public static final <K, V> java.util.Map<K, V> emptyMap();
+ method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+ method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
method public static final <T> java.util.Set<T> emptySet();
+ method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+ method public static <E> java.util.SortedSet<E> emptySortedSet();
method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
method public static <T> void fill(java.util.List<? super T>, T);
method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -65407,12 +65870,16 @@
method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+ method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+ method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+ method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+ method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/api/test-current.txt b/api/test-current.txt
index c4081f0..35ebf83 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -203,6 +203,8 @@
public static final class R.attr {
ctor public R.attr();
+ field public static final int __removed0 = 16844097; // 0x1010541
+ field public static final int __removed1 = 16844099; // 0x1010543
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -406,6 +408,7 @@
field public static final int colorForeground = 16842800; // 0x1010030
field public static final int colorForegroundInverse = 16843270; // 0x1010206
field public static final int colorLongPressedHighlight = 16843662; // 0x101038e
+ field public static final int colorMode = 16844108; // 0x101054c
field public static final int colorMultiSelectHighlight = 16843665; // 0x1010391
field public static final int colorPressedHighlight = 16843661; // 0x101038d
field public static final int colorPrimary = 16843827; // 0x1010433
@@ -758,7 +761,6 @@
field public static final int keyboardLayout = 16843691; // 0x10103ab
field public static final int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
- field public static final int keyboardNavigationSection = 16844097; // 0x1010541
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
field public static final int label = 16842753; // 0x1010001
@@ -908,7 +910,6 @@
field public static final int nextFocusLeft = 16842977; // 0x10100e1
field public static final int nextFocusRight = 16842978; // 0x10100e2
field public static final int nextFocusUp = 16842979; // 0x10100e3
- field public static final int nextSectionForward = 16844099; // 0x1010543
field public static final int noHistory = 16843309; // 0x101022d
field public static final int normalScreens = 16843397; // 0x1010285
field public static final int notificationTimeout = 16843651; // 0x1010383
@@ -1169,6 +1170,7 @@
field public static final int spinnerStyle = 16842881; // 0x1010081
field public static final int spinnersShown = 16843595; // 0x101034b
field public static final int splitMotionEvents = 16843503; // 0x10102ef
+ field public static final int splitName = 16844107; // 0x101054b
field public static final int splitTrack = 16843852; // 0x101044c
field public static final int spotShadowAlpha = 16843967; // 0x10104bf
field public static final int src = 16843033; // 0x1010119
@@ -1817,6 +1819,7 @@
field public static final int tabs = 16908307; // 0x1020013
field public static final int text1 = 16908308; // 0x1020014
field public static final int text2 = 16908309; // 0x1020015
+ field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int undo = 16908338; // 0x1020032
@@ -3538,7 +3541,6 @@
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
method public int getTaskId();
- method public android.text.TextAssistant getTextAssistant();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3688,7 +3690,6 @@
method public final void setResult(int, android.content.Intent);
method public final deprecated void setSecondaryProgress(int);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
- method public void setTextAssistant(android.text.TextAssistant);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -5026,6 +5027,7 @@
method public android.graphics.drawable.Icon getLargeIcon();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
+ method public long getTimeout();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5254,6 +5256,7 @@
method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public android.app.Notification.Builder setTimeout(long);
method public android.app.Notification.Builder setUsesChronometer(boolean);
method public android.app.Notification.Builder setVibrate(long[]);
method public android.app.Notification.Builder setVisibility(int);
@@ -5594,6 +5597,18 @@
field public static final int STYLE_SPINNER = 0; // 0x0
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
+ method public int describeContents();
+ method public android.app.PendingIntent getUserAction();
+ method public java.lang.CharSequence getUserActionTitle();
+ method public java.lang.CharSequence getUserMessage();
+ method public void showAsDialog(android.app.Activity);
+ method public void showAsNotification(android.content.Context);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
+ }
+
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
method public android.app.RemoteAction clone();
@@ -6125,16 +6140,18 @@
method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
- method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
+ method public deprecated java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
method public boolean getAutoTimeRequired();
method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
method public boolean getCameraDisabled(android.content.ComponentName);
- method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
+ method public deprecated java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
+ method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String);
+ method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
method public java.lang.CharSequence getDeviceOwnerOrganizationName();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
@@ -6183,7 +6200,7 @@
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
method public boolean isBackupServiceEnabled(android.content.ComponentName);
- method public boolean isCallerApplicationRestrictionsManagingPackage();
+ method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
method public boolean isDeviceManaged();
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isLockTaskPermitted(java.lang.String);
@@ -6212,14 +6229,15 @@
method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
- method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public deprecated void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public void setAutoTimeRequired(android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean);
method public void setCameraDisabled(android.content.ComponentName, boolean);
- method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
+ method public deprecated void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+ method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
@@ -6267,6 +6285,7 @@
method public void uninstallCaCert(android.content.ComponentName, byte[]);
method public void wipeData(int);
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";
field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
@@ -6276,6 +6295,8 @@
field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+ field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+ field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4
@@ -6283,6 +6304,7 @@
field public static final int ENCRYPTION_STATUS_INACTIVE = 1; // 0x1
field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+ field public static final java.lang.String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
field public static final java.lang.String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
field public static final java.lang.String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
@@ -8500,6 +8522,7 @@
field public static final java.lang.String DOWNLOAD_SERVICE = "download";
field public static final java.lang.String DROPBOX_SERVICE = "dropbox";
field public static final java.lang.String FINGERPRINT_SERVICE = "fingerprint";
+ field public static final java.lang.String FONT_SERVICE = "font";
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
@@ -8873,6 +8896,7 @@
field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
+ field public static final java.lang.String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE";
field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
@@ -9587,7 +9611,10 @@
method public int describeContents();
method public void dump(android.util.Printer, java.lang.String);
method public final int getThemeResource();
- field public static final int CONFIG_COLORIMETRY = 16384; // 0x4000
+ field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
+ field public static final int COLOR_MODE_HDR = 2; // 0x2
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1
+ field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
field public static final int CONFIG_DENSITY = 4096; // 0x1000
field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
field public static final int CONFIG_KEYBOARD = 16; // 0x10
@@ -9648,6 +9675,7 @@
field public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11; // 0xb
field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc
field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1
+ field public int colorMode;
field public int configChanges;
field public int documentLaunchMode;
field public int flags;
@@ -9774,6 +9802,7 @@
field public boolean enabled;
field public boolean exported;
field public java.lang.String processName;
+ field public java.lang.String splitName;
}
public class ConfigurationInfo implements android.os.Parcelable {
@@ -10192,6 +10221,7 @@
field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+ field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded";
field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet";
field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch";
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
@@ -10629,16 +10659,16 @@
method public void setToDefaults();
method public int updateFrom(android.content.res.Configuration);
method public void writeToParcel(android.os.Parcel, int);
- field public static final int COLORIMETRY_HDR_MASK = 12; // 0xc
- field public static final int COLORIMETRY_HDR_NO = 4; // 0x4
- field public static final int COLORIMETRY_HDR_SHIFT = 2; // 0x2
- field public static final int COLORIMETRY_HDR_UNDEFINED = 0; // 0x0
- field public static final int COLORIMETRY_HDR_YES = 8; // 0x8
- field public static final int COLORIMETRY_UNDEFINED = 0; // 0x0
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 1; // 0x1
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
- field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 2; // 0x2
+ field public static final int COLOR_MODE_HDR_MASK = 12; // 0xc
+ field public static final int COLOR_MODE_HDR_NO = 4; // 0x4
+ field public static final int COLOR_MODE_HDR_SHIFT = 2; // 0x2
+ field public static final int COLOR_MODE_HDR_UNDEFINED = 0; // 0x0
+ field public static final int COLOR_MODE_HDR_YES = 8; // 0x8
+ field public static final int COLOR_MODE_UNDEFINED = 0; // 0x0
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 1; // 0x1
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
+ field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2
field public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
@@ -10704,7 +10734,7 @@
field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0
field public static final int UI_MODE_TYPE_VR_HEADSET = 7; // 0x7
field public static final int UI_MODE_TYPE_WATCH = 6; // 0x6
- field public int colorimetry;
+ field public int colorMode;
field public int densityDpi;
field public float fontScale;
field public int hardKeyboardHidden;
@@ -13245,6 +13275,7 @@
}
public class Typeface {
+ method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
method public static android.graphics.Typeface create(java.lang.String, int);
method public static android.graphics.Typeface create(android.graphics.Typeface, int);
method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13265,6 +13296,14 @@
field public static final android.graphics.Typeface SERIF;
}
+ public static abstract interface Typeface.FontRequestCallback {
+ method public abstract void onTypefaceRequestFailed(int);
+ method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
+ field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1
+ field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
+ field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
+ }
+
public class Xfermode {
ctor public Xfermode();
}
@@ -13671,6 +13710,23 @@
method public void addLevel(int, int, android.graphics.drawable.Drawable);
}
+ public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+ ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+ method public void draw(android.graphics.Canvas);
+ method public android.graphics.drawable.Drawable getBackground();
+ method public android.graphics.drawable.Drawable getForeground();
+ method public android.graphics.Path getIconMask();
+ method public int getOpacity();
+ method public void invalidateDrawable(android.graphics.drawable.Drawable);
+ method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setOpacity(int);
+ method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+ field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f;
+ field public static final float MASK_SIZE = 100.0f;
+ }
+
public class NinePatchDrawable extends android.graphics.drawable.Drawable {
ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
@@ -13819,6 +13875,19 @@
}
+package android.graphics.fonts {
+
+ public final class FontRequest implements android.os.Parcelable {
+ ctor public FontRequest(java.lang.String, java.lang.String);
+ method public int describeContents();
+ method public java.lang.String getProviderAuthority();
+ method public java.lang.String getQuery();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
+ }
+
+}
+
package android.graphics.pdf {
public class PdfDocument {
@@ -14146,9 +14215,41 @@
method public float getZ();
}
+ public final class HardwareBuffer implements android.os.Parcelable {
+ method public static android.hardware.HardwareBuffer create(int, int, int, int, long);
+ method public int describeContents();
+ method public void destroy();
+ method public int getFormat();
+ method public int getHeight();
+ method public int getLayers();
+ method public long getUsage();
+ method public int getWidth();
+ method public boolean isDestroyed();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
+ field public static final int RGBA_8888 = 1; // 0x1
+ field public static final int RGBA_FP16 = 5; // 0x5
+ field public static final int RGBX_8888 = 2; // 0x2
+ field public static final int RGB_565 = 4; // 0x4
+ field public static final int RGB_888 = 3; // 0x3
+ field public static final long USAGE0_CPU_READ = 2L; // 0x2L
+ field public static final long USAGE0_CPU_READ_OFTEN = 6L; // 0x6L
+ field public static final long USAGE0_CPU_WRITE = 32L; // 0x20L
+ field public static final long USAGE0_CPU_WRITE_OFTEN = 96L; // 0x60L
+ field public static final long USAGE0_GPU_COLOR_OUTPUT = 2048L; // 0x800L
+ field public static final long USAGE0_GPU_CUBEMAP = 8192L; // 0x2000L
+ field public static final long USAGE0_GPU_DATA_BUFFER = 16384L; // 0x4000L
+ field public static final long USAGE0_GPU_SAMPLED_IMAGE = 1024L; // 0x400L
+ field public static final long USAGE0_GPU_STORAGE_IMAGE = 3072L; // 0xc00L
+ field public static final long USAGE0_PROTECTED_CONTENT = 262144L; // 0x40000L
+ field public static final long USAGE0_SENSOR_DIRECT_DATA = 536870912L; // 0x20000000L
+ field public static final long USAGE0_VIDEO_ENCODE = 2097152L; // 0x200000L
+ }
+
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
+ method public int getHighestDirectReportRateLevel();
method public int getId();
method public int getMaxDelay();
method public float getMaximumRange();
@@ -14162,6 +14263,7 @@
method public java.lang.String getVendor();
method public int getVersion();
method public boolean isAdditionalInfoSupported();
+ method public boolean isDirectChannelTypeSupported(int);
method public boolean isDynamicSensor();
method public boolean isWakeUpSensor();
field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
@@ -14239,6 +14341,17 @@
field public final int type;
}
+ public final class SensorDirectChannel implements java.lang.AutoCloseable {
+ method public void close();
+ method public boolean isValid();
+ field public static final int RATE_FAST = 2; // 0x2
+ field public static final int RATE_NORMAL = 1; // 0x1
+ field public static final int RATE_STOP = 0; // 0x0
+ field public static final int RATE_VERY_FAST = 3; // 0x3
+ field public static final int TYPE_ASHMEM = 1; // 0x1
+ field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2
+ }
+
public class SensorEvent {
field public int accuracy;
field public android.hardware.Sensor sensor;
@@ -14270,6 +14383,9 @@
public abstract class SensorManager {
method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+ method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer);
method public boolean flush(android.hardware.SensorEventListener);
method public static float getAltitude(float, float);
method public static void getAngleChange(float[], float[], float[]);
@@ -14399,7 +14515,7 @@
method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void close();
- method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
+ method public abstract void finalizeOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CameraDevice getDevice();
method public abstract android.view.Surface getInputSurface();
method public abstract boolean isReprocessable();
@@ -15040,10 +15156,12 @@
ctor public OutputConfiguration(android.view.Surface);
ctor public OutputConfiguration(int, android.view.Surface);
ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>);
+ method public void addSurface(android.view.Surface);
method public int describeContents();
+ method public void enableSurfaceSharing();
method public android.view.Surface getSurface();
method public int getSurfaceGroupId();
- method public void setDeferredSurface(android.view.Surface);
+ method public java.util.List<android.view.Surface> getSurfaces();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff
@@ -17593,6 +17711,15 @@
method public boolean isTransitionalDifferent();
}
+ public final class ListFormatter {
+ method public java.lang.String format(java.lang.Object...);
+ method public java.lang.String format(java.util.Collection<?>);
+ method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+ method public static android.icu.text.ListFormatter getInstance();
+ method public java.lang.String getPatternForNumItems(int);
+ }
+
public abstract class LocaleDisplayNames {
method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -17602,6 +17729,8 @@
method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
method public abstract android.icu.util.ULocale getLocale();
+ method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+ method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
method public abstract java.lang.String keyDisplayName(java.lang.String);
method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -17621,9 +17750,19 @@
enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
}
+ public static class LocaleDisplayNames.UiListItem {
+ ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+ field public final android.icu.util.ULocale minimized;
+ field public final android.icu.util.ULocale modified;
+ field public final java.lang.String nameInDisplayLocale;
+ field public final java.lang.String nameInSelf;
+ }
+
public class MeasureFormat extends android.icu.text.UFormat {
method public final boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+ method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
method public final java.lang.String formatMeasures(android.icu.util.Measure...);
method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -18092,6 +18231,14 @@
method public void setUpperCaseFirst(boolean);
}
+ public final class ScientificNumberFormatter {
+ method public java.lang.String format(java.lang.Object);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+ }
+
public abstract class SearchIterator {
ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
method public final int first();
@@ -18867,6 +19014,34 @@
method public long getToDate();
}
+ public final class EthiopicCalendar extends android.icu.util.CECalendar {
+ ctor public EthiopicCalendar();
+ ctor public EthiopicCalendar(android.icu.util.TimeZone);
+ ctor public EthiopicCalendar(java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.ULocale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+ ctor public EthiopicCalendar(int, int, int);
+ ctor public EthiopicCalendar(java.util.Date);
+ ctor public EthiopicCalendar(int, int, int, int, int, int);
+ method protected deprecated int handleGetExtendedYear();
+ method public boolean isAmeteAlemEra();
+ method public void setAmeteAlemEra(boolean);
+ field public static final int GENBOT = 8; // 0x8
+ field public static final int HAMLE = 10; // 0xa
+ field public static final int HEDAR = 2; // 0x2
+ field public static final int MEGABIT = 6; // 0x6
+ field public static final int MESKEREM = 0; // 0x0
+ field public static final int MIAZIA = 7; // 0x7
+ field public static final int NEHASSE = 11; // 0xb
+ field public static final int PAGUMEN = 12; // 0xc
+ field public static final int SENE = 9; // 0x9
+ field public static final int TAHSAS = 3; // 0x3
+ field public static final int TEKEMT = 1; // 0x1
+ field public static final int TER = 4; // 0x4
+ field public static final int YEKATIT = 5; // 0x5
+ }
+
public abstract interface Freezable<T> implements java.lang.Cloneable {
method public abstract T cloneAsThawed();
method public abstract T freeze();
@@ -19395,6 +19570,35 @@
enum_constant public static final android.icu.util.ULocale.Category FORMAT;
}
+ public final class UniversalTimeScale {
+ method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+ method public static long from(long, int);
+ method public static long getTimeScaleValue(int, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+ method public static long toLong(long, int);
+ field public static final int DB2_TIME = 8; // 0x8
+ field public static final int DOTNET_DATE_TIME = 4; // 0x4
+ field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+ field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+ field public static final int EXCEL_TIME = 7; // 0x7
+ field public static final int FROM_MAX_VALUE = 3; // 0x3
+ field public static final int FROM_MIN_VALUE = 2; // 0x2
+ field public static final int ICU4C_TIME = 2; // 0x2
+ field public static final int JAVA_TIME = 0; // 0x0
+ field public static final int MAC_OLD_TIME = 5; // 0x5
+ field public static final int MAC_TIME = 6; // 0x6
+ field public static final int MAX_SCALE = 10; // 0xa
+ field public static final int TO_MAX_VALUE = 5; // 0x5
+ field public static final int TO_MIN_VALUE = 4; // 0x4
+ field public static final int UNITS_VALUE = 0; // 0x0
+ field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+ field public static final int UNIX_TIME = 1; // 0x1
+ field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+ }
+
public abstract interface ValueIterator {
method public abstract boolean next(android.icu.util.ValueIterator.Element);
method public abstract void reset();
@@ -23626,6 +23830,7 @@
field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC";
field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER";
field public static final java.lang.String TYPE_PAL = "TYPE_PAL";
+ field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW";
field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM";
field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB";
field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB";
@@ -23666,8 +23871,14 @@
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+ field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
+ field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration";
+ field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri";
+ field public static final java.lang.String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = "preview_last_playback_position";
+ field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
+ field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight";
field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
@@ -23796,6 +24007,7 @@
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_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 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
@@ -30535,14 +30747,22 @@
}
public class StorageManager {
+ method public long getCacheQuotaBytes();
+ method public long getCacheSizeBytes();
+ method public long getExternalCacheQuotaBytes();
+ method public long getExternalCacheSizeBytes();
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
+ method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException;
+ method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+ method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException;
+ method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
@@ -30670,6 +30890,7 @@
method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
method public int getOrder();
+ method public android.preference.PreferenceGroup getParent();
method protected boolean getPersistedBoolean(boolean);
method protected float getPersistedFloat(float);
method protected int getPersistedInt(int);
@@ -33001,7 +33222,6 @@
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
- method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException;
@@ -33015,6 +33235,16 @@
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
}
+ public class FontsContract {
+ }
+
+ public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ ctor public FontsContract.Columns();
+ field public static final java.lang.String STYLE = "font_style";
+ field public static final java.lang.String TTC_INDEX = "font_ttc_index";
+ field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
+ }
+
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";
@@ -35320,6 +35550,18 @@
package android.security.keystore {
+ public abstract class AttestationUtils {
+ method public static java.security.cert.X509Certificate[] attestDeviceIds(android.content.Context, int[], byte[]) throws android.security.keystore.DeviceIdAttestationException;
+ field public static final int ID_TYPE_IMEI = 2; // 0x2
+ field public static final int ID_TYPE_MEID = 3; // 0x3
+ field public static final int ID_TYPE_SERIAL = 1; // 0x1
+ }
+
+ public class DeviceIdAttestationException extends java.lang.Exception {
+ ctor public DeviceIdAttestationException(java.lang.String);
+ ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+ }
+
public class KeyExpiredException extends java.security.InvalidKeyException {
ctor public KeyExpiredException();
ctor public KeyExpiredException(java.lang.String);
@@ -35788,6 +36030,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+ method public final void unsnoozeNotification(java.lang.String);
method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -35803,6 +36046,7 @@
method public final int getCurrentInterruptionFilter();
method public final int getCurrentListenerHints();
method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
+ method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public android.os.IBinder onBind(android.content.Intent);
method public void onInterruptionFilterChanged(int);
method public void onListenerConnected();
@@ -35821,8 +36065,6 @@
method public final void setNotificationsShown(java.lang.String[]);
method public final void snoozeNotification(java.lang.String, java.lang.String);
method public final void snoozeNotification(java.lang.String, long);
- method public final void snoozeNotification(java.lang.String);
- method public final void unsnoozeNotification(java.lang.String);
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -35847,9 +36089,9 @@
field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
field public static final int REASON_SNOOZED = 18; // 0x12
+ field public static final int REASON_TIMEOUT = 19; // 0x13
field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
field public static final int REASON_USER_STOPPED = 6; // 0x6
- field public static final int REASON_USER_SWITCH = 19; // 0x13
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -35857,6 +36099,7 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
+ method public boolean canShowBadge();
method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
@@ -35898,7 +36141,6 @@
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
- method public android.app.NotificationChannel getNotificationChannel();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
@@ -36314,6 +36556,7 @@
method public abstract int getMaxBufferSize();
method public abstract boolean hasFinished();
method public abstract boolean hasStarted();
+ method public default void rangeStart(int, int, int);
method public abstract int start(int, int, int);
}
@@ -36466,6 +36709,7 @@
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
@@ -37816,7 +38060,7 @@
field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
field public static final java.lang.String ACTION_CONFIGURE_PHONE_ACCOUNT = "android.telecom.action.CONFIGURE_PHONE_ACCOUNT";
field public static final java.lang.String ACTION_DEFAULT_DIALER_CHANGED = "android.telecom.action.DEFAULT_DIALER_CHANGED";
- field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+ field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
@@ -37919,7 +38163,8 @@
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
- field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
@@ -38011,9 +38256,13 @@
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+ field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+ field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+ field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+ field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -38552,6 +38801,7 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isNetworkRoaming();
method public boolean isSmsCapable();
@@ -39578,6 +39828,65 @@
method public android.text.Editable newEditable(java.lang.CharSequence);
}
+ public final class FontConfig implements android.os.Parcelable {
+ ctor public FontConfig();
+ ctor public FontConfig(android.text.FontConfig);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Alias> getAliases();
+ method public java.util.List<android.text.FontConfig.Family> getFamilies();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
+ }
+
+ public static final class FontConfig.Alias implements android.os.Parcelable {
+ ctor public FontConfig.Alias(java.lang.String, java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getName();
+ method public java.lang.String getToName();
+ method public int getWeight();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Alias> CREATOR;
+ }
+
+ public static final class FontConfig.Axis implements android.os.Parcelable {
+ ctor public FontConfig.Axis(int, float);
+ method public int describeContents();
+ method public float getStyleValue();
+ method public int getTag();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Axis> CREATOR;
+ }
+
+ public static final class FontConfig.Family implements android.os.Parcelable {
+ ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String);
+ ctor public FontConfig.Family(android.text.FontConfig.Family);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Font> getFonts();
+ method public java.lang.String getLanguage();
+ method public java.lang.String getName();
+ method public java.lang.String getVariant();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR;
+ }
+
+ public static final class FontConfig.Font implements android.os.Parcelable {
+ ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean);
+ ctor public FontConfig.Font(android.text.FontConfig.Font);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Axis> getAxes();
+ method public android.os.ParcelFileDescriptor getFd();
+ method public java.lang.String getFontName();
+ method public int getTtcIndex();
+ method public int getWeight();
+ method public boolean isItalic();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Font> CREATOR;
+ }
+
+ public final class FontManager {
+ method public android.text.FontConfig getSystemFonts();
+ }
+
public abstract interface GetChars implements java.lang.CharSequence {
method public abstract void getChars(int, int, char[], int);
}
@@ -39933,22 +40242,6 @@
method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
}
- public abstract interface TextAssistant {
- method public abstract void addLinks(android.text.Spannable, int);
- method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
- public class TextClassification {
- ctor public TextClassification();
- method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
- }
-
- public final class TextClassificationManager implements android.text.TextAssistant {
- method public void addLinks(android.text.Spannable, int);
- method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
- method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
public abstract interface TextDirectionHeuristic {
method public abstract boolean isRtl(char[], int, int);
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -39964,13 +40257,6 @@
field public static final android.text.TextDirectionHeuristic RTL;
}
- public final class TextLanguage {
- ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
- method public int getEndIndex();
- method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
- method public int getStartIndex();
- }
-
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -39983,13 +40269,6 @@
field public int linkColor;
}
- public class TextSelection {
- ctor public TextSelection();
- method public int getSelectionEndIndex();
- method public int getSelectionStartIndex();
- method public android.text.TextClassification getTextClassification();
- }
-
public class TextUtils {
method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -42408,7 +42687,9 @@
method public android.view.Display.Mode[] getSupportedModes();
method public deprecated float[] getSupportedRefreshRates();
method public deprecated int getWidth();
+ method public boolean isHdr();
method public boolean isValid();
+ method public boolean isWideColorGamut();
field public static final int DEFAULT_DISPLAY = 0; // 0x0
field public static final int FLAG_PRESENTATION = 8; // 0x8
field public static final int FLAG_PRIVATE = 4; // 0x4
@@ -42477,7 +42758,7 @@
method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]);
method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int);
method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int);
- method public android.view.View findNextKeyboardNavigationGroup(int, android.view.View, android.view.View, int);
+ method public android.view.View findNextKeyboardNavigationCluster(android.view.View, android.view.View, int);
method public static android.view.FocusFinder getInstance();
}
@@ -43777,7 +44058,7 @@
method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
method public void addFocusables(java.util.ArrayList<android.view.View>, int);
method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
- method public void addKeyboardNavigationGroups(int, java.util.Collection<android.view.View>, int);
+ method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
method public void addTouchables(java.util.ArrayList<android.view.View>);
@@ -43899,6 +44180,7 @@
method public float getElevation();
method public boolean getFilterTouchesWhenObscured();
method public boolean getFitsSystemWindows();
+ method public int getFocusable();
method public java.util.ArrayList<android.view.View> getFocusables(int);
method public void getFocusedRect(android.graphics.Rect);
method public android.graphics.drawable.Drawable getForeground();
@@ -43941,7 +44223,6 @@
method public int getNextFocusLeftId();
method public int getNextFocusRightId();
method public int getNextFocusUpId();
- method public int getNextSectionForwardId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
method public android.view.ViewOutlineProvider getOutlineProvider();
method public int getOverScrollMode();
@@ -44048,7 +44329,6 @@
method public boolean isInLayout();
method public boolean isInTouchMode();
method public final boolean isKeyboardNavigationCluster();
- method public final boolean isKeyboardNavigationSection();
method public boolean isLaidOut();
method public boolean isLayoutDirectionResolved();
method public boolean isLayoutRequested();
@@ -44071,7 +44351,7 @@
method public boolean isVerticalFadingEdgeEnabled();
method public boolean isVerticalScrollBarEnabled();
method public void jumpDrawablesToCurrentState();
- method public android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public void layout(int, int, int, int);
method public final void measure(int, int);
method protected static int[] mergeDrawableStates(int[], int[]);
@@ -44206,6 +44486,7 @@
method public void setFilterTouchesWhenObscured(boolean);
method public void setFitsSystemWindows(boolean);
method public void setFocusable(boolean);
+ method public void setFocusable(int);
method public void setFocusableInTouchMode(boolean);
method public void setFocusedByDefault(boolean);
method public void setForeground(android.graphics.drawable.Drawable);
@@ -44221,7 +44502,6 @@
method public void setImportantForAccessibility(int);
method public void setKeepScreenOn(boolean);
method public void setKeyboardNavigationCluster(boolean);
- method public void setKeyboardNavigationSection(boolean);
method public void setLabelFor(int);
method public void setLayerPaint(android.graphics.Paint);
method public void setLayerType(int, android.graphics.Paint);
@@ -44239,7 +44519,6 @@
method public void setNextFocusLeftId(int);
method public void setNextFocusRightId(int);
method public void setNextFocusUpId(int);
- method public void setNextSectionForwardId(int);
method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener);
method public void setOnClickListener(android.view.View.OnClickListener);
method public void setOnContextClickListener(android.view.View.OnContextClickListener);
@@ -44346,8 +44625,10 @@
field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
+ field public static final int FOCUSABLE = 1; // 0x1
field public static final int FOCUSABLES_ALL = 0; // 0x0
field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
+ field public static final int FOCUSABLE_AUTO = 16; // 0x10
field protected static final int[] FOCUSED_SELECTED_STATE_SET;
field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
field protected static final int[] FOCUSED_STATE_SET;
@@ -44366,8 +44647,6 @@
field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
- field public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; // 0x1
- field public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; // 0x2
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
field public static final int LAYER_TYPE_NONE = 0; // 0x0
field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
@@ -44379,6 +44658,7 @@
field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field public static final int NOT_FOCUSABLE = 0; // 0x0
field public static final int NO_ID = -1; // 0xffffffff
field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
@@ -44890,7 +45170,7 @@
method public abstract boolean isLayoutRequested();
method public abstract boolean isTextAlignmentResolved();
method public abstract boolean isTextDirectionResolved();
- method public abstract android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
method public abstract boolean onNestedPreFling(android.view.View, float, float);
@@ -45087,6 +45367,7 @@
method public boolean getAllowReturnTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
method public final android.view.Window.Callback getCallback();
+ method public int getColorMode();
method public final android.view.Window getContainer();
method public android.transition.Scene getContentScene();
method public final android.content.Context getContext();
@@ -45143,6 +45424,7 @@
method public abstract void setChildDrawable(int, android.graphics.drawable.Drawable);
method public abstract void setChildInt(int, int);
method public void setClipToOutline(boolean);
+ method public void setColorMode(int);
method public void setContainer(android.view.Window);
method public abstract void setContentView(int);
method public abstract void setContentView(android.view.View);
@@ -45341,8 +45623,10 @@
method public final int copyFrom(android.view.WindowManager.LayoutParams);
method public java.lang.String debug(java.lang.String);
method public int describeContents();
+ method public int getColorMode();
method public final java.lang.CharSequence getTitle();
method public static boolean mayUseInputMethod(int);
+ method public void setColorMode(int);
method public final void setTitle(java.lang.CharSequence);
method public void writeToParcel(android.os.Parcel, int);
field public static final int ALPHA_CHANGED = 128; // 0x80
@@ -46679,6 +46963,83 @@
}
+package android.view.textclassifier {
+
+ public abstract interface LinksInfo {
+ method public abstract boolean apply(java.lang.CharSequence);
+ }
+
+ public final class TextClassificationManager {
+ method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+ method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+ }
+
+ public final class TextClassificationResult {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getLabel();
+ method public android.view.View.OnClickListener getOnClickListener();
+ method public java.lang.String getText();
+ }
+
+ public static final class TextClassificationResult.Builder {
+ ctor public TextClassificationResult.Builder();
+ method public android.view.textclassifier.TextClassificationResult build();
+ method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+ method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+ method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+ }
+
+ public abstract interface TextClassifier {
+ method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+ method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+ method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+ field public static final android.view.textclassifier.TextClassifier NO_OP;
+ field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_OTHER = "other";
+ field public static final java.lang.String TYPE_PHONE = "phone";
+ }
+
+ public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+ }
+
+ public final class TextLanguage {
+ method public float getConfidenceScore(java.util.Locale);
+ method public int getEndIndex();
+ method public java.util.Locale getLanguage(int);
+ method public int getLanguageCount();
+ method public int getStartIndex();
+ }
+
+ public static final class TextLanguage.Builder {
+ ctor public TextLanguage.Builder(int, int);
+ method public android.view.textclassifier.TextLanguage build();
+ method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+ }
+
+ public final class TextSelection {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public int getSelectionEndIndex();
+ method public int getSelectionStartIndex();
+ }
+
+ public static final class TextSelection.Builder {
+ ctor public TextSelection.Builder(int, int);
+ method public android.view.textclassifier.TextSelection build();
+ method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+ }
+
+}
+
package android.view.textservice {
public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -49775,7 +50136,7 @@
method public float getShadowRadius();
method public final boolean getShowSoftInputOnFocus();
method public java.lang.CharSequence getText();
- method public android.text.TextAssistant getTextAssistant();
+ method public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method public java.util.Locale getTextLocale();
method public android.os.LocaleList getTextLocales();
@@ -49891,7 +50252,7 @@
method public final void setText(int, android.widget.TextView.BufferType);
method public void setTextAppearance(int);
method public deprecated void setTextAppearance(android.content.Context, int);
- method public void setTextAssistant(android.text.TextAssistant);
+ method public void setTextClassifier(android.view.textclassifier.TextClassifier);
method public void setTextColor(int);
method public void setTextColor(android.content.res.ColorStateList);
method public void setTextIsSelectable(boolean);
@@ -62042,6 +62403,9 @@
method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+ method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -62052,7 +62416,11 @@
method public static final <T> java.util.List<T> emptyList();
method public static <T> java.util.ListIterator<T> emptyListIterator();
method public static final <K, V> java.util.Map<K, V> emptyMap();
+ method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+ method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
method public static final <T> java.util.Set<T> emptySet();
+ method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+ method public static <E> java.util.SortedSet<E> emptySortedSet();
method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
method public static <T> void fill(java.util.List<? super T>, T);
method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -62081,12 +62449,16 @@
method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+ method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+ method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+ method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+ method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 780db5e..7e91391 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -18,20 +18,24 @@
import android.app.backup.BackupManager;
import android.app.backup.BackupProgress;
-import android.app.backup.RestoreSet;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupObserver;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.app.backup.RestoreSet;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.content.ComponentName;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.util.Log;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
public final class Bmgr {
IBackupManager mBmgr;
@@ -363,6 +367,11 @@
return;
}
+ if ("-c".equals(which)) {
+ doTransportByComponent();
+ return;
+ }
+
String old = mBmgr.selectBackupTransport(which);
if (old == null) {
System.out.println("Unknown transport '" + which
@@ -370,12 +379,50 @@
} else {
System.out.println("Selected transport " + which + " (formerly " + old + ")");
}
+
} catch (RemoteException e) {
System.err.println(e.toString());
System.err.println(BMGR_NOT_RUNNING_ERR);
}
}
+ private void doTransportByComponent() {
+ String which = nextArg();
+ if (which == null) {
+ showUsage();
+ return;
+ }
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ try {
+ mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which),
+ new ISelectBackupTransportCallback.Stub() {
+ @Override
+ public void onSuccess(String transportName) {
+ System.out.println("Success. Selected transport: " + transportName);
+ latch.countDown();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ System.err.println("Failure. error=" + reason);
+ latch.countDown();
+ }
+ });
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(BMGR_NOT_RUNNING_ERR);
+ return;
+ }
+
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ System.err.println("Operation interrupted.");
+ }
+ }
+
private void doWipe() {
String transport = nextArg();
if (transport == null) {
@@ -427,7 +474,16 @@
}
private void doListTransports() {
+ String arg = nextArg();
+
try {
+ if ("-c".equals(arg)) {
+ for (ComponentName transport : mBmgr.listAllTransportComponents()) {
+ System.out.println(transport.flattenToShortString());
+ }
+ return;
+ }
+
String current = mBmgr.getCurrentTransport();
String[] transports = mBmgr.listAllTransports();
if (transports == null || transports.length == 0) {
@@ -649,9 +705,9 @@
System.err.println(" bmgr backup PACKAGE");
System.err.println(" bmgr enable BOOL");
System.err.println(" bmgr enabled");
- System.err.println(" bmgr list transports");
+ System.err.println(" bmgr list transports [-c]");
System.err.println(" bmgr list sets");
- System.err.println(" bmgr transport WHICH");
+ System.err.println(" bmgr transport WHICH|-c WHICH_COMPONENT");
System.err.println(" bmgr restore TOKEN");
System.err.println(" bmgr restore TOKEN PACKAGE...");
System.err.println(" bmgr restore PACKAGE");
@@ -673,15 +729,18 @@
System.err.println("the backup mechanism.");
System.err.println("");
System.err.println("The 'list transports' command reports the names of the backup transports");
- System.err.println("currently available on the device. These names can be passed as arguments");
+ System.err.println("BackupManager is currently bound to. These names can be passed as arguments");
System.err.println("to the 'transport' and 'wipe' commands. The currently active transport");
- System.err.println("is indicated with a '*' character.");
+ System.err.println("is indicated with a '*' character. If -c flag is used, all available");
+ System.err.println("transport components on the device are listed. These can be used with");
+ System.err.println("the component variant of 'transport' command.");
System.err.println("");
System.err.println("The 'list sets' command reports the token and name of each restore set");
System.err.println("available to the device via the currently active transport.");
System.err.println("");
System.err.println("The 'transport' command designates the named transport as the currently");
- System.err.println("active one. This setting is persistent across reboots.");
+ System.err.println("active one. This setting is persistent across reboots. If -c flag is");
+ System.err.println("specified, the following string is treated as a component name.");
System.err.println("");
System.err.println("The 'restore' command when given just a restore token initiates a full-system");
System.err.println("restore operation from the currently active transport. It will deliver");
diff --git a/cmds/media/src/com/android/commands/media/VolumeCtrl.java b/cmds/media/src/com/android/commands/media/VolumeCtrl.java
index a171932..1629c6f 100755
--- a/cmds/media/src/com/android/commands/media/VolumeCtrl.java
+++ b/cmds/media/src/com/android/commands/media/VolumeCtrl.java
@@ -42,6 +42,7 @@
// --stream affects --set, --adj or --get options.
// --show affects --set and --adj options.
+ // --get can be used with --set, --adj or by itself.
public final static String USAGE = new String(
"the options are as follows: \n" +
"\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n" +
@@ -56,9 +57,8 @@
"\t\tadb shell media volume --stream 3 --get\n"
);
- private final static int VOLUME_CONTROL_MODE_SET = 0;
- private final static int VOLUME_CONTROL_MODE_ADJUST = 1;
- private final static int VOLUME_CONTROL_MODE_GET = 2;
+ private final static int VOLUME_CONTROL_MODE_SET = 1;
+ private final static int VOLUME_CONTROL_MODE_ADJUST = 2;
private final static String ADJUST_LOWER = "lower";
private final static String ADJUST_SAME = "same";
@@ -69,9 +69,10 @@
// Default parameters
int stream = AudioManager.STREAM_MUSIC;
int volIndex = 5;
- int mode = VOLUME_CONTROL_MODE_SET;
+ int mode = 0;
int adjDir = AudioManager.ADJUST_RAISE;
boolean showUi = false;
+ boolean doGet = false;
//----------------------------------------
// read options
@@ -83,7 +84,7 @@
showUi = true;
break;
case "--get":
- mode = VOLUME_CONTROL_MODE_GET;
+ doGet = true;
log(LOG_V, "will get volume");
break;
case "--stream":
@@ -150,15 +151,16 @@
// Non-interactive test
final int flag = showUi? AudioManager.FLAG_SHOW_UI : 0;
final String pack = cmd.getClass().getPackage().getName();
- if (mode == VOLUME_CONTROL_MODE_GET) {
- log(LOG_V, "volume is " + audioService.getStreamVolume(stream) +
- " in range [" + audioService.getStreamMinVolume(stream) +
- ".." + audioService.getStreamMaxVolume(stream) + "]");
- } else if (mode == VOLUME_CONTROL_MODE_SET) {
+ if (mode == VOLUME_CONTROL_MODE_SET) {
audioService.setStreamVolume(stream, volIndex, flag, pack/*callingPackage*/);
} else if (mode == VOLUME_CONTROL_MODE_ADJUST) {
audioService.adjustStreamVolume(stream, adjDir, flag, pack);
}
+ if (doGet) {
+ log(LOG_V, "volume is " + audioService.getStreamVolume(stream) +
+ " in range [" + audioService.getStreamMinVolume(stream) +
+ ".." + audioService.getStreamMaxVolume(stream) + "]");
+ }
}
//--------------------------------------------
diff --git a/compiled-classes-phone b/compiled-classes-phone
index ebc54f2..ed0a4a6 100644
--- a/compiled-classes-phone
+++ b/compiled-classes-phone
@@ -1195,13 +1195,13 @@
android.graphics.DiscretePathEffect
android.graphics.DrawFilter
android.graphics.EmbossMaskFilter
+android.graphics.FontConfig
+android.graphics.FontConfig$Alias
+android.graphics.FontConfig$Axis
+android.graphics.FontConfig$Family
+android.graphics.FontConfig$Font
android.graphics.FontFamily
android.graphics.FontListParser
-android.graphics.FontListParser$Alias
-android.graphics.FontListParser$Axis
-android.graphics.FontListParser$Config
-android.graphics.FontListParser$Family
-android.graphics.FontListParser$Font
android.graphics.ImageFormat
android.graphics.Insets
android.graphics.Interpolator
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 556d7ad..df4c4af 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -78,8 +78,6 @@
import android.service.autofill.IAutoFillAppCallback;
import android.text.Selection;
import android.text.SpannableStringBuilder;
-import android.text.TextAssistant;
-import android.text.TextClassificationManager;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.transition.Scene;
@@ -792,8 +790,6 @@
private VoiceInteractor mVoiceInteractor;
- private TextAssistant mTextAssistant;
-
private CharSequence mTitle;
private int mTitleColor = 0;
@@ -1398,24 +1394,6 @@
}
/**
- * Sets the default {@link TextAssistant} for {@link android.widget.TextView}s in this Activity.
- */
- public void setTextAssistant(TextAssistant textAssistant) {
- mTextAssistant = textAssistant;
- }
-
- /**
- * Returns the default {@link TextAssistant} for {@link android.widget.TextView}s
- * in this Activity.
- */
- public TextAssistant getTextAssistant() {
- if (mTextAssistant != null) {
- return mTextAssistant;
- }
- return getSystemService(TextClassificationManager.class);
- }
-
- /**
* This is called for activities that set launchMode to "singleTop" in
* their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
* flag when calling {@link #startActivity}. In either case, when the
@@ -3130,7 +3108,7 @@
*/
@Override
public void enterPictureInPictureModeIfPossible() {
- if (mActivityInfo.resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE) {
+ if (mActivityInfo.supportsPictureInPicture()) {
enterPictureInPictureMode();
}
}
@@ -6814,6 +6792,8 @@
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
+
+ mWindow.setColorMode(info.colorMode);
}
/** @hide */
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1d4b038..3cb920a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -520,17 +520,17 @@
/** @hide Flag for registerUidObserver: report uid has become active. */
public static final int UID_OBSERVER_ACTIVE = 1<<3;
- /** @hide Mode for {@link IActivityManager#getAppStartMode}: normal free-to-run operation. */
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: normal free-to-run operation. */
public static final int APP_START_MODE_NORMAL = 0;
- /** @hide Mode for {@link IActivityManager#getAppStartMode}: delay running until later. */
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later. */
public static final int APP_START_MODE_DELAYED = 1;
- /** @hide Mode for {@link IActivityManager#getAppStartMode}: delay running until later, with
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later, with
* rigid errors (throwing exception). */
public static final int APP_START_MODE_DELAYED_RIGID = 2;
- /** @hide Mode for {@link IActivityManager#getAppStartMode}: disable/cancel pending
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: disable/cancel pending
* launches; this is the mode for ephemeral apps. */
public static final int APP_START_MODE_DISABLED = 3;
@@ -1482,7 +1482,7 @@
* True if the task can go in the docked stack.
* @hide
*/
- public boolean isDockable;
+ public boolean supportsSplitScreenMultiWindow;
/**
* The resize mode of the task. See {@link ActivityInfo#resizeMode}.
@@ -1533,7 +1533,7 @@
} else {
dest.writeInt(0);
}
- dest.writeInt(isDockable ? 1 : 0);
+ dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
dest.writeInt(resizeMode);
}
@@ -1557,7 +1557,7 @@
numActivities = source.readInt();
bounds = source.readInt() > 0 ?
Rect.CREATOR.createFromParcel(source) : null;
- isDockable = source.readInt() == 1;
+ supportsSplitScreenMultiWindow = source.readInt() == 1;
resizeMode = source.readInt();
}
@@ -1745,7 +1745,7 @@
* True if the task can go in the docked stack.
* @hide
*/
- public boolean isDockable;
+ public boolean supportsSplitScreenMultiWindow;
/**
* The resize mode of the task. See {@link ActivityInfo#resizeMode}.
@@ -1775,7 +1775,7 @@
Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
dest.writeInt(numActivities);
dest.writeInt(numRunning);
- dest.writeInt(isDockable ? 1 : 0);
+ dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
dest.writeInt(resizeMode);
}
@@ -1792,7 +1792,7 @@
description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
numActivities = source.readInt();
numRunning = source.readInt();
- isDockable = source.readInt() != 0;
+ supportsSplitScreenMultiWindow = source.readInt() != 0;
resizeMode = source.readInt();
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9cc13ab..603126b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -245,8 +245,10 @@
public static final int OP_READ_PHONE_NUMBER = 65;
/** @hide Request package installs through package installer */
public static final int OP_REQUEST_INSTALL_PACKAGES = 66;
+ /** @hide Enter picture-in-picture when hidden. */
+ public static final int OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
/** @hide */
- public static final int _NUM_OP = 67;
+ public static final int _NUM_OP = 68;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -464,6 +466,7 @@
OP_AUDIO_ACCESSIBILITY_VOLUME,
OP_READ_PHONE_NUMBER,
OP_REQUEST_INSTALL_PACKAGES,
+ OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
};
/**
@@ -538,6 +541,7 @@
null, // OP_AUDIO_ACCESSIBILITY_VOLUME
OPSTR_READ_PHONE_NUMBER,
null, // OP_REQUEST_INSTALL_PACKAGES
+ null,
};
/**
@@ -612,6 +616,7 @@
"AUDIO_ACCESSIBILITY_VOLUME",
"READ_PHONE_NUMBER",
"REQUEST_INSTALL_PACKAGES",
+ "OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE",
};
/**
@@ -686,6 +691,7 @@
null, // no permission for changing accessibility volume
Manifest.permission.READ_PHONE_NUMBER,
Manifest.permission.REQUEST_INSTALL_PACKAGES,
+ null, // no permission for entering picture-in-picture on hide
};
/**
@@ -761,6 +767,7 @@
UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
null, // READ_PHONE_NUMBER
null, // REQUEST_INSTALL_PACKAGES
+ null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
};
/**
@@ -835,6 +842,7 @@
false, // AUDIO_ACCESSIBILITY_VOLUME
false, // READ_PHONE_NUMBER
false, // REQUEST_INSTALL_PACKAGES
+ false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
};
/**
@@ -908,6 +916,7 @@
AppOpsManager.MODE_ALLOWED, // OP_AUDIO_ACCESSIBILITY_VOLUME
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES
+ AppOpsManager.MODE_ALLOWED, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
};
/**
@@ -985,6 +994,7 @@
false, // OP_AUDIO_ACCESSIBILITY_VOLUME
false,
false, // OP_REQUEST_INSTALL_PACKAGES
+ false, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
};
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1658e82..d37888d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1341,8 +1341,8 @@
}
try {
final Intent intent = ActivityManager.getService().registerReceiver(
- mMainThread.getApplicationThread(), mBasePackageName,
- rd, filter, broadcastPermission, userId);
+ mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
+ broadcastPermission, userId);
if (intent != null) {
intent.setExtrasClassLoader(getClassLoader());
intent.prepareToEnterProcess();
@@ -1591,12 +1591,15 @@
}
final IActivityManager am = ActivityManager.getService();
- if (am == null && UserHandle.getAppId(Binder.getCallingUid()) == Process.SYSTEM_UID) {
+ if (am == null) {
// Well this is super awkward; we somehow don't have an active
- // ActivityManager instance. If this is the system UID, then we
- // totally have whatever permission this is.
- Slog.w(TAG, "Missing ActivityManager; assuming system UID holds " + permission);
- return PackageManager.PERMISSION_GRANTED;
+ // ActivityManager instance. If we're testing a root or system
+ // UID, then they totally have whatever permission this is.
+ final int appId = UserHandle.getAppId(uid);
+ if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
+ Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);
+ return PackageManager.PERMISSION_GRANTED;
+ }
}
try {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 135c2a4..5a48793 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -264,7 +264,7 @@
boolean isImmersive(in IBinder token);
void setImmersive(in IBinder token, boolean immersive);
boolean isTopActivityImmersive();
- void crashApplication(int uid, int initialPid, in String packageName, in String message);
+ void crashApplication(int uid, int initialPid, in String packageName, int userId, in String message);
String getProviderMimeType(in Uri uri, int userId);
IBinder newUriPermissionOwner(in String name);
void grantUriPermissionFromOwner(in IBinder owner, int fromUid, in String targetPkg,
@@ -475,7 +475,7 @@
void suppressResizeConfigChanges(boolean suppress);
void moveTasksToFullscreenStack(int fromStackId, boolean onTop);
boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds);
- int getAppStartMode(int uid, in String packageName);
+ boolean isAppStartModeDisabled(int uid, in String packageName);
boolean unlockUser(int userid, in byte[] token, in byte[] secret,
in IProgressListener listener);
boolean isInMultiWindowMode(in IBinder token);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index f909af0..740af9c 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -47,6 +47,8 @@
in Notification notification, inout int[] idReceived, int userId);
void cancelNotificationWithTag(String pkg, String tag, int id, int userId);
+ void setShowBadge(String pkg, int uid, boolean showBadge);
+ boolean canShowBadge(String pkg, int uid);
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
boolean areNotificationsEnabled(String pkg);
@@ -73,8 +75,6 @@
void snoozeNotificationUntilContextFromListener(in INotificationListener token, String key, String snoozeCriterionId);
void snoozeNotificationUntilFromListener(in INotificationListener token, String key, long until);
- void snoozeNotificationFromListener(in INotificationListener token, String key);
- void unsnoozeNotificationFromListener(in INotificationListener token, String key);
void requestBindListener(in ComponentName component);
void requestUnbindListener(in INotificationListener token);
@@ -84,6 +84,7 @@
void setNotificationsShownFromListener(in INotificationListener token, in String[] keys);
ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim);
+ ParceledListSlice getSnoozedNotificationsFromListener(in INotificationListener token, int trim);
void requestHintsFromListener(in INotificationListener token, int hints);
int getHintsFromListener(in INotificationListener token);
void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
@@ -98,6 +99,7 @@
void updateNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel);
void deleteNotificationChannelFromAssistant(in INotificationListener token, String pkg, String channelId);
ParceledListSlice getNotificationChannelsFromAssistant(in INotificationListener token, String pkg);
+ void unsnoozeNotificationFromAssistant(in INotificationListener token, String key);
ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 036b47c..c0bf0c4 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -24,25 +24,25 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.IBinder;
-import android.os.IUserManager;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
import android.util.Log;
-import android.view.IWindowManager;
import android.view.IOnKeyguardExitResult;
-import android.view.WindowManager;
+import android.view.IWindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
import com.android.internal.policy.IKeyguardDismissCallback;
+import java.util.List;
+
/**
* Class that can be used to lock and unlock the keyboard. Get an instance of this
* class by calling {@link android.content.Context#getSystemService(java.lang.String)}
@@ -100,12 +100,9 @@
Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_DESCRIPTION, description);
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- intent.setPackage("com.google.android.apps.wearable.settings");
- } else {
- // For security reasons, only allow this to come from system settings.
- intent.setPackage("com.android.settings");
- }
+
+ // explicitly set the package for security
+ intent.setPackage(getSettingsPackageForIntent(intent));
return intent;
}
@@ -126,15 +123,23 @@
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_DESCRIPTION, description);
intent.putExtra(Intent.EXTRA_USER_ID, userId);
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- intent.setPackage("com.google.android.apps.wearable.settings");
- } else {
- // For security reasons, only allow this to come from system settings.
- intent.setPackage("com.android.settings");
- }
+
+ // explicitly set the package for security
+ intent.setPackage(getSettingsPackageForIntent(intent));
+
return intent;
}
+ private String getSettingsPackageForIntent(Intent intent) {
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+ .queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
+ for (int i = 0; i < resolveInfos.size(); i++) {
+ return resolveInfos.get(i).activityInfo.packageName;
+ }
+
+ return "com.android.settings";
+ }
+
/**
* @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
* and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
@@ -322,7 +327,7 @@
* password.
*/
public boolean isDeviceLocked() {
- return isDeviceLocked(UserHandle.getCallingUserId());
+ return isDeviceLocked(UserHandle.myUserId());
}
/**
@@ -347,7 +352,7 @@
* @return true if a PIN, pattern or password was set.
*/
public boolean isDeviceSecure() {
- return isDeviceSecure(UserHandle.getCallingUserId());
+ return isDeviceSecure(UserHandle.myUserId());
}
/**
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5b74e23..4172ed7 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1022,6 +1022,7 @@
private Icon mLargeIcon;
private String mChannelId;
+ private long mTimeout;
/**
* Structure to encapsulate a named action that can be shown as part of this notification.
@@ -1051,7 +1052,7 @@
private final Bundle mExtras;
private Icon mIcon;
private final RemoteInput[] mRemoteInputs;
- private boolean mAllowGeneratedReplies = false;
+ private boolean mAllowGeneratedReplies = true;
/**
* Small icon representing the action.
@@ -1093,7 +1094,7 @@
*/
@Deprecated
public Action(int icon, CharSequence title, PendingIntent intent) {
- this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, false);
+ this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true);
}
/** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
@@ -1166,7 +1167,7 @@
private final Icon mIcon;
private final CharSequence mTitle;
private final PendingIntent mIntent;
- private boolean mAllowGeneratedReplies;
+ private boolean mAllowGeneratedReplies = true;
private final Bundle mExtras;
private ArrayList<RemoteInput> mRemoteInputs;
@@ -1188,7 +1189,7 @@
* @param intent the {@link PendingIntent} to fire when users trigger this action
*/
public Builder(Icon icon, CharSequence title, PendingIntent intent) {
- this(icon, title, intent, new Bundle(), null, false);
+ this(icon, title, intent, new Bundle(), null, true);
}
/**
@@ -1260,7 +1261,7 @@
* @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
* otherwise
* @return this object for method chaining
- * The default value is {@code false}
+ * The default value is {@code true}
*/
public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
mAllowGeneratedReplies = allowGeneratedReplies;
@@ -1766,6 +1767,7 @@
if (parcel.readInt() != 0) {
mChannelId = parcel.readString();
}
+ mTimeout = parcel.readLong();
}
@Override
@@ -1872,6 +1874,7 @@
that.color = this.color;
that.mChannelId = this.mChannelId;
+ that.mTimeout = this.mTimeout;
if (!heavy) {
that.lightenPayload(); // will clean out extras
@@ -2128,6 +2131,7 @@
} else {
parcel.writeInt(0);
}
+ parcel.writeLong(mTimeout);
}
/**
@@ -2325,6 +2329,13 @@
}
/**
+ * Returns the time at which this notification should be canceled, if it's not canceled already.
+ */
+ public long getTimeout() {
+ return mTimeout;
+ }
+
+ /**
* The small icon representing this notification in the status bar and content view.
*
* @return the small icon representing this notification.
@@ -2532,6 +2543,15 @@
}
/**
+ * Specifies the time at which this notification should be canceled, if it is not already
+ * canceled.
+ */
+ public Builder setTimeout(long when) {
+ mN.mTimeout = when;
+ return this;
+ }
+
+ /**
* Add a timestamp pertaining to the notification (usually the time the event occurred).
*
* For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
@@ -7088,11 +7108,13 @@
private static final String EXTRA_FLAGS = "flags";
private static final String EXTRA_CONTENT_INTENT = "content_intent";
private static final String EXTRA_DELETE_INTENT = "delete_intent";
+ private static final String EXTRA_CHANNEL_ID = "channel_id";
// Flags bitwise-ored to mFlags
private static final int FLAG_AVAILABLE_ON_TV = 0x1;
private int mFlags;
+ private String mChannelId;
private PendingIntent mContentIntent;
private PendingIntent mDeleteIntent;
@@ -7113,6 +7135,7 @@
null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
if (bundle != null) {
mFlags = bundle.getInt(EXTRA_FLAGS);
+ mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
}
@@ -7128,6 +7151,7 @@
Bundle bundle = new Bundle();
bundle.putInt(EXTRA_FLAGS, mFlags);
+ bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
if (mContentIntent != null) {
bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
}
@@ -7149,6 +7173,23 @@
}
/**
+ * Specifies the channel the notification should be delivered on when shown on TV.
+ * It can be different from the channel that the notification is delivered to when
+ * posting on a non-TV device.
+ */
+ public TvExtender setChannel(String channelId) {
+ mChannelId = channelId;
+ return this;
+ }
+
+ /**
+ * Returns the id of the channel this notification posts to on TV.
+ */
+ public String getChannel() {
+ return mChannelId;
+ }
+
+ /**
* Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
* If provided, it is used instead of the content intent specified
* at the level of Notification.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 56ef791..be5f80a 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -122,6 +122,7 @@
private static final int DEFAULT_IMPORTANCE =
NotificationManager.IMPORTANCE_UNSPECIFIED;
private static final boolean DEFAULT_DELETED = false;
+ private static final boolean DEFAULT_SHOW_BADGE = true;
private final String mId;
private CharSequence mName;
@@ -133,7 +134,7 @@
private long[] mVibration;
private int mUserLockedFields;
private boolean mVibrationEnabled;
- private boolean mShowBadge;
+ private boolean mShowBadge = DEFAULT_SHOW_BADGE;
private boolean mDeleted = DEFAULT_DELETED;
/**
@@ -368,6 +369,8 @@
/**
* Returns whether notifications posted to this channel can appear as badges in a Launcher
* application.
+ *
+ * Note that badging may be disabled for other reasons.
*/
public boolean canShowBadge() {
return mShowBadge;
diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java
new file mode 100644
index 0000000..1f015a6
--- /dev/null
+++ b/core/java/android/app/RecoverableSecurityException.java
@@ -0,0 +1,201 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Specialization of {@link SecurityException} that contains additional
+ * information about how to involve the end user to recover from the exception.
+ * <p>
+ * This exception is only appropriate where there is a concrete action the user
+ * can take to recover and make forward progress, such as confirming or entering
+ * authentication credentials.
+ * <p class="note">
+ * Note: legacy code that receives this exception may treat it as a general
+ * {@link SecurityException}, and thus there is no guarantee that the messages
+ * contained will be shown to the end user.
+ * </p>
+ */
+public final class RecoverableSecurityException extends SecurityException implements Parcelable {
+ private static final String TAG = "RecoverableSecurityException";
+
+ private final CharSequence mUserMessage;
+ private final CharSequence mUserActionTitle;
+ private final PendingIntent mUserAction;
+
+ /** {@hide} */
+ public RecoverableSecurityException(Parcel in) {
+ this(new SecurityException(in.readString()), in.readCharSequence(), in.readCharSequence(),
+ PendingIntent.CREATOR.createFromParcel(in));
+ }
+
+ /**
+ * Create an instance ready to be thrown.
+ *
+ * @param cause original cause with details designed for engineering
+ * audiences.
+ * @param userMessage short message describing the issue for end user
+ * audiences, which may be shown in a notification or dialog.
+ * This should be less than 64 characters. For example: <em>PIN
+ * required to access Document.pdf</em>
+ * @param userActionTitle short title describing the primary action. This
+ * should be less than 24 characters. For example: <em>Enter
+ * PIN</em>
+ * @param userAction primary action that will initiate the recovery. This
+ * must launch an activity that is expected to set
+ * {@link Activity#setResult(int)} before finishing to
+ * communicate the final status of the recovery. For example,
+ * apps that observe {@link Activity#RESULT_OK} may choose to
+ * immediately retry their operation.
+ */
+ public RecoverableSecurityException(Throwable cause, CharSequence userMessage,
+ CharSequence userActionTitle, PendingIntent userAction) {
+ super(cause.getMessage());
+ mUserMessage = Preconditions.checkNotNull(userMessage);
+ mUserActionTitle = Preconditions.checkNotNull(userActionTitle);
+ mUserAction = Preconditions.checkNotNull(userAction);
+ }
+
+ /**
+ * Return short message describing the issue for end user audiences, which
+ * may be shown in a notification or dialog.
+ */
+ public CharSequence getUserMessage() {
+ return mUserMessage;
+ }
+
+ /**
+ * Return short title describing the primary action.
+ */
+ public CharSequence getUserActionTitle() {
+ return mUserActionTitle;
+ }
+
+ /**
+ * Return primary action that will initiate the recovery.
+ */
+ public PendingIntent getUserAction() {
+ return mUserAction;
+ }
+
+ /**
+ * Convenience method that will show a very simple notification populated
+ * with the details from this exception.
+ * <p>
+ * If you want more flexibility over retrying your original operation once
+ * the user action has finished, consider presenting your own UI that uses
+ * {@link Activity#startIntentSenderForResult} to launch the
+ * {@link PendingIntent#getIntentSender()} from {@link #getUserAction()}
+ * when requested. If the result of that activity is
+ * {@link Activity#RESULT_OK}, you should consider retrying.
+ * <p>
+ * This method will only display the most recent exception from any single
+ * remote UID; notifications from older exceptions will always be replaced.
+ */
+ public void showAsNotification(Context context) {
+ final Notification.Builder builder = new Notification.Builder(context)
+ .setSmallIcon(com.android.internal.R.drawable.ic_print_error)
+ .setContentTitle(mUserActionTitle)
+ .setContentText(mUserMessage)
+ .setContentIntent(mUserAction)
+ .setCategory(Notification.CATEGORY_ERROR);
+
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
+ nm.notify(TAG, mUserAction.getCreatorUid(), builder.build());
+ }
+
+ /**
+ * Convenience method that will show a very simple dialog populated with the
+ * details from this exception.
+ * <p>
+ * If you want more flexibility over retrying your original operation once
+ * the user action has finished, consider presenting your own UI that uses
+ * {@link Activity#startIntentSenderForResult} to launch the
+ * {@link PendingIntent#getIntentSender()} from {@link #getUserAction()}
+ * when requested. If the result of that activity is
+ * {@link Activity#RESULT_OK}, you should consider retrying.
+ * <p>
+ * This method will only display the most recent exception from any single
+ * remote UID; dialogs from older exceptions will always be replaced.
+ */
+ public void showAsDialog(Activity activity) {
+ final LocalDialog dialog = new LocalDialog();
+ final Bundle args = new Bundle();
+ args.putParcelable(TAG, this);
+ dialog.setArguments(args);
+
+ final String tag = TAG + "_" + mUserAction.getCreatorUid();
+ final FragmentManager fm = activity.getFragmentManager();
+ final FragmentTransaction ft = fm.beginTransaction();
+ final Fragment old = fm.findFragmentByTag(tag);
+ if (old != null) {
+ ft.remove(old);
+ }
+ ft.add(dialog, tag);
+ ft.commitAllowingStateLoss();
+ }
+
+ /** {@hide} */
+ public static class LocalDialog extends DialogFragment {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final RecoverableSecurityException e = getArguments().getParcelable(TAG);
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(e.mUserMessage)
+ .setPositiveButton(e.mUserActionTitle, (dialog, which) -> {
+ try {
+ e.mUserAction.send();
+ } catch (PendingIntent.CanceledException ignored) {
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(getMessage());
+ dest.writeCharSequence(mUserMessage);
+ dest.writeCharSequence(mUserActionTitle);
+ mUserAction.writeToParcel(dest, flags);
+ }
+
+ public static final Creator<RecoverableSecurityException> CREATOR =
+ new Creator<RecoverableSecurityException>() {
+ @Override
+ public RecoverableSecurityException createFromParcel(Parcel source) {
+ return new RecoverableSecurityException(source);
+ }
+
+ @Override
+ public RecoverableSecurityException[] newArray(int size) {
+ return new RecoverableSecurityException[size];
+ }
+ };
+}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9387019..5d8909c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -118,7 +118,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.text.TextClassificationManager;
+import android.text.FontManager;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -127,12 +127,14 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
import android.view.textservice.TextServicesManager;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.ISoundTriggerService;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.font.IFontManager;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
@@ -226,10 +228,10 @@
}});
registerService(Context.TEXT_CLASSIFICATION_SERVICE, TextClassificationManager.class,
- new StaticServiceFetcher<TextClassificationManager>() {
+ new CachedServiceFetcher<TextClassificationManager>() {
@Override
- public TextClassificationManager createService() {
- return new TextClassificationManager();
+ public TextClassificationManager createService(ContextImpl ctx) {
+ return new TextClassificationManager(ctx);
}});
registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class,
@@ -793,6 +795,15 @@
public IncidentManager createService(ContextImpl ctx) throws ServiceNotFoundException {
return new IncidentManager(ctx);
}});
+
+ registerService(Context.FONT_SERVICE, FontManager.class,
+ new CachedServiceFetcher<FontManager>() {
+ @Override
+ public FontManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.FONT_SERVICE);
+ return new FontManager(IFontManager.Stub.asInterface(b));
+ }});
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0da89eb..f06d19c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1132,6 +1132,23 @@
= "android.app.action.SHOW_DEVICE_MONITORING_DIALOG";
/**
+ * Broadcast Action: Sent after application delegation scopes are changed. The new list of
+ * delegation scopes will be sent in an extra identified by the {@link #EXTRA_DELEGATION_SCOPES}
+ * key.
+ *
+ * <p class=”note”> Note: This is a protected intent that can only be sent by the system.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED =
+ "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
+
+ /**
+ * A list of Strings corresponding to the delegation scopes given to an app in the
+ * {@link #ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED} broadcast.
+ */
+ public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
+
+ /**
* Flag used by {@link #addCrossProfileIntentFilter} to allow activities in
* the parent profile to access intents sent from the managed profile.
* That is, when an app in the managed profile calls
@@ -1194,6 +1211,19 @@
public static final int PERMISSION_GRANT_STATE_DENIED = 2;
/**
+ * Delegation of certificate installation and management. This scope grants access to the
+ * {@link #getInstalledCaCerts}, {@link #hasCaCertInstalled}, {@link #installCaCert},
+ * {@link #uninstallCaCert}, {@link #uninstallAllUserCaCerts} and {@link #installKeyPair} APIs.
+ */
+ public static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+
+ /**
+ * Delegation of application restrictions management. This scope grants access to the
+ * {@link #setApplicationRestrictions} and {@link #getApplicationRestrictions} APIs.
+ */
+ public static final String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+
+ /**
* No management for current user in-effect. This is the default.
* @hide
*/
@@ -3246,6 +3276,10 @@
/**
* Installs the given certificate as a user CA.
*
+ * The caller must be a profile or device owner on that user, or a delegate package given the
+ * {@link #DELEGATION_CERT_INSTALL} scope via {@link #setDelegatedScopes}; otherwise a
+ * security exception will be thrown.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if calling from a delegated certificate installer.
* @param certBuffer encoded form of the certificate to install.
@@ -3254,12 +3288,14 @@
* interrupted, true otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public boolean installCaCert(@Nullable ComponentName admin, byte[] certBuffer) {
throwIfParentInstance("installCaCert");
if (mService != null) {
try {
- return mService.installCaCert(admin, certBuffer);
+ return mService.installCaCert(admin, mContext.getPackageName(), certBuffer);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3270,18 +3306,24 @@
/**
* Uninstalls the given certificate from trusted user CAs, if present.
*
+ * The caller must be a profile or device owner on that user, or a delegate package given the
+ * {@link #DELEGATION_CERT_INSTALL} scope via {@link #setDelegatedScopes}; otherwise a
+ * security exception will be thrown.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if calling from a delegated certificate installer.
* @param certBuffer encoded form of the certificate to remove.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public void uninstallCaCert(@Nullable ComponentName admin, byte[] certBuffer) {
throwIfParentInstance("uninstallCaCert");
if (mService != null) {
try {
final String alias = getCaCertAlias(certBuffer);
- mService.uninstallCaCerts(admin, new String[] {alias});
+ mService.uninstallCaCerts(admin, mContext.getPackageName(), new String[] {alias});
} catch (CertificateException e) {
Log.w(TAG, "Unable to parse certificate", e);
} catch (RemoteException e) {
@@ -3306,7 +3348,7 @@
throwIfParentInstance("getInstalledCaCerts");
if (mService != null) {
try {
- mService.enforceCanManageCaCerts(admin);
+ mService.enforceCanManageCaCerts(admin, mContext.getPackageName());
final TrustedCertificateStore certStore = new TrustedCertificateStore();
for (String alias : certStore.userAliases()) {
try {
@@ -3335,8 +3377,8 @@
throwIfParentInstance("uninstallAllUserCaCerts");
if (mService != null) {
try {
- mService.uninstallCaCerts(admin, new TrustedCertificateStore().userAliases()
- .toArray(new String[0]));
+ mService.uninstallCaCerts(admin, mContext.getPackageName(),
+ new TrustedCertificateStore().userAliases() .toArray(new String[0]));
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -3356,7 +3398,7 @@
throwIfParentInstance("hasCaCertInstalled");
if (mService != null) {
try {
- mService.enforceCanManageCaCerts(admin);
+ mService.enforceCanManageCaCerts(admin, mContext.getPackageName());
return getCaCertAlias(certBuffer) != null;
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
@@ -3388,6 +3430,8 @@
* @return {@code true} if the keys were installed, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate cert, @NonNull String alias) {
@@ -3419,6 +3463,8 @@
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
* @see android.security.KeyChain#getCertificateChain
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
@@ -3431,8 +3477,8 @@
}
final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
.getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
- return mService.installKeyPair(admin, pkcs8Key, pemCert, pemChain, alias,
- requestAccess);
+ return mService.installKeyPair(admin, mContext.getPackageName(), pkcs8Key, pemCert,
+ pemChain, alias, requestAccess);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
@@ -3453,11 +3499,13 @@
* @return {@code true} if the private key alias no longer exists, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public boolean removeKeyPair(@Nullable ComponentName admin, @NonNull String alias) {
throwIfParentInstance("removeKeyPair");
try {
- return mService.removeKeyPair(admin, alias);
+ return mService.removeKeyPair(admin, mContext.getPackageName(), alias);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3493,7 +3541,11 @@
* @param installerPackage The package name of the certificate installer which will be given
* access. If {@code null} is given the current package will be cleared.
* @throws SecurityException if {@code admin} is not a device or a profile owner.
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #setDelegatedScopes}
+ * with the {@link #DELEGATION_CERT_INSTALL} scope instead.
*/
+ @Deprecated
public void setCertInstallerPackage(@NonNull ComponentName admin, @Nullable String
installerPackage) throws SecurityException {
throwIfParentInstance("setCertInstallerPackage");
@@ -3507,14 +3559,19 @@
}
/**
- * Called by a profile owner or device owner to retrieve the certificate installer for the user.
- * null if none is set.
+ * Called by a profile owner or device owner to retrieve the certificate installer for the user,
+ * or {@code null} if none is set. If there are multiple delegates this function will return one
+ * of them.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return The package name of the current delegated certificate installer, or {@code null} if
* none is set.
* @throws SecurityException if {@code admin} is not a device or a profile owner.
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #getDelegatePackages}
+ * with the {@link #DELEGATION_CERT_INSTALL} scope instead.
*/
+ @Deprecated
public @Nullable String getCertInstallerPackage(@NonNull ComponentName admin)
throws SecurityException {
throwIfParentInstance("getCertInstallerPackage");
@@ -3529,6 +3586,83 @@
}
/**
+ * Called by a profile owner or device owner to grant access to privileged APIs to another app.
+ * Granted APIs are determined by {@code scopes}, which is a list of the {@code DELEGATION_*}
+ * constants.
+ * <p>
+ * Delegated scopes are a per-user state. The delegated access is persistent until it is later
+ * cleared by calling this method with an empty {@code scopes} list or uninstalling the
+ * {@code delegatePackage}.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param delegatePackage The package name of the app which will be given access.
+ * @param scopes The groups of privileged APIs whose access should be granted to
+ * {@code delegatedPackage}.
+ * @throws SecurityException if {@code admin} is not a device or a profile owner.
+ */
+ public void setDelegatedScopes(@NonNull ComponentName admin, @NonNull String delegatePackage,
+ @NonNull List<String> scopes) {
+ throwIfParentInstance("setDelegatedScopes");
+ if (mService != null) {
+ try {
+ mService.setDelegatedScopes(admin, delegatePackage, scopes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Called by a profile owner or device owner to retrieve a list of the scopes given to a
+ * delegate package. Other apps can use this method to retrieve their own delegated scopes by
+ * passing {@code null} for {@code admin} and their own package name as
+ * {@code delegatedPackage}.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is {@code delegatedPackage}.
+ * @param delegatedPackage The package name of the app whose scopes should be retrieved.
+ * @return A list containing the scopes given to {@code delegatedPackage}.
+ * @throws SecurityException if {@code admin} is not a device or a profile owner.
+ */
+ @NonNull
+ public List<String> getDelegatedScopes(@NonNull ComponentName admin,
+ @NonNull String delegatedPackage) {
+ throwIfParentInstance("getDelegatedScopes");
+ if (mService != null) {
+ try {
+ return mService.getDelegatedScopes(admin, delegatedPackage);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Called by a profile owner or device owner to retrieve a list of delegate packages that were
+ * granted a delegation scope.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param delegationScope The scope whose delegates should be retrieved.
+ * @return A list of package names of the current delegated packages for
+ {@code delegationScope}.
+ * @throws SecurityException if {@code admin} is not a device or a profile owner.
+ */
+ @Nullable
+ public List<String> getDelegatePackages(@NonNull ComponentName admin,
+ @NonNull String delegationScope) {
+ throwIfParentInstance("getDelegatePackages");
+ if (mService != null) {
+ try {
+ return mService.getDelegatePackages(admin, delegationScope);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Called by a device or profile owner to configure an always-on VPN connection through a
* specific application for the current user.
*
@@ -3735,13 +3869,13 @@
}
/**
- * Called by a device owner to set whether auto time is required. If auto time is required the
- * user cannot set the date and time, but has to use network date and time.
+ * Called by a device or profile owner to set whether auto time is required. If auto time is
+ * required, no user will be able set the date and time and network date and time will be used.
* <p>
* Note: if auto time is required the user can still manually set the time zone.
* <p>
- * The calling device admin must be a device owner. If it is not, a security exception will be
- * thrown.
+ * The calling device admin must be a device or profile owner. If it is not, a security
+ * exception will be thrown.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param required Whether auto time is set required or not.
@@ -4686,7 +4820,11 @@
* APIs. If {@code null} is given the current package will be cleared.
* @throws SecurityException if {@code admin} is not a device or profile owner.
* @throws NameNotFoundException if {@code packageName} is not found
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #setDelegatedScopes}
+ * with the {@link #DELEGATION_APP_RESTRICTIONS} scope instead.
*/
+ @Deprecated
public void setApplicationRestrictionsManagingPackage(@NonNull ComponentName admin,
@Nullable String packageName) throws NameNotFoundException {
throwIfParentInstance("setApplicationRestrictionsManagingPackage");
@@ -4703,14 +4841,20 @@
/**
* Called by a profile owner or device owner to retrieve the application restrictions managing
- * package for the current user, or {@code null} if none is set.
+ * package for the current user, or {@code null} if none is set. If there are multiple
+ * delegates this function will return one of them.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return The package name allowed to manage application restrictions on the current user, or
* {@code null} if none is set.
* @throws SecurityException if {@code admin} is not a device or profile owner.
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #getDelegatePackages}
+ * with the {@link #DELEGATION_APP_RESTRICTIONS} scope instead.
*/
- public @Nullable String getApplicationRestrictionsManagingPackage(
+ @Deprecated
+ @Nullable
+ public String getApplicationRestrictionsManagingPackage(
@NonNull ComponentName admin) {
throwIfParentInstance("getApplicationRestrictionsManagingPackage");
if (mService != null) {
@@ -4730,12 +4874,17 @@
*
* <p>This is done by comparing the calling Linux uid with the uid of the package specified by
* that method.
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #getDelegatedScopes}
+ * instead.
*/
+ @Deprecated
public boolean isCallerApplicationRestrictionsManagingPackage() {
throwIfParentInstance("isCallerApplicationRestrictionsManagingPackage");
if (mService != null) {
try {
- return mService.isCallerApplicationRestrictionsManagingPackage();
+ return mService.isCallerApplicationRestrictionsManagingPackage(
+ mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4747,8 +4896,8 @@
* Sets the application restrictions for a given target application running in the calling user.
* <p>
* The caller must be a profile or device owner on that user, or the package allowed to manage
- * application restrictions via {@link #setApplicationRestrictionsManagingPackage}; otherwise a
- * security exception will be thrown.
+ * application restrictions via {@link #setDelegatedScopes} with the
+ * {@link #DELEGATION_APP_RESTRICTIONS} scope; otherwise a security exception will be thrown.
* <p>
* The provided {@link Bundle} consists of key-value pairs, where the types of values may be:
* <ul>
@@ -4775,7 +4924,8 @@
* @param settings A {@link Bundle} to be parsed by the receiving application, conveying a new
* set of active restrictions.
* @throws SecurityException if {@code admin} is not a device or profile owner.
- * @see #setApplicationRestrictionsManagingPackage
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_APP_RESTRICTIONS
* @see UserManager#KEY_RESTRICTIONS_PENDING
*/
@WorkerThread
@@ -4784,7 +4934,8 @@
throwIfParentInstance("setApplicationRestrictions");
if (mService != null) {
try {
- mService.setApplicationRestrictions(admin, packageName, settings);
+ mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName,
+ settings);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5523,8 +5674,8 @@
* user.
* <p>
* The caller must be a profile or device owner on that user, or the package allowed to manage
- * application restrictions via {@link #setApplicationRestrictionsManagingPackage}; otherwise a
- * security exception will be thrown.
+ * application restrictions via {@link #setDelegatedScopes} with the
+ * {@link #DELEGATION_APP_RESTRICTIONS} scope; otherwise a security exception will be thrown.
*
* <p>NOTE: The method performs disk I/O and shouldn't be called on the main thread
*
@@ -5535,7 +5686,8 @@
* {@link DevicePolicyManager#setApplicationRestrictions} was called, or an empty
* {@link Bundle} if no restrictions have been set.
* @throws SecurityException if {@code admin} is not a device or profile owner.
- * @see #setApplicationRestrictionsManagingPackage
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_APP_RESTRICTIONS
*/
@WorkerThread
public @NonNull Bundle getApplicationRestrictions(
@@ -5543,7 +5695,8 @@
throwIfParentInstance("getApplicationRestrictions");
if (mService != null) {
try {
- return mService.getApplicationRestrictions(admin, packageName);
+ return mService.getApplicationRestrictions(admin, mContext.getPackageName(),
+ packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 80ef557..983de56 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -153,17 +153,21 @@
String[] setPackagesSuspended(in ComponentName admin, in String[] packageNames, boolean suspended);
boolean isPackageSuspended(in ComponentName admin, String packageName);
- boolean installCaCert(in ComponentName admin, in byte[] certBuffer);
- void uninstallCaCerts(in ComponentName admin, in String[] aliases);
- void enforceCanManageCaCerts(in ComponentName admin);
+ boolean installCaCert(in ComponentName admin, String callerPackage, in byte[] certBuffer);
+ void uninstallCaCerts(in ComponentName admin, String callerPackage, in String[] aliases);
+ void enforceCanManageCaCerts(in ComponentName admin, in String callerPackage);
boolean approveCaCert(in String alias, int userHandle, boolean approval);
boolean isCaCertApproved(in String alias, int userHandle);
- boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer,
- in byte[] certChainBuffer, String alias, boolean requestAccess);
- boolean removeKeyPair(in ComponentName who, String alias);
+ boolean installKeyPair(in ComponentName who, in String callerPackage, in byte[] privKeyBuffer,
+ in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess);
+ boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
+ void setDelegatedScopes(in ComponentName who, in String delegatePackage, in List<String> scopes);
+ List<String> getDelegatedScopes(in ComponentName who, String delegatePackage);
+ List<String> getDelegatePackages(in ComponentName who, String scope);
+
void setCertInstallerPackage(in ComponentName who, String installerPackage);
String getCertInstallerPackage(in ComponentName who);
@@ -173,11 +177,11 @@
void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
- void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
- Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
+ void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings);
+ Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName);
boolean setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName);
String getApplicationRestrictionsManagingPackage(in ComponentName admin);
- boolean isCallerApplicationRestrictionsManagingPackage();
+ boolean isCallerApplicationRestrictionsManagingPackage(in String callerPackage);
void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
ComponentName getRestrictionsProvider(int userHandle);
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 540683d..f0abe33 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -17,6 +17,7 @@
package android.app.backup;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
@@ -157,6 +158,25 @@
@SystemApi
public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
+
+ /**
+ * This error code is passed to {@link SelectBackupTransportCallback#onFailure(int)}
+ * if the requested transport is unavailable.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_TRANSPORT_UNAVAILABLE = -1;
+
+ /**
+ * This error code is passed to {@link SelectBackupTransportCallback#onFailure(int)} if the
+ * requested transport is not a valid BackupTransport.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_TRANSPORT_INVALID = -2;
+
private Context mContext;
private static IBackupManager sService;
@@ -390,17 +410,20 @@
}
/**
- * Specify the current backup transport. Callers must hold the
- * android.permission.BACKUP permission to use this method.
+ * Specify the current backup transport.
+ *
+ * <p> Callers must hold the android.permission.BACKUP permission to use this method.
*
* @param transport The name of the transport to select. This should be one
- * of the names returned by {@link #listAllTransports()}.
+ * of the names returned by {@link #listAllTransports()}. This is the String returned by
+ * {@link BackupTransport#name()} for the particular transport.
* @return The name of the previously selected transport. If the given transport
* name is not one of the currently available transports, no change is made to
* the current transport setting and the method returns null.
*
* @hide
*/
+ @Deprecated
@SystemApi
public String selectBackupTransport(String transport) {
checkServiceBinder();
@@ -415,6 +438,34 @@
}
/**
+ * Specify the current backup transport and get notified when the transport is ready to be used.
+ * This method is async because BackupManager might need to bind to the specified transport
+ * which is in a separate process.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param transport ComponentName of the service hosting the transport. This is different from
+ * the transport's name that is returned by {@link BackupTransport#name()}.
+ * @param listener A listener object to get a callback on the transport being selected.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void selectBackupTransport(ComponentName transport,
+ SelectBackupTransportCallback listener) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ SelectTransportListenerWrapper wrapper = listener == null ?
+ null : new SelectTransportListenerWrapper(mContext, listener);
+ sService.selectBackupTransportAsync(transport, wrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "selectBackupTransportAsync() couldn't connect");
+ }
+ }
+ }
+
+ /**
* Schedule an immediate backup attempt for all pending key/value updates. This
* is primarily intended for transports to use when they detect a suitable
* opportunity for doing a backup pass. If there are no pending updates to
@@ -598,4 +649,35 @@
mHandler.obtainMessage(MSG_FINISHED, status, 0));
}
}
+
+ private class SelectTransportListenerWrapper extends ISelectBackupTransportCallback.Stub {
+
+ private final Handler mHandler;
+ private final SelectBackupTransportCallback mListener;
+
+ SelectTransportListenerWrapper(Context context, SelectBackupTransportCallback listener) {
+ mHandler = new Handler(context.getMainLooper());
+ mListener = listener;
+ }
+
+ @Override
+ public void onSuccess(final String transportName) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onSuccess(transportName);
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(final int reason) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onFailure(reason);
+ }
+ });
+ }
+ }
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index fe23c28..1657e2e 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -19,8 +19,10 @@
import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
import android.os.ParcelFileDescriptor;
import android.content.Intent;
+import android.content.ComponentName;
/**
* Direct interface to the Backup Manager Service that applications invoke on. The only
@@ -217,6 +219,8 @@
*/
String[] listAllTransports();
+ ComponentName[] listAllTransportComponents();
+
/**
* Retrieve the list of whitelisted transport components. Callers do </i>not</i> need
* any special permission.
@@ -238,6 +242,21 @@
String selectBackupTransport(String transport);
/**
+ * Specify the current backup transport and get notified when the transport is ready to be used.
+ * This method is async because BackupManager might need to bind to the specified transport
+ * which is in a separate process.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param transport ComponentName of the service hosting the transport. This is different from
+ * the transport's name that is returned by {@link BackupTransport#name()}.
+ * @param listener A listener object to get a callback on the transport being selected.
+ *
+ * @hide
+ */
+ void selectBackupTransportAsync(in ComponentName transport, ISelectBackupTransportCallback listener);
+
+ /**
* Get the configuration Intent, if any, from the given transport. Callers must
* hold the android.permission.BACKUP permission in order to use this method.
*
diff --git a/core/java/android/app/backup/ISelectBackupTransportCallback.aidl b/core/java/android/app/backup/ISelectBackupTransportCallback.aidl
new file mode 100644
index 0000000..5de7c5e
--- /dev/null
+++ b/core/java/android/app/backup/ISelectBackupTransportCallback.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.app.backup;
+
+/**
+ * Callback class for receiving success or failure callbacks on selecting a backup transport. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+oneway interface ISelectBackupTransportCallback {
+
+ /**
+ * Called when BackupManager has successfully bound to the requested transport.
+ *
+ * @param transportName Name of the selected transport. This is the String returned by
+ * {@link BackupTransport#name()}.
+ */
+ void onSuccess(String transportName);
+
+ /**
+ * Called when BackupManager fails to bind to the requested transport.
+ *
+ * @param reason Error code denoting reason for failure.
+ */
+ void onFailure(int reason);
+}
diff --git a/core/java/android/app/backup/SelectBackupTransportCallback.java b/core/java/android/app/backup/SelectBackupTransportCallback.java
new file mode 100644
index 0000000..0c8a0dc
--- /dev/null
+++ b/core/java/android/app/backup/SelectBackupTransportCallback.java
@@ -0,0 +1,44 @@
+/*
+ * 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.app.backup;
+
+import android.annotation.SystemApi;
+
+/**
+ * Callback class for receiving success or failure callbacks on selecting a backup transport. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SelectBackupTransportCallback {
+
+ /**
+ * Called when BackupManager has successfully bound to the requested transport.
+ *
+ * @param transportName Name of the selected transport. This is the String returned by
+ * {@link BackupTransport#name()}.
+ */
+ public void onSuccess(String transportName){}
+
+ /**
+ * Called when BackupManager fails to bind to the requested transport.
+ *
+ * @param reason Error code denoting reason for failure.
+ */
+ public void onFailure(int reason){}
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 596a9fd..38e6fbe 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -34,9 +34,7 @@
import android.annotation.UserIdInt;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
-import android.app.LoadedApk;
import android.app.Notification;
-import android.app.admin.DevicePolicyManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -64,6 +62,7 @@
import android.view.DisplayAdjustments;
import android.view.ViewDebug;
import android.view.WindowManager;
+import android.view.textclassifier.TextClassificationManager;
import java.io.File;
import java.io.FileInputStream;
@@ -3348,10 +3347,10 @@
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.text.TextClassificationManager} for text classification services.
+ * {@link TextClassificationManager} for text classification services.
*
* @see #getSystemService
- * @see android.text.TextClassificationManager
+ * @see TextClassificationManager
*/
public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
@@ -3744,6 +3743,11 @@
public static final String DEVICE_IDENTIFIERS_SERVICE = "device_identifiers";
/**
+ * Service that provides System font data.
+ */
+ public static final String FONT_SERVICE = "font";
+
+ /**
* Service to report a system health "incident"
* @hide
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8cc9a3a..44e106e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1628,6 +1628,19 @@
public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
/**
+ * Activity Action: Launch application uninstaller.
+ * <p>
+ * Input: The data must be a package: URI whose scheme specific part is
+ * the package name of the current installed package to be uninstalled.
+ * You can optionally supply {@link #EXTRA_RETURN_RESULT}.
+ * <p>
+ * Output: Nothing.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE";
+
+ /**
* Specify whether the package should be uninstalled for all users.
* @hide because these should not be part of normal application flow.
*/
@@ -3966,6 +3979,12 @@
public static final String EXTRA_EPHEMERAL_TOKEN = "android.intent.extra.EPHEMERAL_TOKEN";
/**
+ * The version code of the app to install components from.
+ * @hide
+ */
+ public static final String EXTRA_VERSION_CODE = "android.intent.extra.VERSION_CODE";
+
+ /**
* A Bundle forming a mapping of potential target package names to different extras Bundles
* to add to the default intent extras in {@link #EXTRA_INTENT} when used with
* {@link #ACTION_CHOOSER}. Each key should be a package name. The package need not
@@ -4842,6 +4861,10 @@
* or not running) apps, regardless of whether that would be done by default. By
* default they will only receive broadcasts if the broadcast has specified an
* explicit component or package name.
+ *
+ * NOTE: dumpstate uses this flag numerically, so when its value is changed
+ * the broadcast code there must also be changed to match.
+ *
* @hide
*/
public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 4bd091d..92cb709 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -177,10 +177,14 @@
*/
public static final int RESIZE_MODE_RESIZEABLE = 2;
/**
- * Activity is resizeable and supported picture-in-picture mode.
+ * Activity is resizeable and supported picture-in-picture mode. This flag is now deprecated
+ * since activities do not need to be resizeable to support picture-in-picture.
+ * See {@link #FLAG_SUPPORTS_PICTURE_IN_PICTURE}.
+ *
* @hide
+ * @deprecated
*/
- public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE = 3;
+ public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED = 3;
/**
* Activity does not support resizing, but we are forcing it to be resizeable. Only affects
* certain pre-N apps where we force them to be resizeable.
@@ -220,6 +224,44 @@
public String requestedVrComponent;
/**
+ * Value for {@link #colorMode} indicating that the activity should use the
+ * default color mode (sRGB, low dynamic range).
+ *
+ * @see android.R.attr#colorMode
+ */
+ public static final int COLOR_MODE_DEFAULT = 0;
+ /**
+ * Value of {@link #colorMode} indicating that the activity should use a
+ * wide color gamut if the presentation display supports it.
+ *
+ * @see android.R.attr#colorMode
+ */
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1;
+ /**
+ * Value of {@link #colorMode} indicating that the activity should use a
+ * high dynamic range if the presentation display supports it.
+ *
+ * @see android.R.attr#colorMode
+ */
+ public static final int COLOR_MODE_HDR = 2;
+
+ /** @hide */
+ @IntDef({
+ COLOR_MODE_DEFAULT,
+ COLOR_MODE_WIDE_COLOR_GAMUT,
+ COLOR_MODE_HDR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ColorMode {}
+
+ /**
+ * The color mode requested by this activity. The target display may not be
+ * able to honor the request.
+ */
+ @ColorMode
+ public int colorMode = COLOR_MODE_DEFAULT;
+
+ /**
* Bit in {@link #flags} indicating whether this activity is able to
* run in multiple processes. If
* true, the system may instantiate it in the some process as the
@@ -369,6 +411,13 @@
public static final int FLAG_VISIBLE_TO_EPHEMERAL = 0x100000;
/**
+ * Bit in {@link #flags} indicating if the activity supports picture-in-picture mode.
+ * See {@link android.R.attr#supportsPictureInPicture}.
+ * @hide
+ */
+ public static final int FLAG_SUPPORTS_PICTURE_IN_PICTURE = 0x200000;
+
+ /**
* @hide Bit in {@link #flags}: If set, this component will only be seen
* by the system user. Only works with broadcast receivers. Set from the
* android.R.attr#systemUserOnly attribute.
@@ -566,7 +615,7 @@
CONFIG_SMALLEST_SCREEN_SIZE,
CONFIG_DENSITY,
CONFIG_LAYOUT_DIRECTION,
- CONFIG_COLORIMETRY,
+ CONFIG_COLOR_MODE,
CONFIG_FONT_SCALE,
})
@Retention(RetentionPolicy.SOURCE)
@@ -675,7 +724,7 @@
* can itself handle the change to the display color gamut or dynamic
* range. Set from the {@link android.R.attr#configChanges} attribute.
*/
- public static final int CONFIG_COLORIMETRY = 0x4000;
+ public static final int CONFIG_COLOR_MODE = 0x4000;
/**
* Bit in {@link #configChanges} that indicates that the activity
* can itself handle asset path changes. Set from the {@link android.R.attr#configChanges}
@@ -713,7 +762,7 @@
Configuration.NATIVE_CONFIG_SMALLEST_SCREEN_SIZE, // SMALLEST SCREEN SIZE
Configuration.NATIVE_CONFIG_DENSITY, // DENSITY
Configuration.NATIVE_CONFIG_LAYOUTDIR, // LAYOUT DIRECTION
- Configuration.NATIVE_CONFIG_COLORIMETRY, // COLORIMETRY
+ Configuration.NATIVE_CONFIG_COLOR_MODE, // COLOR_MODE
};
/**
@@ -770,7 +819,7 @@
* {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
* {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT},
* {@link #CONFIG_DENSITY}, {@link #CONFIG_LAYOUT_DIRECTION} and
- * {@link #CONFIG_COLORIMETRY}.
+ * {@link #CONFIG_COLOR_MODE}.
* Set from the {@link android.R.attr#configChanges} attribute.
*/
public int configChanges;
@@ -873,6 +922,7 @@
resizeMode = orig.resizeMode;
requestedVrComponent = orig.requestedVrComponent;
rotationAnimation = orig.rotationAnimation;
+ colorMode = orig.colorMode;
}
/**
@@ -926,10 +976,17 @@
|| screenOrientation == SCREEN_ORIENTATION_USER_PORTRAIT;
}
+ /**
+ * Returns true if the activity supports picture-in-picture.
+ * @hide
+ */
+ public boolean supportsPictureInPicture() {
+ return (flags & FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0;
+ }
+
/** @hide */
public static boolean isResizeableMode(int mode) {
return mode == RESIZE_MODE_RESIZEABLE
- || mode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE
|| mode == RESIZE_MODE_FORCE_RESIZEABLE
|| mode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
|| mode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
@@ -953,8 +1010,6 @@
return "RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION";
case RESIZE_MODE_RESIZEABLE:
return "RESIZE_MODE_RESIZEABLE";
- case RESIZE_MODE_RESIZEABLE_AND_PIPABLE:
- return "RESIZE_MODE_RESIZEABLE_AND_PIPABLE";
case RESIZE_MODE_FORCE_RESIZEABLE:
return "RESIZE_MODE_FORCE_RESIZEABLE";
case RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY:
@@ -1055,6 +1110,7 @@
dest.writeInt(resizeMode);
dest.writeString(requestedVrComponent);
dest.writeInt(rotationAnimation);
+ dest.writeInt(colorMode);
}
public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -1090,6 +1146,7 @@
resizeMode = source.readInt();
requestedVrComponent = source.readString();
rotationAnimation = source.readInt();
+ colorMode = source.readInt();
}
/**
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 5cd15dd..b091d7e 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -72,6 +72,11 @@
*/
public boolean directBootAware = false;
+ /**
+ * The name of the split that contains the code for this component.
+ */
+ public String splitName;
+
/** @removed */
@Deprecated
public boolean encryptionAware = false;
diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java
index f620088..1d7b8f2 100644
--- a/core/java/android/content/pm/EphemeralResolveInfo.java
+++ b/core/java/android/content/pm/EphemeralResolveInfo.java
@@ -43,9 +43,11 @@
private final String mPackageName;
/** The filters used to match domain */
private final List<EphemeralIntentFilter> mFilters;
+ /** The version code of the app that this class resolves to */
+ private final int mVersionCode;
/** Filters only for legacy clients */
@Deprecated
- private List<IntentFilter> mLegacyFilters;
+ private final List<IntentFilter> mLegacyFilters;
@Deprecated
public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName,
@@ -59,10 +61,17 @@
mFilters.add(new EphemeralIntentFilter(packageName, filters));
mLegacyFilters = new ArrayList<IntentFilter>(filters.size());
mLegacyFilters.addAll(filters);
+ mVersionCode = -1;
+ }
+
+ @Deprecated
+ public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName,
+ @Nullable List<EphemeralIntentFilter> filters) {
+ this(digest, packageName, filters, -1 /*versionCode*/);
}
public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName,
- @Nullable List<EphemeralIntentFilter> filters) {
+ @Nullable List<EphemeralIntentFilter> filters, int versionConde) {
// validate arguments
if ((packageName == null && (filters != null && filters.size() != 0))
|| (packageName != null && (filters == null || filters.size() == 0))) {
@@ -75,7 +84,9 @@
} else {
mFilters = null;
}
+ mLegacyFilters = null;
mPackageName = packageName;
+ mVersionCode = versionConde;
}
public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName,
@@ -88,6 +99,7 @@
mPackageName = in.readString();
mFilters = new ArrayList<EphemeralIntentFilter>();
in.readList(mFilters, null /*loader*/);
+ mVersionCode = in.readInt();
mLegacyFilters = new ArrayList<IntentFilter>();
in.readList(mLegacyFilters, null /*loader*/);
}
@@ -104,15 +116,19 @@
return mPackageName;
}
+ public List<EphemeralIntentFilter> getIntentFilters() {
+ return mFilters;
+ }
+
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
@Deprecated
public List<IntentFilter> getFilters() {
return mLegacyFilters;
}
- public List<EphemeralIntentFilter> getIntentFilters() {
- return mFilters;
- }
-
@Override
public int describeContents() {
return 0;
@@ -123,6 +139,7 @@
out.writeParcelable(mDigest, flags);
out.writeString(mPackageName);
out.writeList(mFilters);
+ out.writeInt(mVersionCode);
out.writeList(mLegacyFilters);
}
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 5152416..c08bd1d 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -38,15 +38,21 @@
interface ILauncherApps {
void addOnAppsChangedListener(String callingPackage, in IOnAppsChangedListener listener);
void removeOnAppsChangedListener(in IOnAppsChangedListener listener);
- ParceledListSlice getLauncherActivities(String packageName, in UserHandle user);
- ActivityInfo resolveActivity(in ComponentName component, in UserHandle user);
- void startActivityAsUser(in ComponentName component, in Rect sourceBounds,
+ ParceledListSlice getLauncherActivities(
+ String callingPackage, String packageName, in UserHandle user);
+ ActivityInfo resolveActivity(
+ String callingPackage, in ComponentName component, in UserHandle user);
+ void startActivityAsUser(String callingPackage,
+ in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
- void showAppDetailsAsUser(in ComponentName component, in Rect sourceBounds,
+ void showAppDetailsAsUser(
+ String callingPackage, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
- boolean isPackageEnabled(String packageName, in UserHandle user);
- boolean isActivityEnabled(in ComponentName component, in UserHandle user);
- ApplicationInfo getApplicationInfo(String packageName, int flags, in UserHandle user);
+ boolean isPackageEnabled(String callingPackage, String packageName, in UserHandle user);
+ boolean isActivityEnabled(
+ String callingPackage, in ComponentName component, in UserHandle user);
+ ApplicationInfo getApplicationInfo(
+ String callingPackage, String packageName, int flags, in UserHandle user);
ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
@@ -62,7 +68,8 @@
boolean hasShortcutHostPermission(String callingPackage);
- ParceledListSlice getShortcutConfigActivities(String packageName, in UserHandle user);
+ ParceledListSlice getShortcutConfigActivities(
+ String callingPackage, String packageName, in UserHandle user);
IntentSender getShortcutConfigActivityIntent(String callingPackage, in ComponentName component,
in UserHandle user);
}
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java
index f12abf3..068973b 100644
--- a/core/java/android/content/pm/IntentFilterVerificationInfo.java
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java
@@ -22,6 +22,7 @@
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -36,6 +37,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Set;
/**
* The {@link com.android.server.pm.PackageManagerService} maintains some
@@ -43,6 +45,7 @@
*
* @hide
*/
+@SystemApi
public final class IntentFilterVerificationInfo implements Parcelable {
private static final String TAG = IntentFilterVerificationInfo.class.getName();
@@ -55,22 +58,26 @@
private String mPackageName;
private int mMainStatus;
+ /** @hide */
public IntentFilterVerificationInfo() {
mPackageName = null;
mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
+ /** @hide */
public IntentFilterVerificationInfo(String packageName, ArraySet<String> domains) {
mPackageName = packageName;
mDomains = domains;
mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
+ /** @hide */
public IntentFilterVerificationInfo(XmlPullParser parser)
throws IOException, XmlPullParserException {
readFromXml(parser);
}
+ /** @hide */
public IntentFilterVerificationInfo(Parcel source) {
readFromParcel(source);
}
@@ -83,6 +90,7 @@
return mMainStatus;
}
+ /** @hide */
public void setStatus(int s) {
if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED &&
s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
@@ -92,14 +100,16 @@
}
}
- public ArraySet<String> getDomains() {
+ public Set<String> getDomains() {
return mDomains;
}
+ /** @hide */
public void setDomains(ArraySet<String> list) {
mDomains = list;
}
+ /** @hide */
public String getDomainsString() {
StringBuilder sb = new StringBuilder();
for (String str : mDomains) {
@@ -135,6 +145,7 @@
}
}
+ /** @hide */
public void readFromXml(XmlPullParser parser) throws XmlPullParserException,
IOException {
mPackageName = getStringFromXml(parser, ATTR_PACKAGE_NAME, null);
@@ -170,6 +181,7 @@
}
}
+ /** @hide */
public void writeToXml(XmlSerializer serializer) throws IOException {
serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
serializer.attribute(null, ATTR_STATUS, String.valueOf(mMainStatus));
@@ -180,10 +192,12 @@
}
}
+ /** @hide */
public String getStatusString() {
return getStatusStringFromValue(((long)mMainStatus) << 32);
}
+ /** @hide */
public static String getStatusStringFromValue(long val) {
StringBuilder sb = new StringBuilder();
switch ((int)(val >> 32)) {
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index c6a8674..57d2ba7 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -407,7 +407,8 @@
*/
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
try {
- return convertToActivityList(mService.getLauncherActivities(packageName, user), user);
+ return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
+ packageName, user), user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -423,7 +424,8 @@
*/
public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
try {
- ActivityInfo ai = mService.resolveActivity(intent.getComponent(), user);
+ ActivityInfo ai = mService.resolveActivity(mContext.getPackageName(),
+ intent.getComponent(), user);
if (ai != null) {
LauncherActivityInfo info = new LauncherActivityInfo(mContext, ai, user);
return info;
@@ -448,7 +450,8 @@
Log.i(TAG, "StartMainActivity " + component + " " + user.getIdentifier());
}
try {
- mService.startActivityAsUser(component, sourceBounds, opts, user);
+ mService.startActivityAsUser(mContext.getPackageName(),
+ component, sourceBounds, opts, user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -466,7 +469,8 @@
public void startAppDetailsActivity(ComponentName component, UserHandle user,
Rect sourceBounds, Bundle opts) {
try {
- mService.showAppDetailsAsUser(component, sourceBounds, opts, user);
+ mService.showAppDetailsAsUser(mContext.getPackageName(),
+ component, sourceBounds, opts, user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -486,7 +490,8 @@
public List<LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String packageName,
@NonNull UserHandle user) {
try {
- return convertToActivityList(mService.getShortcutConfigActivities(packageName, user),
+ return convertToActivityList(mService.getShortcutConfigActivities(
+ mContext.getPackageName(), packageName, user),
user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
@@ -549,7 +554,7 @@
*/
public boolean isPackageEnabled(String packageName, UserHandle user) {
try {
- return mService.isPackageEnabled(packageName, user);
+ return mService.isPackageEnabled(mContext.getPackageName(), packageName, user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -569,7 +574,7 @@
public ApplicationInfo getApplicationInfo(String packageName, @ApplicationInfoFlags int flags,
UserHandle user) {
try {
- return mService.getApplicationInfo(packageName, flags, user);
+ return mService.getApplicationInfo(mContext.getPackageName(), packageName, flags, user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -585,7 +590,7 @@
*/
public boolean isActivityEnabled(ComponentName component, UserHandle user) {
try {
- return mService.isActivityEnabled(component, user);
+ return mService.isActivityEnabled(mContext.getPackageName(), component, user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7bdc56d..ffcb1f3 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1444,6 +1444,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0;
/**
@@ -1454,6 +1455,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1;
/**
@@ -1465,6 +1467,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2;
/**
@@ -1476,6 +1479,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3;
/**
@@ -1489,6 +1493,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4;
/**
@@ -2157,6 +2162,15 @@
public static final String FEATURE_WATCH = "android.hardware.type.watch";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This is a device for IoT and may not have an UI. An embedded
+ * device is defined as a full stack Android device with or without a display and no
+ * user-installable apps.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device supports printing.
*/
@@ -5070,6 +5084,7 @@
*
* @hide
*/
+ @SystemApi
public abstract int getIntentVerificationStatusAsUser(String packageName, @UserIdInt int userId);
/**
@@ -5092,6 +5107,7 @@
*
* @hide
*/
+ @SystemApi
public abstract boolean updateIntentVerificationStatusAsUser(String packageName, int status,
@UserIdInt int userId);
@@ -5107,6 +5123,7 @@
*
* @hide
*/
+ @SystemApi
public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications(
String packageName);
@@ -5121,6 +5138,7 @@
*
* @hide
*/
+ @SystemApi
public abstract List<IntentFilter> getAllIntentFilters(String packageName);
/**
@@ -5134,6 +5152,7 @@
* @hide
*/
@TestApi
+ @SystemApi
public abstract String getDefaultBrowserPackageNameAsUser(@UserIdInt int userId);
/**
@@ -5148,6 +5167,7 @@
*
* @hide
*/
+ @SystemApi
public abstract boolean setDefaultBrowserPackageNameAsUser(String packageName,
@UserIdInt int userId);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d8d7abe..cd51bce 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -18,12 +18,12 @@
import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
+import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
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.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -2402,7 +2402,7 @@
// cannot be windowed / resized. Note that an SDK version of 0 is common for
// pre-Doughnut applications.
if (pkg.applicationInfo.usesCompatibilityMode()) {
- adjustPackageToBeUnresizeable(pkg);
+ adjustPackageToBeUnresizeableAndUnpipable(pkg);
}
return pkg;
}
@@ -2413,9 +2413,10 @@
*
* @param pkg The package which needs to be marked as unresizable.
*/
- private void adjustPackageToBeUnresizeable(Package pkg) {
+ private void adjustPackageToBeUnresizeableAndUnpipable(Package pkg) {
for (Activity a : pkg.activities) {
a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ a.info.flags &= ~FLAG_SUPPORTS_PICTURE_IN_PICTURE;
}
}
@@ -3899,6 +3900,9 @@
a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
owner.applicationInfo.taskAffinity, str, outError);
+ a.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_splitName, 0);
+
a.info.flags = 0;
if (sa.getBoolean(
R.styleable.AndroidManifestActivity_multiprocess, false)) {
@@ -3997,6 +4001,11 @@
setActivityResizeMode(a.info, sa, owner);
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture,
+ false)) {
+ a.info.flags |= FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+ }
+
if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysFocusable, false)) {
a.info.flags |= FLAG_ALWAYS_FOCUSABLE;
}
@@ -4017,6 +4026,9 @@
a.info.rotationAnimation =
sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, ROTATION_ANIMATION_ROTATE);
+
+ a.info.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode,
+ ActivityInfo.COLOR_MODE_DEFAULT);
} else {
a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
a.info.configChanges = 0;
@@ -4158,16 +4170,13 @@
private void setActivityResizeMode(ActivityInfo aInfo, TypedArray sa, Package owner) {
final boolean appExplicitDefault = (owner.applicationInfo.privateFlags
& PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET) != 0;
- final boolean supportsPip =
- sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture, false);
if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity)
|| appExplicitDefault) {
// Activity or app explicitly set if it is resizeable or not;
if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity,
appExplicitDefault)) {
- aInfo.resizeMode =
- supportsPip ? RESIZE_MODE_RESIZEABLE_AND_PIPABLE : RESIZE_MODE_RESIZEABLE;
+ aInfo.resizeMode = RESIZE_MODE_RESIZEABLE;
} else {
aInfo.resizeMode = RESIZE_MODE_UNRESIZEABLE;
}
@@ -4520,6 +4529,9 @@
com.android.internal.R.styleable.AndroidManifestProvider_initOrder,
0);
+ p.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_splitName, 0);
+
p.info.flags = 0;
if (sa.getBoolean(
@@ -4816,6 +4828,9 @@
s.info.permission = str.length() > 0 ? str.toString().intern() : null;
}
+ s.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestService_splitName, 0);
+
s.info.flags = 0;
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestService_stopWithTask,
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index a81329d..99fbee1 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -102,66 +102,66 @@
public boolean userSetLocale;
- /** Constant for {@link #colorimetry}: bits that encode whether the screen is wide gamut. */
- public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 0x3;
+ /** Constant for {@link #colorMode}: bits that encode whether the screen is wide gamut. */
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x3;
/**
- * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} value
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value
* indicating that it is unknown whether or not the screen is wide gamut.
*/
- public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0x0;
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0x0;
/**
- * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} value
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value
* indicating that the screen is not wide gamut.
* <p>Corresponds to the <code>-nowidecg</code> resource qualifier.</p>
*/
- public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 0x1;
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 0x1;
/**
- * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} value
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value
* indicating that the screen is wide gamut.
* <p>Corresponds to the <code>-widecg</code> resource qualifier.</p>
*/
- public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 0x2;
+ public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 0x2;
- /** Constant for {@link #colorimetry}: bits that encode whether the dynamic range of the screen. */
- public static final int COLORIMETRY_HDR_MASK = 0xc;
- /** Constant for {@link #colorimetry}: bits shift to get the screen dynamic range. */
- public static final int COLORIMETRY_HDR_SHIFT = 2;
+ /** Constant for {@link #colorMode}: bits that encode whether the dynamic range of the screen. */
+ public static final int COLOR_MODE_HDR_MASK = 0xc;
+ /** Constant for {@link #colorMode}: bits shift to get the screen dynamic range. */
+ public static final int COLOR_MODE_HDR_SHIFT = 2;
/**
- * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_HDR_MASK} value
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
* indicating that it is unknown whether or not the screen is HDR.
*/
- public static final int COLORIMETRY_HDR_UNDEFINED = 0x0;
+ public static final int COLOR_MODE_HDR_UNDEFINED = 0x0;
/**
- * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_HDR_MASK} value
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
* indicating that the screen is not HDR (low/standard dynamic range).
* <p>Corresponds to the <code>-lowdr</code> resource qualifier.</p>
*/
- public static final int COLORIMETRY_HDR_NO = 0x1 << COLORIMETRY_HDR_SHIFT;
+ public static final int COLOR_MODE_HDR_NO = 0x1 << COLOR_MODE_HDR_SHIFT;
/**
- * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_HDR_MASK} value
+ * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
* indicating that the screen is HDR (dynamic range).
* <p>Corresponds to the <code>-highdr</code> resource qualifier.</p>
*/
- public static final int COLORIMETRY_HDR_YES = 0x2 << COLORIMETRY_HDR_SHIFT;
+ public static final int COLOR_MODE_HDR_YES = 0x2 << COLOR_MODE_HDR_SHIFT;
- /** Constant for {@link #colorimetry}: a value indicating that colorimetry is undefined */
+ /** Constant for {@link #colorMode}: a value indicating that the color mode is undefined */
@SuppressWarnings("PointlessBitwiseExpression")
- public static final int COLORIMETRY_UNDEFINED = COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED |
- COLORIMETRY_HDR_UNDEFINED;
+ public static final int COLOR_MODE_UNDEFINED = COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED |
+ COLOR_MODE_HDR_UNDEFINED;
/**
* Bit mask of for color capabilities of the screen. Currently there are two fields:
- * <p>The {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} bits define the color gamut of
+ * <p>The {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} bits define the color gamut of
* the screen. They may be one of
- * {@link #COLORIMETRY_WIDE_COLOR_GAMUT_NO} or {@link #COLORIMETRY_WIDE_COLOR_GAMUT_YES}.</p>
+ * {@link #COLOR_MODE_WIDE_COLOR_GAMUT_NO} or {@link #COLOR_MODE_WIDE_COLOR_GAMUT_YES}.</p>
*
- * <p>The {@link #COLORIMETRY_HDR_MASK} defines the dynamic range of the screen. They may be
- * one of {@link #COLORIMETRY_HDR_NO} or {@link #COLORIMETRY_HDR_YES}.</p>
+ * <p>The {@link #COLOR_MODE_HDR_MASK} defines the dynamic range of the screen. They may be
+ * one of {@link #COLOR_MODE_HDR_NO} or {@link #COLOR_MODE_HDR_YES}.</p>
*
* <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
* Multiple Screens</a> for more information.</p>
*/
- public int colorimetry;
+ public int colorMode;
/** Constant for {@link #screenLayout}: bits that encode the size. */
public static final int SCREENLAYOUT_SIZE_MASK = 0x0f;
@@ -393,8 +393,8 @@
if ((diff & ActivityInfo.CONFIG_SCREEN_LAYOUT) != 0) {
list.add("CONFIG_SCREEN_LAYOUT");
}
- if ((diff & ActivityInfo.CONFIG_COLORIMETRY) != 0) {
- list.add("CONFIG_COLORIMETRY");
+ if ((diff & ActivityInfo.CONFIG_COLOR_MODE) != 0) {
+ list.add("CONFIG_COLOR_MODE");
}
if ((diff & ActivityInfo.CONFIG_UI_MODE) != 0) {
list.add("CONFIG_UI_MODE");
@@ -776,7 +776,7 @@
NATIVE_CONFIG_UI_MODE,
NATIVE_CONFIG_SMALLEST_SCREEN_SIZE,
NATIVE_CONFIG_LAYOUTDIR,
- NATIVE_CONFIG_COLORIMETRY,
+ NATIVE_CONFIG_COLOR_MODE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface NativeConfig {}
@@ -813,8 +813,8 @@
public static final int NATIVE_CONFIG_SMALLEST_SCREEN_SIZE = 0x2000;
/** @hide Native-specific bit mask for LAYOUTDIR config ; DO NOT USE UNLESS YOU ARE SURE.*/
public static final int NATIVE_CONFIG_LAYOUTDIR = 0x4000;
- /** @hide Native-specific bit mask for COLORIMETRY config ; DO NOT USE UNLESS YOU ARE SURE.*/
- public static final int NATIVE_CONFIG_COLORIMETRY = 0x10000;
+ /** @hide Native-specific bit mask for COLOR_MODE config ; DO NOT USE UNLESS YOU ARE SURE.*/
+ public static final int NATIVE_CONFIG_COLOR_MODE = 0x10000;
/**
* <p>Construct an invalid Configuration. This state is only suitable for constructing a
@@ -873,7 +873,7 @@
navigationHidden = o.navigationHidden;
orientation = o.orientation;
screenLayout = o.screenLayout;
- colorimetry = o.colorimetry;
+ colorMode = o.colorMode;
uiMode = o.uiMode;
screenWidthDp = o.screenWidthDp;
screenHeightDp = o.screenHeightDp;
@@ -954,19 +954,19 @@
default: sb.append(" layoutLong=");
sb.append(screenLayout&SCREENLAYOUT_LONG_MASK); break;
}
- switch ((colorimetry&COLORIMETRY_HDR_MASK)) {
- case COLORIMETRY_HDR_UNDEFINED: sb.append(" ?ldr"); break; // most likely not HDR
- case COLORIMETRY_HDR_NO: /* ldr is not interesting to print */ break;
- case COLORIMETRY_HDR_YES: sb.append(" hdr"); break;
+ switch ((colorMode &COLOR_MODE_HDR_MASK)) {
+ case COLOR_MODE_HDR_UNDEFINED: sb.append(" ?ldr"); break; // most likely not HDR
+ case COLOR_MODE_HDR_NO: /* ldr is not interesting to print */ break;
+ case COLOR_MODE_HDR_YES: sb.append(" hdr"); break;
default: sb.append(" dynamicRange=");
- sb.append(colorimetry&COLORIMETRY_HDR_MASK); break;
+ sb.append(colorMode &COLOR_MODE_HDR_MASK); break;
}
- switch ((colorimetry&COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) {
- case COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED: sb.append(" ?wideColorGamut"); break;
- case COLORIMETRY_WIDE_COLOR_GAMUT_NO: /* not wide is not interesting to print */ break;
- case COLORIMETRY_WIDE_COLOR_GAMUT_YES: sb.append(" widecg"); break;
+ switch ((colorMode &COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+ case COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED: sb.append(" ?wideColorGamut"); break;
+ case COLOR_MODE_WIDE_COLOR_GAMUT_NO: /* not wide is not interesting to print */ break;
+ case COLOR_MODE_WIDE_COLOR_GAMUT_YES: sb.append(" widecg"); break;
default: sb.append(" wideColorGamut=");
- sb.append(colorimetry&COLORIMETRY_WIDE_COLOR_GAMUT_MASK); break;
+ sb.append(colorMode &COLOR_MODE_WIDE_COLOR_GAMUT_MASK); break;
}
switch (orientation) {
case ORIENTATION_UNDEFINED: sb.append(" ?orien"); break;
@@ -1059,7 +1059,7 @@
navigationHidden = NAVIGATIONHIDDEN_UNDEFINED;
orientation = ORIENTATION_UNDEFINED;
screenLayout = SCREENLAYOUT_UNDEFINED;
- colorimetry = COLORIMETRY_UNDEFINED;
+ colorMode = COLOR_MODE_UNDEFINED;
uiMode = UI_MODE_TYPE_UNDEFINED;
screenWidthDp = compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
@@ -1195,21 +1195,21 @@
| (delta.screenLayout & SCREENLAYOUT_COMPAT_NEEDED);
}
- if (((delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) !=
- COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED)
- && (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)
- != (colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) {
- changed |= ActivityInfo.CONFIG_COLORIMETRY;
- colorimetry = (colorimetry & ~COLORIMETRY_WIDE_COLOR_GAMUT_MASK)
- | (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK);
+ if (((delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+ COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED)
+ && (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)
+ != (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+ changed |= ActivityInfo.CONFIG_COLOR_MODE;
+ colorMode = (colorMode & ~COLOR_MODE_WIDE_COLOR_GAMUT_MASK)
+ | (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK);
}
- if (((delta.colorimetry & COLORIMETRY_HDR_MASK) != COLORIMETRY_HDR_UNDEFINED)
- && (delta.colorimetry & COLORIMETRY_HDR_MASK)
- != (colorimetry & COLORIMETRY_HDR_MASK)) {
- changed |= ActivityInfo.CONFIG_COLORIMETRY;
- colorimetry = (colorimetry & ~COLORIMETRY_HDR_MASK)
- | (delta.colorimetry & COLORIMETRY_HDR_MASK);
+ if (((delta.colorMode & COLOR_MODE_HDR_MASK) != COLOR_MODE_HDR_UNDEFINED)
+ && (delta.colorMode & COLOR_MODE_HDR_MASK)
+ != (colorMode & COLOR_MODE_HDR_MASK)) {
+ changed |= ActivityInfo.CONFIG_COLOR_MODE;
+ colorMode = (colorMode & ~COLOR_MODE_HDR_MASK)
+ | (delta.colorMode & COLOR_MODE_HDR_MASK);
}
if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)
@@ -1362,17 +1362,17 @@
changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
}
if ((compareUndefined ||
- (delta.colorimetry & COLORIMETRY_HDR_MASK) != COLORIMETRY_HDR_UNDEFINED)
- && (colorimetry & COLORIMETRY_HDR_MASK) !=
- (delta.colorimetry & COLORIMETRY_HDR_MASK)) {
- changed |= ActivityInfo.CONFIG_COLORIMETRY;
+ (delta.colorMode & COLOR_MODE_HDR_MASK) != COLOR_MODE_HDR_UNDEFINED)
+ && (colorMode & COLOR_MODE_HDR_MASK) !=
+ (delta.colorMode & COLOR_MODE_HDR_MASK)) {
+ changed |= ActivityInfo.CONFIG_COLOR_MODE;
}
if ((compareUndefined ||
- (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) !=
- COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED)
- && (colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) !=
- (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) {
- changed |= ActivityInfo.CONFIG_COLORIMETRY;
+ (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+ COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED)
+ && (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+ (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+ changed |= ActivityInfo.CONFIG_COLOR_MODE;
}
if ((compareUndefined || delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED))
&& uiMode != delta.uiMode) {
@@ -1485,7 +1485,7 @@
dest.writeInt(navigationHidden);
dest.writeInt(orientation);
dest.writeInt(screenLayout);
- dest.writeInt(colorimetry);
+ dest.writeInt(colorMode);
dest.writeInt(uiMode);
dest.writeInt(screenWidthDp);
dest.writeInt(screenHeightDp);
@@ -1520,7 +1520,7 @@
navigationHidden = source.readInt();
orientation = source.readInt();
screenLayout = source.readInt();
- colorimetry = source.readInt();
+ colorMode = source.readInt();
uiMode = source.readInt();
screenWidthDp = source.readInt();
screenHeightDp = source.readInt();
@@ -1602,7 +1602,7 @@
if (n != 0) return n;
n = this.orientation - that.orientation;
if (n != 0) return n;
- n = this.colorimetry - that.colorimetry;
+ n = this.colorMode - that.colorMode;
if (n != 0) return n;
n = this.screenLayout - that.screenLayout;
if (n != 0) return n;
@@ -1649,7 +1649,7 @@
result = 31 * result + navigationHidden;
result = 31 * result + orientation;
result = 31 * result + screenLayout;
- result = 31 * result + colorimetry;
+ result = 31 * result + colorMode;
result = 31 * result + uiMode;
result = 31 * result + screenWidthDp;
result = 31 * result + screenHeightDp;
@@ -1763,7 +1763,7 @@
* @return true if the screen has a wide color gamut, false otherwise
*/
public boolean isScreenWideColorGamut() {
- return (colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) == COLORIMETRY_WIDE_COLOR_GAMUT_YES;
+ return (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) == COLOR_MODE_WIDE_COLOR_GAMUT_YES;
}
/**
@@ -1772,7 +1772,7 @@
* @return true if the screen has a high dynamic range, false otherwise
*/
public boolean isScreenHdr() {
- return (colorimetry & COLORIMETRY_HDR_MASK) == COLORIMETRY_HDR_YES;
+ return (colorMode & COLOR_MODE_HDR_MASK) == COLOR_MODE_HDR_YES;
}
/**
@@ -1907,22 +1907,22 @@
break;
}
- switch (config.colorimetry & Configuration.COLORIMETRY_HDR_MASK) {
- case Configuration.COLORIMETRY_HDR_YES:
+ switch (config.colorMode & Configuration.COLOR_MODE_HDR_MASK) {
+ case Configuration.COLOR_MODE_HDR_YES:
parts.add("highdr");
break;
- case Configuration.COLORIMETRY_HDR_NO:
+ case Configuration.COLOR_MODE_HDR_NO:
parts.add("lowdr");
break;
default:
break;
}
- switch (config.colorimetry & Configuration.COLORIMETRY_WIDE_COLOR_GAMUT_MASK) {
- case Configuration.COLORIMETRY_WIDE_COLOR_GAMUT_YES:
+ switch (config.colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK) {
+ case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES:
parts.add("widecg");
break;
- case Configuration.COLORIMETRY_WIDE_COLOR_GAMUT_NO:
+ case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO:
parts.add("nowidecg");
break;
default:
@@ -2154,14 +2154,14 @@
delta.screenLayout |= change.screenLayout & SCREENLAYOUT_ROUND_MASK;
}
- if ((base.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) !=
- (change.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) {
- delta.colorimetry |= change.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK;
+ if ((base.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+ (change.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+ delta.colorMode |= change.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK;
}
- if ((base.colorimetry & COLORIMETRY_HDR_MASK) !=
- (change.colorimetry & COLORIMETRY_HDR_MASK)) {
- delta.colorimetry |= change.colorimetry & COLORIMETRY_HDR_MASK;
+ if ((base.colorMode & COLOR_MODE_HDR_MASK) !=
+ (change.colorMode & COLOR_MODE_HDR_MASK)) {
+ delta.colorMode |= change.colorMode & COLOR_MODE_HDR_MASK;
}
if ((base.uiMode & UI_MODE_TYPE_MASK) != (change.uiMode & UI_MODE_TYPE_MASK)) {
@@ -2206,7 +2206,7 @@
private static final String XML_ATTR_NAVIGATION_HIDDEN = "navHid";
private static final String XML_ATTR_ORIENTATION = "ori";
private static final String XML_ATTR_SCREEN_LAYOUT = "scrLay";
- private static final String XML_ATTR_COLORIMETRY = "clrMtry";
+ private static final String XML_ATTR_COLOR_MODE = "clrMod";
private static final String XML_ATTR_UI_MODE = "ui";
private static final String XML_ATTR_SCREEN_WIDTH = "width";
private static final String XML_ATTR_SCREEN_HEIGHT = "height";
@@ -2249,8 +2249,8 @@
ORIENTATION_UNDEFINED);
configOut.screenLayout = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_LAYOUT,
SCREENLAYOUT_UNDEFINED);
- configOut.colorimetry = XmlUtils.readIntAttribute(parser, XML_ATTR_COLORIMETRY,
- COLORIMETRY_UNDEFINED);
+ configOut.colorMode = XmlUtils.readIntAttribute(parser, XML_ATTR_COLOR_MODE,
+ COLOR_MODE_UNDEFINED);
configOut.uiMode = XmlUtils.readIntAttribute(parser, XML_ATTR_UI_MODE, 0);
configOut.screenWidthDp = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_WIDTH,
SCREEN_WIDTH_DP_UNDEFINED);
@@ -2313,8 +2313,8 @@
if (config.screenLayout != SCREENLAYOUT_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_LAYOUT, config.screenLayout);
}
- if (config.colorimetry != COLORIMETRY_UNDEFINED) {
- XmlUtils.writeIntAttribute(xml, XML_ATTR_COLORIMETRY, config.colorimetry);
+ if (config.colorMode != COLOR_MODE_UNDEFINED) {
+ XmlUtils.writeIntAttribute(xml, XML_ATTR_COLOR_MODE, config.colorMode);
}
if (config.uiMode != 0) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_UI_MODE, config.uiMode);
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
new file mode 100644
index 0000000..5bdd966
--- /dev/null
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.hardware;
+
+parcelable HardwareBuffer;
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
new file mode 100644
index 0000000..fffb1d7
--- /dev/null
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -0,0 +1,318 @@
+/*
+ * 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.hardware;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import dalvik.annotation.optimization.FastNative;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * HardwareBuffer wraps a native <code>AHardwareBuffer</code> object, which is a low-level object
+ * representing a memory buffer accessible by various hardware units. HardwareBuffer allows sharing
+ * buffers across different application processes. In particular, HardwareBuffers may be mappable
+ * to memory accessibly to various hardware systems, such as the GPU, a sensor or context hub, or
+ * other auxiliary processing units.
+ *
+ * For more information, see the NDK documentation for <code>AHardwareBuffer</code>.
+ */
+public final class HardwareBuffer implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({RGBA_8888, RGBA_FP16, RGBX_8888, RGB_888, RGB_565})
+ public @interface Format {};
+
+ /** Format: 8 bits each red, green, blue, alpha */
+ public static final int RGBA_8888 = 1;
+ /** Format: 8 bits each red, green, blue, alpha, alpha is always 0xFF */
+ public static final int RGBX_8888 = 2;
+ /** Format: 8 bits each red, green, blue, no alpha */
+ public static final int RGB_888 = 3;
+ /** Format: 5 bits each red and blue, 6 bits green, no alpha */
+ public static final int RGB_565 = 4;
+ /** Format: 16 bits each red, green, blue, alpha */
+ public static final int RGBA_FP16 = 5;
+
+ // Note: do not rename, this field is used by native code
+ private long mNativeObject;
+
+ // Invoked on destruction
+ private Runnable mCleaner;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {USAGE0_CPU_READ, USAGE0_CPU_READ_OFTEN, USAGE0_CPU_WRITE,
+ USAGE0_CPU_WRITE_OFTEN, USAGE0_GPU_SAMPLED_IMAGE, USAGE0_GPU_COLOR_OUTPUT,
+ USAGE0_GPU_STORAGE_IMAGE, USAGE0_GPU_CUBEMAP, USAGE0_GPU_DATA_BUFFER,
+ USAGE0_PROTECTED_CONTENT, USAGE0_SENSOR_DIRECT_DATA, USAGE0_VIDEO_ENCODE})
+ public @interface Usage0 {};
+
+ /** Usage0: the buffer will sometimes be read by the CPU */
+ public static final long USAGE0_CPU_READ = (1 << 1);
+ /** Usage0: the buffer will often be read by the CPU*/
+ public static final long USAGE0_CPU_READ_OFTEN = (1 << 2 | USAGE0_CPU_READ);
+ /** Usage0: the buffer will sometimes be written to by the CPU */
+ public static final long USAGE0_CPU_WRITE = (1 << 5);
+ /** Usage0: the buffer will often be written to by the CPU */
+ public static final long USAGE0_CPU_WRITE_OFTEN = (1 << 6 | USAGE0_CPU_WRITE);
+ /** Usage0: the buffer will be read from by the GPU */
+ public static final long USAGE0_GPU_SAMPLED_IMAGE = (1 << 10);
+ /** Usage0: the buffer will be written to by the GPU */
+ public static final long USAGE0_GPU_COLOR_OUTPUT = (1 << 11);
+ /** Usage0: the buffer will be read from and written to by the GPU */
+ public static final long USAGE0_GPU_STORAGE_IMAGE = (USAGE0_GPU_SAMPLED_IMAGE |
+ USAGE0_GPU_COLOR_OUTPUT);
+ /** Usage0: the buffer will be used as a cubemap texture */
+ public static final long USAGE0_GPU_CUBEMAP = (1 << 13);
+ /** Usage0: the buffer will be used as a shader storage or uniform buffer object*/
+ public static final long USAGE0_GPU_DATA_BUFFER = (1 << 14);
+ /** Usage0: the buffer must not be used outside of a protected hardware path */
+ public static final long USAGE0_PROTECTED_CONTENT = (1 << 18);
+ /** Usage0: the buffer will be used for sensor direct data */
+ public static final long USAGE0_SENSOR_DIRECT_DATA = (1 << 29);
+ /** Usage0: the buffer will be read by a hardware video encoder */
+ public static final long USAGE0_VIDEO_ENCODE = (1 << 21);
+
+ // The approximate size of a native AHardwareBuffer object.
+ private static final long NATIVE_HARDWARE_BUFFER_SIZE = 232;
+ /**
+ * Creates a new <code>HardwareBuffer</code> instance.
+ *
+ * <p>Calling this method will throw an <code>IllegalStateException</code> if
+ * format is not a supported Format type.</p>
+ *
+ * @param width The width in pixels of the buffer
+ * @param height The height in pixels of the buffer
+ * @param format The format of each pixel, one of {@link #RGBA_8888}, {@link #RGBA_FP16},
+ * {@link #RGBX_8888}, {@link #RGB_565}, {@link #RGB_888}
+ * @param layers The number of layers in the buffer
+ * @param usage Flags describing how the buffer will be used, one of
+ * {@link #USAGE0_CPU_READ}, {@link #USAGE0_CPU_READ_OFTEN}, {@link #USAGE0_CPU_WRITE},
+ * {@link #USAGE0_CPU_WRITE_OFTEN}, {@link #USAGE0_GPU_SAMPLED_IMAGE},
+ * {@link #USAGE0_GPU_COLOR_OUTPUT},{@link #USAGE0_GPU_STORAGE_IMAGE},
+ * {@link #USAGE0_GPU_CUBEMAP}, {@link #USAGE0_GPU_DATA_BUFFER},
+ * {@link #USAGE0_PROTECTED_CONTENT}, {@link #USAGE0_SENSOR_DIRECT_DATA},
+ * {@link #USAGE0_VIDEO_ENCODE}
+ *
+ * @return A <code>HardwareBuffer</code> instance if successful, or throws an
+ * IllegalArgumentException if the dimensions passed are invalid (either zero, negative, or
+ * too large to allocate), if the format is not supported, if the requested number of layers
+ * is less than one or not supported, or if the passed usage flags are not a supported set.
+ */
+ @NonNull
+ public static HardwareBuffer create(int width, int height, @Format int format, int layers,
+ @Usage0 long usage) {
+ if (!HardwareBuffer.isSupportedFormat(format)) {
+ throw new IllegalArgumentException("Invalid pixel format " + format);
+ }
+ if (width <= 0) {
+ throw new IllegalArgumentException("Invalid width " + width);
+ }
+ if (height <= 0) {
+ throw new IllegalArgumentException("Invalid height " + height);
+ }
+ if (layers <= 0) {
+ throw new IllegalArgumentException("Invalid layer count " + layers);
+ }
+ long nativeObject = nCreateHardwareBuffer(width, height, format, layers, usage);
+ if (nativeObject == 0) {
+ throw new IllegalArgumentException("Unable to create a HardwareBuffer, either the " +
+ "dimensions passed were too large, too many image layers were requested, " +
+ "or an invalid set of usage flags was passed");
+ }
+ return new HardwareBuffer(nativeObject);
+ }
+
+ /**
+ * Private use only. See {@link #create(int, int, int, int, int, long, long)}. May also be
+ * called from JNI using an already allocated native <code>HardwareBuffer</code>.
+ */
+ private HardwareBuffer(long nativeObject) {
+ mNativeObject = nativeObject;
+
+ long nativeSize = NATIVE_HARDWARE_BUFFER_SIZE;
+ NativeAllocationRegistry registry = new NativeAllocationRegistry(
+ HardwareBuffer.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
+ mCleaner = registry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ /**
+ * Returns the width of this buffer in pixels.
+ */
+ public int getWidth() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its width "
+ + "cannot be obtained.");
+ }
+ return nGetWidth(mNativeObject);
+ }
+
+ /**
+ * Returns the height of this buffer in pixels.
+ */
+ public int getHeight() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its height "
+ + "cannot be obtained.");
+ }
+ return nGetHeight(mNativeObject);
+ }
+
+ /**
+ * Returns the format of this buffer, one of {@link #RGBA_8888}, {@link #RGBA_FP16},
+ * {@link #RGBX_8888}, {@link #RGB_565}, or {@link #RGB_888}.
+ */
+ public int getFormat() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its format "
+ + "cannot be obtained.");
+ }
+ return nGetFormat(mNativeObject);
+ }
+
+ /**
+ * Returns the number of layers in this buffer.
+ */
+ public int getLayers() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its layer "
+ + "count cannot be obtained.");
+ }
+ return nGetLayers(mNativeObject);
+ }
+
+ /**
+ * Returns the usage flags of the usage hints set on this buffer.
+ */
+ public long getUsage() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its usage "
+ + "cannot be obtained.");
+ }
+ return nGetUsage(mNativeObject);
+ }
+
+ /**
+ * Destroys this buffer immediately. Calling this method frees up any
+ * underlying native resources. After calling this method, this buffer
+ * must not be used in any way.
+ *
+ * @see #isDestroyed()
+ */
+ public void destroy() {
+ if (mNativeObject != 0) {
+ mNativeObject = 0;
+ mCleaner.run();
+ mCleaner = null;
+ }
+ }
+
+ /**
+ * Indicates whether this buffer has been destroyed. A destroyed buffer
+ * cannot be used in any way: the buffer cannot be written to a parcel, etc.
+ *
+ * @return True if this <code>HardwareBuffer</code> is in a destroyed state,
+ * false otherwise.
+ *
+ * @see #destroy()
+ */
+ public boolean isDestroyed() {
+ return mNativeObject == 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * <p>Calling this method will throw an <code>IllegalStateException</code> if
+ * {@link #destroy()} has been previously called.</p>
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and cannot be "
+ + "written to a parcel.");
+ }
+ nWriteHardwareBufferToParcel(mNativeObject, dest);
+ }
+
+ public static final Parcelable.Creator<HardwareBuffer> CREATOR =
+ new Parcelable.Creator<HardwareBuffer>() {
+ public HardwareBuffer createFromParcel(Parcel in) {
+ long nativeObject = nReadHardwareBufferFromParcel(in);
+ if (nativeObject != 0) {
+ return new HardwareBuffer(nativeObject);
+ }
+ return null;
+ }
+
+ public HardwareBuffer[] newArray(int size) {
+ return new HardwareBuffer[size];
+ }
+ };
+
+ /**
+ * Validates whether a particular format is supported by HardwareBuffer.
+ *
+ * @param format The format to validate.
+ *
+ * @return True if <code>format</code> is a supported format. false otherwise.
+ * See {@link #create(int, int, int, int, int, long, long)}.a
+ */
+ private static boolean isSupportedFormat(@Format int format) {
+ switch(format) {
+ case RGBA_8888:
+ case RGBA_FP16:
+ case RGBX_8888:
+ case RGB_565:
+ case RGB_888:
+ return true;
+ }
+ return false;
+ }
+
+ private static native long nCreateHardwareBuffer(int width, int height, int format, int layers,
+ long usage);
+ private static native long nGetNativeFinalizer();
+ private static native void nWriteHardwareBufferToParcel(long nativeObject, Parcel dest);
+ private static native long nReadHardwareBufferFromParcel(Parcel in);
+ @FastNative
+ private static native int nGetWidth(long nativeObject);
+ @FastNative
+ private static native int nGetHeight(long nativeObject);
+ @FastNative
+ private static native int nGetFormat(long nativeObject);
+ @FastNative
+ private static native int nGetLayers(long nativeObject);
+ @FastNative
+ private static native long nGetUsage(long nativeObject);
+}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 06ab13e..d87c55e 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -729,14 +729,22 @@
private static final int DATA_INJECTION_MASK = 0x10;
private static final int DATA_INJECTION_SHIFT = 4;
- // MASK for dynamic sensor (sensor that added during runtime), bit 6.
+ // MASK for dynamic sensor (sensor that added during runtime), bit 5.
private static final int DYNAMIC_SENSOR_MASK = 0x20;
private static final int DYNAMIC_SENSOR_SHIFT = 5;
- // MASK for indication bit of sensor additional information support (bit 7).
+ // MASK for indication bit of sensor additional information support, bit 6.
private static final int ADDITIONAL_INFO_MASK = 0x40;
private static final int ADDITIONAL_INFO_SHIFT = 6;
+ // Mask for direct mode highest rate level, bit 7, 8, 9.
+ private static final int DIRECT_REPORT_MASK = 0x380;
+ private static final int DIRECT_REPORT_SHIFT = 7;
+
+ // Mask for supported direct channel, bit 10, 11
+ private static final int DIRECT_CHANNEL_MASK = 0xC00;
+ private static final int DIRECT_CHANNEL_SHIFT = 10;
+
// TODO(): The following arrays are fragile and error-prone. This needs to be refactored.
// Note: This needs to be updated, whenever a new sensor is added.
@@ -796,6 +804,42 @@
return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT);
}
+ /**
+ * Get the highest supported direct report mode rate level of the sensor.
+ *
+ * @return Highest direct report rate level of this sensor. If the sensor does not support
+ * direct report mode, this returns {@link SensorDirectChannel#RATE_STOP}.
+ * @see SensorDirectChannel#RATE_STOP
+ * @see SensorDirectChannel#RATE_NORMAL
+ * @see SensorDirectChannel#RATE_FAST
+ * @see SensorDirectChannel#RATE_VERY_FAST
+ */
+ @SensorDirectChannel.RateLevel
+ public int getHighestDirectReportRateLevel() {
+ int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT);
+ return rateLevel <= SensorDirectChannel.RATE_VERY_FAST
+ ? rateLevel : SensorDirectChannel.RATE_VERY_FAST;
+ }
+
+ /**
+ * Test if sensor support direct channel backed by a specific type of shared memory.
+ *
+ * @param sharedMemType type of shared memory used by direct channel.
+ * @return <code>true</code> if the shared memory type is supported.
+ * @see SensorDirectChannel#TYPE_ASHMEM
+ * @see SensorDirectChannel#TYPE_HARDWARE_BUFFER
+ */
+ public boolean isDirectChannelTypeSupported(@SensorDirectChannel.MemoryType int sharedMemType) {
+ switch (sharedMemType) {
+ case SensorDirectChannel.TYPE_ASHMEM:
+ return (mFlags & (1 << DIRECT_CHANNEL_SHIFT)) > 0;
+ case SensorDirectChannel.TYPE_HARDWARE_BUFFER:
+ return (mFlags & (1 << DIRECT_CHANNEL_SHIFT + 1)) > 0;
+ default:
+ return false;
+ }
+ }
+
static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) {
// RotationVector length has changed to 3 to 5 for API level 18
// Set it to 3 for backward compatibility.
diff --git a/core/java/android/hardware/SensorDirectChannel.java b/core/java/android/hardware/SensorDirectChannel.java
new file mode 100644
index 0000000..0efd62b
--- /dev/null
+++ b/core/java/android/hardware/SensorDirectChannel.java
@@ -0,0 +1,168 @@
+/*
+ * 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.hardware;
+
+import android.annotation.IntDef;
+import android.os.MemoryFile;
+
+import dalvik.system.CloseGuard;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Class representing a sensor direct channel. Use {@link
+ * SensorManager#createDirectChannel(android.os.MemoryFile)} to obtain object.
+ */
+public final class SensorDirectChannel implements AutoCloseable {
+
+ // shared memory types
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {TYPE_ASHMEM, TYPE_HARDWARE_BUFFER})
+ public @interface MemoryType {};
+ /**
+ * Shared memory type ashmem, wrapped in MemoryFile object.
+ *
+ * @see SensorManager#createDirectChannel(MemoryFile)
+ */
+ public static final int TYPE_ASHMEM = 1;
+
+ /**
+ * Shared memory type wrapped by HardwareBuffer object.
+ *
+ * @see SensorManager#createDirectChannel(HardwareBuffer)
+ */
+ public static final int TYPE_HARDWARE_BUFFER = 2;
+
+ // sensor rate levels
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {RATE_STOP, RATE_NORMAL, RATE_FAST, RATE_VERY_FAST})
+ public @interface RateLevel {};
+
+ /**
+ * Sensor stopped (no event output).
+ *
+ * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public static final int RATE_STOP = 0;
+ /**
+ * Sensor operates at nominal rate of 50Hz.
+ *
+ * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 27.5Hz to
+ * 110Hz.
+ *
+ * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public static final int RATE_NORMAL = 1; //50Hz
+ /**
+ * Sensor operates at nominal rate of 200Hz.
+ *
+ * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 110Hz to
+ * 440Hz.
+ *
+ * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public static final int RATE_FAST = 2; // ~200Hz
+ /**
+ * Sensor operates at nominal rate of 800Hz.
+ *
+ * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 440Hz to
+ * 1760Hz.
+ *
+ * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public static final int RATE_VERY_FAST = 3; // ~800Hz
+
+ /**
+ * Determine if a channel is still valid. A channel is invalidated after {@link #close()} is
+ * called.
+ *
+ * @return <code>true</code> if channel is valid.
+ */
+ public boolean isValid() {
+ return !mClosed.get();
+ }
+
+ /**
+ * Close sensor direct channel.
+ *
+ * Stop all active sensor in the channel and free sensor system resource related to channel.
+ * Shared memory used for creating the direct channel need to be closed or freed separately.
+ *
+ * @see SensorManager#createDirectChannel(MemoryFile)
+ * @see SensorManager#createDirectChannel(HardwareBuffer)
+ */
+ @Override
+ public void close() {
+ mCloseGuard.close();
+ if (mClosed.compareAndSet(false, true)) {
+ // actual close action
+ mManager.destroyDirectChannel(this);
+ }
+ }
+
+ /** @hide */
+ SensorDirectChannel(SensorManager manager, int id, int type, long size) {
+ mManager = manager;
+ mNativeHandle = id;
+ mType = type;
+ mSize = size;
+ mCloseGuard.open("SensorDirectChannel");
+ }
+
+ /** @hide */
+ int getNativeHandle() {
+ return mNativeHandle;
+ }
+
+ /**
+ * This function encode handle information in {@link android.os.Memory} into a long array to be
+ * passed down to native methods.
+ *
+ * @hide */
+ static long[] encodeData(MemoryFile ashmem) {
+ int fd;
+ try {
+ fd = ashmem.getFileDescriptor().getInt$();
+ } catch (IOException e) {
+ fd = -1;
+ }
+ return new long[] { 1 /*numFds*/, 0 /*numInts*/, fd };
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mCloseGuard.warnIfOpen();
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private final AtomicBoolean mClosed = new AtomicBoolean();
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final SensorManager mManager;
+ private final int mNativeHandle;
+ private final long mSize;
+ private final int mType;
+}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 3ccac69..cfda2f4 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -19,6 +19,7 @@
import android.annotation.SystemApi;
import android.os.Build;
import android.os.Handler;
+import android.os.MemoryFile;
import android.util.Log;
import android.util.SparseArray;
@@ -881,6 +882,104 @@
/**
+ * Create a sensor direct channel backed by shared memory wrapped by MemoryFile object.
+ *
+ * Use the returned {@link android.hardware.SensorDirectChannel} object to configure direct
+ * report of sensor events. After use, call {@link android.hardware.SensorDirectChannel#close()}
+ * to free up resource in sensor system associated with the direct channel.
+ *
+ * @param mem A {@link android.os.MemoryFile} shared memory object.
+ * @return A {@link android.hardware.SensorDirectChannel} object if successful, null otherwise.
+ * @throws IllegalArgumentException when mem is null.
+ * @see SensorDirectChannel#close()
+ * @see #configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public SensorDirectChannel createDirectChannel(MemoryFile mem) {
+ return createDirectChannelImpl(mem.length(), mem, null);
+ }
+
+ /**
+ * Create a sensor direct channel backed by shared memory wrapped by HardwareBuffer object.
+ *
+ * Use the returned {@link android.hardware.SensorDirectChannel} object to configure direct
+ * report of sensor events. After use, call {@link android.hardware.SensorDirectChannel#close()}
+ * to free up resource in sensor system associated with the direct channel.
+ *
+ * @param mem A {@link android.hardware.HardwareBuffer} shared memory object.
+ * @return A {@link android.hardware.SensorDirectChannel} object if successful,
+ * null otherwise.
+ * @throws IllegalArgumentException when mem is null.
+ * @see SensorDirectChannel#close()
+ * @see #configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public SensorDirectChannel createDirectChannel(HardwareBuffer mem) {
+ return null;
+ }
+
+ /** @hide */
+ protected abstract SensorDirectChannel createDirectChannelImpl(long size,
+ MemoryFile ashmemFile, HardwareBuffer hardwareBuffer);
+
+ /** @hide */
+ void destroyDirectChannel(SensorDirectChannel channel) {
+ destroyDirectChannelImpl(channel);
+ }
+
+ /** @hide */
+ protected abstract void destroyDirectChannelImpl(SensorDirectChannel channel);
+
+ /**
+ * Configure sensor rate or stop sensor report on a direct report channel specified.
+ *
+ * To start event report of a sensor, or change rate of existing report, call this function with
+ * rateLevel other than {@link android.hardware.SensorDirectChannel#RATE_STOP}. Sensor events
+ * will be added into a queue formed by the shared memory used in creation of direction channel.
+ * Each element of the queue has size of 104 bytes and represents a sensor event. Data
+ * structure of an element (all fields in little-endian):
+ *
+ * offset type name
+ *- ---------------------------------------------
+ * 0x0000 int32_t size (always 104)
+ * 0x0004 int32_t sensor report token
+ * 0x0008 int32_t type (see SensorType)
+ * 0x000C uint32_t atomic counter
+ * 0x0010 int64_t timestamp (see Event)
+ * 0x0018 float[16]/int64_t[8] data (data type depends on sensor type)
+ * 0x0058 int32_t[4] reserved (set to zero)
+ *
+ * There is no head or tail pointers. The sequence and frontier of new sensor events is
+ * determined by the atomic counter, which counts from 1 after creation of direct channel and
+ * increments 1 for each new event. The writer in sensor system will wrap around from to
+ * start of shared memory region when it reaches the end. If size of memory region is not
+ * a multiple of size of element (104 bytes), the residual is not used at the end.
+ * Function returns a positive sensor report token on success. This token can be used for
+ * differentiate sensor events from multiple sensor of the same type. For example, if there are
+ * two accelerometer in the system A and B, it is guaranteed different report tokens will be
+ * returned when starting sensor A and B.
+ *
+ * To stop a sensor, call this function with rateLevel equal {@link
+ * android.hardware.SensorDirectChannel#RATE_STOP}. If the sensor parameter is left to be null,
+ * this will stop all active sensor report associated with the direct channel specified.
+ * Function return 1 on success or 0 on failure.
+ *
+ * @param channel A {@link android.hardware.SensorDirectChannel} object representing direct
+ * channel to be configured.
+ * @param sensor A {@link android.hardware.Sensor} object to denote sensor to be operated.
+ * @param rateLevel rate level defined in {@link android.hardware.SensorDirectChannel}.
+ * @return starting report or changing rate: positive sensor report token on success, 0 on failure;
+ * stopping report: 1 on success, 0 on failure.
+ * @throws IllegalArgumentException when SensorDirectChannel is null.
+ */
+ public int configureDirectChannel(SensorDirectChannel channel, Sensor sensor,
+ @SensorDirectChannel.RateLevel int rateLevel) {
+ return configureDirectChannelImpl(channel, sensor, rateLevel);
+ }
+
+ /** @hide */
+ protected abstract int configureDirectChannelImpl(
+ SensorDirectChannel channel, Sensor s, int rate);
+
+ /**
* Used for receiving notifications from the SensorManager when dynamic sensors are connected or
* disconnected.
*/
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 259ca03..4992def 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
+import android.os.MemoryFile;
import android.os.MessageQueue;
import android.util.Log;
import android.util.SparseArray;
@@ -57,6 +58,13 @@
private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
+ private static native int nativeCreateDirectChannel(
+ long nativeInstance, long size, int channelType, long [] channelData);
+ private static native void nativeDestroyDirectChannel(
+ long nativeInstance, int channelHandle);
+ private static native int nativeConfigDirectChannel(
+ long nativeInstance, int channelHandle, int sensorHandle, int rate);
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static boolean sNativeClassInited = false;
@@ -484,6 +492,71 @@
return changed;
}
+ /** @hide */
+ protected int configureDirectChannelImpl(
+ SensorDirectChannel channel, Sensor sensor, int rate) {
+ if (channel == null) throw new IllegalArgumentException("channel cannot be null");
+
+ if (!channel.isValid()) {
+ throw new IllegalStateException("channel is invalid");
+ }
+
+ if (rate < SensorDirectChannel.RATE_STOP
+ || rate > SensorDirectChannel.RATE_VERY_FAST) {
+ throw new IllegalArgumentException("rate parameter invalid");
+ }
+
+ if (sensor == null && rate != SensorDirectChannel.RATE_STOP) {
+ // the stop all sensors case
+ throw new IllegalArgumentException(
+ "when sensor is null, rate can only be DIRECT_RATE_STOP");
+ }
+
+ int sensorHandle = (sensor == null) ? -1 : sensor.getHandle();
+
+ int ret = nativeConfigDirectChannel(
+ mNativeInstance, channel.getNativeHandle(), sensorHandle, rate);
+
+ if (rate == SensorDirectChannel.RATE_STOP) {
+ return (ret == 0) ? 1 : 0;
+ } else {
+ return (ret > 0) ? ret : 0;
+ }
+ }
+
+ /** @hide */
+ protected SensorDirectChannel createDirectChannelImpl(long size,
+ MemoryFile ashmemFile, HardwareBuffer grallocMemObject) {
+ SensorDirectChannel ch = null;
+
+ if (size <= 0) throw new IllegalArgumentException("size has to be greater than 0");
+
+ if (ashmemFile != null) {
+ if (size != ashmemFile.length()) {
+ throw new IllegalArgumentException("size has to match MemoryFile.length()");
+ }
+ int id = nativeCreateDirectChannel(
+ mNativeInstance, size, SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.encodeData(ashmemFile));
+ if (id > 0) {
+ ch = new SensorDirectChannel(this, id, SensorDirectChannel.TYPE_ASHMEM, size);
+ }
+ } else if (grallocMemObject != null) {
+ Log.wtf(TAG, "Implement GRALLOC or remove GRALLOC support entirely");
+ } else {
+ throw new IllegalArgumentException("Invalid parameter");
+ }
+
+ return ch;
+ }
+
+ /** @hide */
+ protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
+ if (channel != null) {
+ nativeDestroyDirectChannel(mNativeInstance, channel.getNativeHandle());
+ }
+ }
+
/*
* BaseEventQueue is the communication channel with the sensor service,
* SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 62b7f32..bcebb7d 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -221,8 +221,8 @@
public abstract void tearDown(@NonNull Surface surface) throws CameraAccessException;
/**
- * <p>Finish the deferred output configurations where the output Surface was not configured
- * before.</p>
+ * <p>Finalize the output configurations that now have their deferred and/or extra Surfaces
+ * included.</p>
*
* <p>For camera use cases where a preview and other output configurations need to be
* configured, it can take some time for the preview Surface to be ready. For example, if the
@@ -235,22 +235,31 @@
* and defer the preview output configuration until the Surface is ready. After the
* {@link CameraCaptureSession} is created successfully with this deferred output and other
* normal outputs, the application can start submitting requests as long as they do not include
- * deferred output Surfaces. Once a deferred Surface is ready, the application can set the
- * Surface on the deferred output configuration with the
- * {@link OutputConfiguration#setDeferredSurface} method, and then finish the deferred output
+ * deferred output Surfaces. Once a deferred Surface is ready, the application can add the
+ * Surface to the deferred output configuration with the
+ * {@link OutputConfiguration#addSurface} method, and then update the deferred output
* configuration via this method, before it can submit capture requests with this output
* target.</p>
*
- * <p>The output Surfaces included by this list of deferred
+ * <p>This function can also be called in case where multiple surfaces share the same
+ * OutputConfiguration, and one of the surfaces becomes available after the {@link
+ * CameraCaptureSession} is created. In that case, the application must first create the
+ * OutputConfiguration with the available Surface, then enable furture surface sharing via
+ * {@link OutputConfiguration#enableSurfaceSharing}, before creating the CameraCaptureSession.
+ * After the CameraCaptureSession is created, and once the extra Surface becomes available, the
+ * application must then call {@link OutputConfiguration#addSurface} before finalizing the
+ * configuration with this method.</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>
*
* <p>This method is not supported by
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}-level devices.</p>
*
- * @param deferredOutputConfigs a list of {@link OutputConfiguration OutputConfigurations} that
- * have had {@link OutputConfiguration#setDeferredSurface setDeferredSurface} invoked
- * with a valid output Surface.
+ * @param outputConfigs a list of {@link OutputConfiguration OutputConfigurations} that
+ * have had {@link OutputConfiguration#addSurface addSurface} invoked with a valid
+ * output Surface after {@link CameraDevice#createCaptureSessionByOutputConfigurations}.
* @throws CameraAccessException if the camera device is no longer connected or has encountered
* a fatal error.
* @throws IllegalStateException if this session is no longer active, either because the session
@@ -261,8 +270,8 @@
* source. Or if one of the output configuration was already finished with an
* included surface in a prior call.
*/
- public abstract void finishDeferredConfiguration(
- List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException;
+ public abstract void finalizeOutputConfigurations(
+ List<OutputConfiguration> outputConfigs) throws CameraAccessException;
/**
* <p>Submit a request for an image to be captured by the camera device.</p>
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 4befb29..891df63 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -151,9 +151,9 @@
}
@Override
- public void finishDeferredConfiguration(
- List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException {
- mDeviceImpl.finishDeferredConfig(deferredOutputConfigs);
+ public void finalizeOutputConfigurations(
+ List<OutputConfiguration> outputConfigs) throws CameraAccessException {
+ mDeviceImpl.finalizeOutputConfigs(outputConfigs);
}
@Override
diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index 01e58f4..15dbf26 100644
--- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -258,9 +258,9 @@
}
@Override
- public void finishDeferredConfiguration(List<OutputConfiguration> deferredOutputConfigs)
+ public void finalizeOutputConfigurations(List<OutputConfiguration> deferredOutputConfigs)
throws CameraAccessException {
- mSessionImpl.finishDeferredConfiguration(deferredOutputConfigs);
+ mSessionImpl.finalizeOutputConfigurations(deferredOutputConfigs);
}
private class WrapperCallback extends StateCallback {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index d2aeaea..2364ebe 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -743,14 +743,14 @@
}
}
- public void finishDeferredConfig(List<OutputConfiguration> deferredConfigs)
+ public void finalizeOutputConfigs(List<OutputConfiguration> outputConfigs)
throws CameraAccessException {
- if (deferredConfigs == null || deferredConfigs.size() == 0) {
+ if (outputConfigs == null || outputConfigs.size() == 0) {
throw new IllegalArgumentException("deferred config is null or empty");
}
synchronized(mInterfaceLock) {
- for (OutputConfiguration config : deferredConfigs) {
+ for (OutputConfiguration config : outputConfigs) {
int streamId = -1;
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
// Have to use equal here, as createCaptureSessionByOutputConfigurations() and
@@ -765,11 +765,11 @@
+ "session");
}
- if (config.getSurface() == null) {
- throw new IllegalArgumentException("The deferred config for stream " + streamId
- + " must have a non-null surface");
+ if (config.getSurfaces().size() == 0) {
+ throw new IllegalArgumentException("The final config for stream " + streamId
+ + " must have at least 1 surface");
}
- mRemoteDevice.setDeferredConfiguration(streamId, config);
+ mRemoteDevice.finalizeOutputConfigurations(streamId, config);
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index d77f60b..d9f666e 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -215,10 +215,10 @@
}
}
- public void setDeferredConfiguration(int streamId, OutputConfiguration deferredConfig)
+ public void finalizeOutputConfigurations(int streamId, OutputConfiguration deferredConfig)
throws CameraAccessException {
try {
- mRemoteDevice.setDeferredConfiguration(streamId, deferredConfig);
+ mRemoteDevice.finalizeOutputConfigurations(streamId, deferredConfig);
} catch (Throwable t) {
CameraManager.throwAsPublicException(t);
throw new UnsupportedOperationException("Unexpected exception", t);
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 2a9bf6b..d8ec4df 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -578,8 +578,8 @@
}
@Override
- public void setDeferredConfiguration(int steamId, OutputConfiguration config) {
- String err = "Set deferred configuration is not supported on legacy devices";
+ public void finalizeOutputConfigurations(int steamId, OutputConfiguration config) {
+ String err = "Finalizing output configuration is not supported on legacy devices";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 4654fc2..2d5c4ce 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -34,6 +34,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Collections;
+import java.util.ArrayList;
import static com.android.internal.util.Preconditions.*;
@@ -116,6 +117,15 @@
private final int SURFACE_TYPE_SURFACE_TEXTURE = 1;
/**
+ * Maximum number of surfaces supported by one {@link OutputConfiguration}.
+ *
+ * <p>The combined number of surfaces added by the constructor and
+ * {@link OutputConfiguration#addSurface} should not exceed this value.</p>
+ *
+ */
+ private static final int MAX_SURFACES_COUNT = 2;
+
+ /**
* Create a new {@link OutputConfiguration} instance with a {@link Surface},
* with a surface group ID.
*
@@ -151,50 +161,6 @@
}
/**
- * Create a new {@link OutputConfiguration} instance with two surfaces sharing the same stream,
- * with a surface group ID.
- *
- * <p>For advanced use cases, a camera application may require more streams than the combination
- * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, two compatible
- * surfaces can be attached to one OutputConfiguration so that they map to one camera stream,
- * and buffers are reference counted when being consumed by both surfaces. </p>
- *
- * <p>Two surfaces are compatible in below 2 cases:</p>
- *
- * <ol>
- * <li> Surfaces with the same size, format, dataSpace, and Surface source class. In this case,
- * {@link CameraDevice#createCaptureSessionByOutputConfigurations} is guaranteed to succeed.
- *
- * <li> Surfaces with the same size, format, and dataSpace, but different Surface
- * source classes. However, on some devices, the underlying camera device is able to use the
- * same buffer layout for both surfaces. The only way to discover if this is the case is to
- * create a capture session with that output configuration. For example, if the camera device
- * uses the same private buffer format between a SurfaceView/SurfaceTexture and a
- * MediaRecorder/MediaCodec, {@link CameraDevice#createCaptureSessionByOutputConfigurations}
- * will succeed. Otherwise, it throws {@code IllegalArgumentException}.
- * </ol>
- *
- * @param surfaceGroupId
- * A group ID for this output, used for sharing memory between multiple outputs.
- * @param surface
- * A Surface for camera to output to.
- * @param surface2
- * Second surface for camera to output to.
- * @throws IllegalArgumentException if the two surfaces have different size, format, or
- * dataSpace.
- *
- * @hide
- */
- public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface,
- @NonNull Surface surface2) {
- this(surfaceGroupId, surface, ROTATION_0, surface2);
-
- checkNotNull(surface2, "Surface must not be null");
- checkMatchingSurfaces(mConfiguredSize, mConfiguredFormat, mConfiguredDataspace,
- mConfiguredGenerationId, surface2);
- }
-
- /**
* Create a new {@link OutputConfiguration} instance.
*
* <p>This constructor takes an argument for desired camera rotation</p>
@@ -240,68 +206,19 @@
*/
@SystemApi
public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation) {
- this(surfaceGroupId, surface, rotation, null /*surface2*/);
- }
-
- /**
- * Create a new {@link OutputConfiguration} instance, with rotation, a group ID, and a secondary
- * surface.
- *
- * <p>This constructor takes an argument for desired camera rotation, the surface group
- * ID, and a secondary surface. See {@link #OutputConfiguration(int, Surface)} for details
- * of the group ID.</p>
- *
- * <p>surface2 should be compatible with surface. See {@link #OutputConfiguration(int, Surface,
- * Surface} for details of compatibility between surfaces.</p>
- *
- * <p>Since the rotation is done by the CameraDevice, both surfaces will receive buffers with
- * the same rotation applied. This means that if the application needs two compatible surfaces
- * to have different rotations, these surfaces cannot be shared within one OutputConfiguration.
- * </p>
- *
- * @param surfaceGroupId
- * A group ID for this output, used for sharing memory between multiple outputs.
- * @param surface
- * A Surface for camera to output to.
- * @param rotation
- * The desired rotation to be applied on camera output. Value must be one of
- * ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees,
- * application should make sure corresponding surface size has width and height
- * transposed relative to the width and height without rotation. For example,
- * if application needs camera to capture 1280x720 picture and rotate it by 90 degree,
- * application should set rotation to {@code ROTATION_90} and make sure the
- * corresponding Surface size is 720x1280. Note that {@link CameraDevice} might
- * throw {@code IllegalArgumentException} if device cannot perform such rotation.
- * @param surface2
- * Second surface for camera to output to.
-
- * @throws IllegalArgumentException if the two surfaces are not compatible to be shared in
- * one OutputConfiguration.
- *
- * @hide
- */
- private OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation,
- @Nullable Surface surface2) {
checkNotNull(surface, "Surface must not be null");
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
-
mSurfaceGroupId = surfaceGroupId;
mSurfaceType = SURFACE_TYPE_UNKNOWN;
+ mSurfaces = new ArrayList<Surface>();
+ mSurfaces.add(surface);
mRotation = rotation;
mConfiguredSize = SurfaceUtils.getSurfaceSize(surface);
mConfiguredFormat = SurfaceUtils.getSurfaceFormat(surface);
mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(surface);
mConfiguredGenerationId = surface.getGenerationId();
mIsDeferredConfig = false;
-
- if (surface2 == null) {
- mSurfaces = new Surface[1];
- mSurfaces[0] = surface;
- } else {
- mSurfaces = new Surface[MAX_SURFACES_COUNT];
- mSurfaces[0] = surface;
- mSurfaces[1] = surface2;
- }
+ mIsShared = false;
}
/**
@@ -309,16 +226,16 @@
* source class.
* <p>
* This constructor takes an argument for desired Surface size and the Surface source class
- * without providing the actual output Surface. This is used to setup a output configuration
+ * without providing the actual output Surface. This is used to setup an output configuration
* with a deferred Surface. The application can use this output configuration to create a
* session.
* </p>
* <p>
- * However, the actual output Surface must be set via {@link #setDeferredSurface} and finish the
- * deferred Surface configuration via {@link CameraCaptureSession#finishDeferredConfiguration}
- * before submitting a request with this Surface target. The deferred Surface can only be
- * obtained from either from {@link android.view.SurfaceView} by calling
- * {@link android.view.SurfaceHolder#getSurface}, or from
+ * However, the actual output Surface must be set via {@link #addSurface} and the deferred
+ * Surface configuration must be finalized via {@link
+ * CameraCaptureSession#finalizeOutputConfigurations} before submitting a request with this
+ * Surface target. The deferred Surface can only be obtained either from {@link
+ * android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, or from
* {@link android.graphics.SurfaceTexture} via
* {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).
* </p>
@@ -329,41 +246,68 @@
* {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
*/
public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) {
- this(surfaceSize, klass, true /* dummy */);
+ checkNotNull(klass, "surfaceSize must not be null");
+ checkNotNull(klass, "klass must not be null");
+ if (klass == android.view.SurfaceHolder.class) {
+ mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
+ } else if (klass == android.graphics.SurfaceTexture.class) {
+ mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
+ } else {
+ mSurfaceType = SURFACE_TYPE_UNKNOWN;
+ throw new IllegalArgumentException("Unknow surface source class type");
+ }
- mSurfaces = new Surface[1];
+ mSurfaceGroupId = SURFACE_GROUP_ID_NONE;
+ mSurfaces = new ArrayList<Surface>();
+ mRotation = ROTATION_0;
+ mConfiguredSize = surfaceSize;
+ mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
+ mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+ mConfiguredGenerationId = 0;
+ mIsDeferredConfig = true;
+ mIsShared = false;
}
/**
- * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface
- * source class for the deferred surface, and a secondary surface.
+ * Enable multiple surfaces sharing the same OutputConfiguration
*
- * <p>This constructor takes an argument for desired surface size and surface source class of
- * the deferred surface, and a secondary surface. See {@link #OutputConfiguration(Size, Class)}
- * for details of the surface size and surface source class.</p>
+ * <p>For advanced use cases, a camera application may require more streams than the combination
+ * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, more than one
+ * compatible surface can be attached to an OutputConfiguration so that they map to one
+ * camera stream, and the outputs share memory buffers when possible. </p>
*
- * <p> The deferred surface and secondary surface should be compatible. See
- * {@link #OutputConfiguration(int, Surface, Surface)} for details of compatible surfaces.
+ * <p>Two surfaces are compatible in the below cases:</p>
*
- * @hide
+ * <li> Surfaces with the same size, format, dataSpace, and Surface source class. In this case,
+ * {@link CameraDevice#createCaptureSessionByOutputConfigurations} is guaranteed to succeed.
+ *
+ * <li> Surfaces with the same size, format, and dataSpace, but different Surface source classes
+ * that are generally not compatible. However, on some devices, the underlying camera device is
+ * able to use the same buffer layout for both surfaces. The only way to discover if this is the
+ * case is to create a capture session with that output configuration. For example, if the
+ * camera device uses the same private buffer format between a SurfaceView/SurfaceTexture and a
+ * MediaRecorder/MediaCodec, {@link CameraDevice#createCaptureSessionByOutputConfigurations}
+ * will succeed. Otherwise, it throws {@code IllegalArgumentException}.
+ * </ol>
+ *
+ * <p>To enable surface sharing, this function must be called before {@link
+ * CameraDevice#createCaptureSessionByOutputConfigurations}. Calling this function after {@link
+ * CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.</p>
+ *
+ * <p>Up to 2 surfaces can be shared for an OutputConfiguration. The supported surfaces for
+ * sharing must be of type SurfaceTexture, SurfaceView, MediaRecorder, MediaCodec, or
+ * implementation defined ImageReader.</p>
*/
- public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass,
- @NonNull Surface surface2) {
- this(surfaceSize, klass, true /* dummy */);
-
- checkMatchingSurfaces(mConfiguredSize, mConfiguredFormat, mConfiguredDataspace,
- mConfiguredGenerationId, surface2);
-
- mSurfaces = new Surface[MAX_SURFACES_COUNT];
- mSurfaces[0] = null;
- mSurfaces[1] = surface2;
+ public void enableSurfaceSharing() {
+ mIsShared = true;
}
/**
* Check if this configuration has deferred configuration.
*
- * <p>This will return true if the output configuration was constructed with surface deferred.
- * It will return true even after the deferred surface is set later.</p>
+ * <p>This will return true if the output configuration was constructed with surface deferred by
+ * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. It will return true even after
+ * the deferred surface is added later by {@link OutputConfiguration#addSurface}.</p>
*
* @return true if this configuration has deferred surface.
* @hide
@@ -373,38 +317,58 @@
}
/**
- * Set the deferred surface to this OutputConfiguration.
+ * Add a surface to this OutputConfiguration.
*
- * <p>
- * The deferred surface must be obtained from either from {@link android.view.SurfaceView} by
- * calling {@link android.view.SurfaceHolder#getSurface}, or from
- * {@link android.graphics.SurfaceTexture} via
- * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). After the deferred
- * surface is set, the application must finish the deferred surface configuration via
- * {@link CameraCaptureSession#finishDeferredConfiguration} before submitting a request with
- * this surface target.
+ * <p> This function can be called before or after {@link
+ * CameraDevice#createCaptureSessionByOutputConfigurations}. If it's called after,
+ * the application must finalize the capture session with
+ * {@link CameraCaptureSession#finalizeOutputConfigurations}.
* </p>
*
- * @param surface The deferred surface to be set.
- * @throws IllegalArgumentException if the Surface is invalid.
- * @throws IllegalStateException if a Surface was already set to this deferred
- * OutputConfiguration.
+ * <p> If the OutputConfiguration was constructed with a deferred surface by {@link
+ * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained
+ * from {@link android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface},
+ * or from {@link android.graphics.SurfaceTexture} via
+ * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).</p>
+ *
+ * <p> If the OutputConfiguration was constructed by other constructors, the added
+ * surface must be compatible with the existing surface. See {@link #enableSurfaceSharing} for
+ * details of compatible surfaces.</p>
+ *
+ * <p> If the OutputConfiguration already contains a Surface, {@link #enableSurfaceSharing} must
+ * be called before calling this function to add a new Surface.</p>
+ *
+ * @param surface The surface to be added.
+ * @throws IllegalArgumentException if the Surface is invalid, the Surface's
+ * size/dataspace/format doesn't match, or adding the Surface would exceed number of
+ * shared surfaces supported.
+ * @throws IllegalStateException if the Surface was already added to this OutputConfiguration,
+ * or if the OutputConfiguration is not shared and it already has a surface associated
+ * with it.
*/
- public void setDeferredSurface(@NonNull Surface surface) {
+ public void addSurface(@NonNull Surface surface) {
checkNotNull(surface, "Surface must not be null");
- if (mSurfaces[0] != null) {
- throw new IllegalStateException("Deferred surface is already set!");
+ if (mSurfaces.contains(surface)) {
+ throw new IllegalStateException("Surface is already added!");
+ }
+ if (mSurfaces.size() == 1 && !mIsShared) {
+ throw new IllegalStateException("Cannot have 2 surfaces for a non-sharing configuration");
+ }
+ if (mSurfaces.size() + 1 > MAX_SURFACES_COUNT) {
+ throw new IllegalArgumentException("Exceeds maximum number of surfaces");
}
- // This will throw IAE is the surface was abandoned.
- Size surfaceSize = SurfaceUtils.getSurfaceSize(surface);
- if (!surfaceSize.equals(mConfiguredSize)) {
- Log.w(TAG, "Deferred surface size " + surfaceSize +
- " is different with pre-configured size " + mConfiguredSize +
- ", the pre-configured size will be used.");
+ if (!mConfiguredSize.equals(SurfaceUtils.getSurfaceSize(surface))) {
+ throw new IllegalArgumentException("The size of added surface doesn't match");
+ }
+ if (mConfiguredDataspace != SurfaceUtils.getSurfaceDataspace(surface)) {
+ throw new IllegalArgumentException("The dataspace of added surface doesn't match");
+ }
+ if (mConfiguredFormat != SurfaceUtils.getSurfaceFormat(surface)) {
+ throw new IllegalArgumentException("The format of added surface format doesn't match");
}
- mSurfaces[0] = surface;
+ mSurfaces.add(surface);
}
/**
@@ -432,49 +396,6 @@
}
/**
- * Private constructor to initialize Configuration based on surface size and class
- */
- private <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass,
- boolean dummy) {
- checkNotNull(surfaceSize, "surfaceSize must not be null");
- checkNotNull(klass, "klass must not be null");
- if (klass == android.view.SurfaceHolder.class) {
- mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
- } else if (klass == android.graphics.SurfaceTexture.class) {
- mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
- } else {
- mSurfaceType = SURFACE_TYPE_UNKNOWN;
- throw new IllegalArgumentException("Unknow surface source class type");
- }
-
- mSurfaceGroupId = SURFACE_GROUP_ID_NONE;
- mRotation = ROTATION_0;
- mConfiguredSize = surfaceSize;
- mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
- mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
- mConfiguredGenerationId = 0;
- mIsDeferredConfig = true;
- }
-
- /**
- * Check if the surface properties match that of the given surface.
- *
- * @return true if the properties and the surface match.
- */
- private void checkMatchingSurfaces(Size size, int format, int dataSpace, int generationId,
- @NonNull Surface surface) {
- if (!size.equals(SurfaceUtils.getSurfaceSize(surface))) {
- throw new IllegalArgumentException("Secondary surface size doesn't match");
- }
- if (dataSpace != SurfaceUtils.getSurfaceDataspace(surface)) {
- throw new IllegalArgumentException("Secondary surface dataspace doesn't match");
- }
- if (format != SurfaceUtils.getSurfaceFormat(surface)) {
- throw new IllegalArgumentException("Secondary surface format doesn't match");
- }
- }
-
- /**
* Create an OutputConfiguration from Parcel.
*/
private OutputConfiguration(@NonNull Parcel source) {
@@ -483,27 +404,9 @@
int surfaceType = source.readInt();
int width = source.readInt();
int height = source.readInt();
- int surfaceCnt = source.readInt();
-
- if (surfaceCnt <= 0) {
- throw new IllegalArgumentException(
- "Surface count in OutputConfiguration must be greater than 0");
- }
- if (surfaceCnt > MAX_SURFACES_COUNT) {
- throw new IllegalArgumentException(
- "Surface count in OutputConfiguration must not be more than "
- + MAX_SURFACES_COUNT);
- }
-
- Surface[] surfaces = new Surface[surfaceCnt];
- for (int i = 0; i < surfaceCnt; i++) {
- Surface surface = Surface.CREATOR.createFromParcel(source);
- surfaces[i] = surface;
-
- if (surface == null && i > 0) {
- throw new IllegalArgumentException("Only the first surface can be deferred");
- }
- }
+ boolean isDeferred = source.readInt() == 1;
+ ArrayList<Surface> surfaces = new ArrayList<Surface>();
+ source.readTypedList(surfaces, Surface.CREATOR);
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
@@ -511,13 +414,13 @@
mRotation = rotation;
mSurfaces = surfaces;
mConfiguredSize = new Size(width, height);
- // First surface could be null (being deferred). Use last surface to look up surface
- // characteristics.
- if (mSurfaces[surfaceCnt-1] != null) {
+ mIsDeferredConfig = isDeferred;
+ mSurfaces = surfaces;
+ if (mSurfaces.size() > 0) {
mSurfaceType = SURFACE_TYPE_UNKNOWN;
- mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces[surfaceCnt-1]);
- mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurfaces[surfaceCnt-1]);
- mConfiguredGenerationId = mSurfaces[surfaceCnt-1].getGenerationId();
+ mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces.get(0));
+ mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurfaces.get(0));
+ mConfiguredGenerationId = mSurfaces.get(0).getGenerationId();
} else {
mSurfaceType = surfaceType;
mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
@@ -525,38 +428,31 @@
StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
mConfiguredGenerationId = 0;
}
-
- if (mSurfaces[0] == null) {
- mIsDeferredConfig = true;
- } else {
- mIsDeferredConfig = false;
- }
}
/**
* Get the {@link Surface} associated with this {@link OutputConfiguration}.
*
- * @return the {@link Surface} associated with this {@link OutputConfiguration}. If more than
- * one surface is associated with this {@link OutputConfiguration}, return the first one as
- * specified in the constructor. If there is a deferred surface, null will be returned.
+ * If more than one surface is associated with this {@link OutputConfiguration}, return the
+ * first one as specified in the constructor or {@link OutputConfiguration#addSurface}.
*/
public @Nullable Surface getSurface() {
- return mSurfaces[0];
+ if (mSurfaces.size() == 0) {
+ return null;
+ }
+
+ return mSurfaces.get(0);
}
/**
* Get the immutable list of surfaces associated with this {@link OutputConfiguration}.
*
- * @return the list of surfaces associated with this {@link OutputConfiguration} in the order
- * specified in the constructor. If there is a deferred surface in the {@link
- * OutputConfiguration}, it is returned as null as first element of the list. The list should
- * not be modified.
- *
- * @hide
+ * @return the list of surfaces associated with this {@link OutputConfiguration} as specified in
+ * the constructor and {@link OutputConfiguration#addSurface}. The list should not be modified.
*/
@NonNull
public List<Surface> getSurfaces() {
- return Collections.unmodifiableList(Arrays.asList(mSurfaces));
+ return Collections.unmodifiableList(mSurfaces);
}
/**
@@ -616,12 +512,9 @@
dest.writeInt(mSurfaceType);
dest.writeInt(mConfiguredSize.getWidth());
dest.writeInt(mConfiguredSize.getHeight());
- dest.writeInt(mSurfaces.length);
- for (int i = 0; i < mSurfaces.length; i++) {
- if (mSurfaces[i] != null) {
- mSurfaces[i].writeToParcel(dest, flags);
- }
- }
+ dest.writeInt(mIsDeferredConfig ? 1 : 0);
+ dest.writeInt(mIsShared ? 1 : 0);
+ dest.writeTypedList(mSurfaces);
}
/**
@@ -647,16 +540,15 @@
mSurfaceGroupId != other.mSurfaceGroupId ||
mSurfaceType != other.mSurfaceType ||
mIsDeferredConfig != other.mIsDeferredConfig ||
+ mIsShared != other.mIsShared ||
mConfiguredFormat != other.mConfiguredFormat ||
mConfiguredDataspace != other.mConfiguredDataspace ||
- mSurfaces.length != other.mSurfaces.length ||
mConfiguredGenerationId != other.mConfiguredGenerationId)
return false;
- // If deferred, skip the first surface of mSurfaces when comparing.
- int minIndex = (mIsDeferredConfig ? 1 : 0);
- for (int i = minIndex; i < mSurfaces.length; i++) {
- if (mSurfaces[i] != other.mSurfaces[i])
+ int minLen = Math.min(mSurfaces.size(), other.mSurfaces.size());
+ for (int i = 0; i < minLen; i++) {
+ if (mSurfaces.get(i) != other.mSurfaces.get(i))
return false;
}
@@ -670,23 +562,23 @@
*/
@Override
public int hashCode() {
- // Need ensure that the hashcode remains unchanged after set a deferred surface. Otherwise
+ // Need ensure that the hashcode remains unchanged after adding a deferred surface. Otherwise
// the deferred output configuration will be lost in the camera streammap after the deferred
// surface is set.
- int minIndex = (mIsDeferredConfig ? 1 : 0);
- Surface nonDeferredSurfaces[] = Arrays.copyOfRange(mSurfaces,
- minIndex, mSurfaces.length);
- int surfaceHash = HashCodeHelpers.hashCodeGeneric(nonDeferredSurfaces);
+ if (mIsDeferredConfig) {
+ return HashCodeHelpers.hashCode(
+ mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace,
+ mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0);
+ }
return HashCodeHelpers.hashCode(
- mRotation, surfaceHash, mConfiguredGenerationId,
+ mRotation, mSurfaces.hashCode(), mConfiguredGenerationId,
mConfiguredSize.hashCode(), mConfiguredFormat,
- mConfiguredDataspace, mSurfaceGroupId);
+ mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0);
}
private static final String TAG = "OutputConfiguration";
- private static final int MAX_SURFACES_COUNT = 2;
- private Surface mSurfaces[];
+ private ArrayList<Surface> mSurfaces;
private final int mRotation;
private final int mSurfaceGroupId;
// Surface source type, this is only used by the deferred surface configuration objects.
@@ -700,4 +592,6 @@
private final int mConfiguredGenerationId;
// Flag indicating if this config has deferred surface.
private final boolean mIsDeferredConfig;
+ // Flag indicating if this config has shared surfaces
+ private boolean mIsShared;
}
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index c9a4e9b..fea730e 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -16,11 +16,12 @@
package android.hardware.usb;
-import com.android.internal.util.Preconditions;
-
+import android.hardware.usb.V1_0.Constants;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
/**
* Represents a physical USB port and describes its characteristics.
* <p>
@@ -33,6 +34,7 @@
private final String mId;
private final int mSupportedModes;
+ public static final int MODE_NONE = Constants.PortMode.NONE;
/**
* Mode bit: This USB port can act as a downstream facing port (host).
* <p>
@@ -40,7 +42,7 @@
* combination of roles (and possibly others as well).
* </p>
*/
- public static final int MODE_DFP = 1 << 0;
+ public static final int MODE_DFP = Constants.PortMode.DFP;
/**
* Mode bit: This USB port can act as an upstream facing port (device).
@@ -49,7 +51,7 @@
* combination of roles (and possibly others as well).
* </p>
*/
- public static final int MODE_UFP = 1 << 1;
+ public static final int MODE_UFP = Constants.PortMode.UFP;
/**
* Mode bit: This USB port can act either as an downstream facing port (host) or as
@@ -60,29 +62,43 @@
* combination of roles (and possibly others as well).
* </p>
*/
- public static final int MODE_DUAL = MODE_DFP | MODE_UFP;
+ public static final int MODE_DUAL = Constants.PortMode.DRP;
+
+ /**
+ * Power role: This USB port does not have a power role.
+ */
+ public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE;
/**
* Power role: This USB port can act as a source (provide power).
*/
- public static final int POWER_ROLE_SOURCE = 1;
+ public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE;
/**
* Power role: This USB port can act as a sink (receive power).
*/
- public static final int POWER_ROLE_SINK = 2;
+ public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK;
+
+ /**
+ * Power role: This USB port does not have a data role.
+ */
+ public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE;
/**
* Data role: This USB port can act as a host (access data services).
*/
- public static final int DATA_ROLE_HOST = 1;
+ public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST;
/**
* Data role: This USB port can act as a device (offer data services).
*/
- public static final int DATA_ROLE_DEVICE = 2;
+ public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE;
- private static final int NUM_DATA_ROLES = 3;
+ private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES;
+ /**
+ * Points to the first power role in the IUsb HAL.
+ */
+ private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE;
/** @hide */
public UsbPort(String id, int supportedModes) {
@@ -126,14 +142,14 @@
*/
public static int combineRolesAsBit(int powerRole, int dataRole) {
checkRoles(powerRole, dataRole);
- final int index = powerRole * NUM_DATA_ROLES + dataRole;
+ final int index = ((powerRole - POWER_ROLE_OFFSET) * NUM_DATA_ROLES) + dataRole;
return 1 << index;
}
/** @hide */
public static String modeToString(int mode) {
switch (mode) {
- case 0:
+ case MODE_NONE:
return "none";
case MODE_DFP:
return "dfp";
@@ -149,7 +165,7 @@
/** @hide */
public static String powerRoleToString(int role) {
switch (role) {
- case 0:
+ case POWER_ROLE_NONE:
return "no-power";
case POWER_ROLE_SOURCE:
return "source";
@@ -163,7 +179,7 @@
/** @hide */
public static String dataRoleToString(int role) {
switch (role) {
- case 0:
+ case DATA_ROLE_NONE:
return "no-data";
case DATA_ROLE_HOST:
return "host";
@@ -183,7 +199,7 @@
while (combo != 0) {
final int index = Integer.numberOfTrailingZeros(combo);
combo &= ~(1 << index);
- final int powerRole = index / NUM_DATA_ROLES;
+ final int powerRole = (index / NUM_DATA_ROLES + POWER_ROLE_OFFSET);
final int dataRole = index % NUM_DATA_ROLES;
if (first) {
first = false;
@@ -200,9 +216,28 @@
}
/** @hide */
+ public static void checkMode(int powerRole) {
+ Preconditions.checkArgumentInRange(powerRole, Constants.PortMode.NONE,
+ Constants.PortMode.NUM_MODES - 1, "portMode");
+ }
+
+ /** @hide */
+ public static void checkPowerRole(int dataRole) {
+ Preconditions.checkArgumentInRange(dataRole, Constants.PortPowerRole.NONE,
+ Constants.PortPowerRole.NUM_POWER_ROLES - 1, "powerRole");
+ }
+
+ /** @hide */
+ public static void checkDataRole(int mode) {
+ Preconditions.checkArgumentInRange(mode, Constants.PortDataRole.NONE,
+ Constants.PortDataRole.NUM_DATA_ROLES - 1, "powerRole");
+ }
+
+ /** @hide */
public static void checkRoles(int powerRole, int dataRole) {
- Preconditions.checkArgumentInRange(powerRole, 0, POWER_ROLE_SINK, "powerRole");
- Preconditions.checkArgumentInRange(dataRole, 0, DATA_ROLE_DEVICE, "dataRole");
+ Preconditions.checkArgumentInRange(powerRole, POWER_ROLE_NONE, POWER_ROLE_SINK,
+ "powerRole");
+ Preconditions.checkArgumentInRange(dataRole, DATA_ROLE_NONE, DATA_ROLE_DEVICE, "dataRole");
}
@Override
diff --git a/core/java/com/android/internal/logging/LogBuilder.java b/core/java/android/metrics/LogMaker.java
similarity index 76%
rename from core/java/com/android/internal/logging/LogBuilder.java
rename to core/java/android/metrics/LogMaker.java
index 7eda3da..0aef532 100644
--- a/core/java/com/android/internal/logging/LogBuilder.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -13,76 +13,89 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.logging;
+package android.metrics;
-import android.util.EventLog;
+import android.annotation.SystemApi;
import android.util.Log;
import android.util.SparseArray;
-import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
/**
* Helper class to assemble more complex logs.
*
* @hide
*/
-
-public class LogBuilder {
+@SystemApi
+public class LogMaker {
private static final String TAG = "LogBuilder";
+
+ /**
+ * Min required eventlog line length.
+ * See: android/util/cts/EventLogTest.java
+ * Size checks enforced here are intended only as sanity checks;
+ * your logs may be truncated earlier. Please log responsibly.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static final int MAX_SERIALIZED_SIZE = 4000;
+
private SparseArray<Object> entries = new SparseArray();
- public LogBuilder(int mainCategory) {
+ public LogMaker(int mainCategory) {
setCategory(mainCategory);
}
/* Deserialize from the eventlog */
- public LogBuilder(Object[] items) {
+ public LogMaker(Object[] items) {
deserialize(items);
}
- public LogBuilder setCategory(int category) {
+ public LogMaker setCategory(int category) {
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, category);
return this;
}
- public LogBuilder setType(int type) {
+ public LogMaker setType(int type) {
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE, type);
return this;
}
- public LogBuilder setSubtype(int subtype) {
+ public LogMaker setSubtype(int subtype) {
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE, subtype);
return this;
}
- public LogBuilder setTimestamp(long timestamp) {
+ public LogMaker setTimestamp(long timestamp) {
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP, timestamp);
return this;
}
- public LogBuilder setPackageName(String packageName) {
+ public LogMaker setPackageName(String packageName) {
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, packageName);
return this;
}
- public LogBuilder setCounterName(String name) {
+ public LogMaker setCounterName(String name) {
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME, name);
return this;
}
- public LogBuilder setCounterBucket(int bucket) {
+ public LogMaker setCounterBucket(int bucket) {
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
return this;
}
- public LogBuilder setCounterBucket(long bucket) {
+ public LogMaker setCounterBucket(long bucket) {
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
return this;
}
- public LogBuilder setCounterValue(int value) {
+ public LogMaker setCounterValue(int value) {
entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE, value);
return this;
}
@@ -92,12 +105,16 @@
* @param value One of Integer, Long, Float, String
* @return
*/
- public LogBuilder addTaggedData(int tag, Object value) {
+ public LogMaker addTaggedData(int tag, Object value) {
if (isValidValue(value)) {
throw new IllegalArgumentException(
"Value must be loggable type - int, long, float, String");
}
- entries.put(tag, value);
+ if (value.toString().getBytes().length > MAX_SERIALIZED_SIZE) {
+ Log.i(TAG, "Log value too long, omitted: " + value.toString());
+ } else {
+ entries.put(tag, value);
+ }
return this;
}
@@ -198,18 +215,23 @@
out[i * 2] = entries.keyAt(i);
out[i * 2 + 1] = entries.valueAt(i);
}
+ int size = out.toString().getBytes().length;
+ if (size > MAX_SERIALIZED_SIZE) {
+ Log.i(TAG, "Log line too long, did not emit: " + size + " bytes.");
+ throw new RuntimeException();
+ }
return out;
}
public void deserialize(Object[] items) {
int i = 0;
- while(i < items.length) {
+ while (i < items.length) {
Object key = items[i++];
Object value = i < items.length ? items[i++] : null;
if (key instanceof Integer) {
entries.put((Integer) key, value);
} else {
- Log.i(TAG, "Invalid key " + key.toString());
+ Log.i(TAG, "Invalid key " + key.toString());
}
}
}
diff --git a/core/java/com/android/internal/logging/MetricsReader.java b/core/java/android/metrics/MetricsReader.java
similarity index 92%
rename from core/java/com/android/internal/logging/MetricsReader.java
rename to core/java/android/metrics/MetricsReader.java
index c4fc963..079c2c9 100644
--- a/core/java/com/android/internal/logging/MetricsReader.java
+++ b/core/java/android/metrics/MetricsReader.java
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.logging;
+package android.metrics;
+
+import android.annotation.SystemApi;
import com.android.internal.logging.legacy.LegacyConversionLogger;
import com.android.internal.logging.legacy.EventLogCollector;
@@ -22,10 +24,12 @@
/**
* Read platform logs.
+ * @hide
*/
+@SystemApi
public class MetricsReader {
private EventLogCollector mReader;
- private Queue<LogBuilder> mEventQueue;
+ private Queue<LogMaker> mEventQueue;
private long mLastEventMs;
private long mCheckpointMs;
@@ -57,7 +61,7 @@
}
/* Next entry in the current log session. */
- public LogBuilder next() {
+ public LogMaker next() {
return mEventQueue == null ? null : mEventQueue.remove();
}
diff --git a/core/java/android/net/INetworkScoreCache.aidl b/core/java/android/net/INetworkScoreCache.aidl
index 35601ce..1da7d67 100644
--- a/core/java/android/net/INetworkScoreCache.aidl
+++ b/core/java/android/net/INetworkScoreCache.aidl
@@ -34,7 +34,7 @@
* the current scores for each network for debugging purposes.
* @hide
*/
-interface INetworkScoreCache
+oneway interface INetworkScoreCache
{
void updateScores(in List<ScoredNetwork> networks);
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index 1a128e0..e5f0bf0 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -16,10 +16,14 @@
package android.net;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.Objects;
@@ -65,6 +69,27 @@
}
/**
+ * Constructs a new NetworkKey for the given {@link WifiInfo}.
+ *
+ * @param wifiInfo the {@link WifiInfo} to create a {@link NetworkKey} for.
+ * @return A new {@link NetworkKey} instance or <code>null</code> if the given {@link WifiInfo}
+ * instance doesn't represent a connected WiFi network.
+ * @hide
+ */
+ @Nullable
+ public static NetworkKey createFromWifiInfo(@Nullable WifiInfo wifiInfo) {
+ if (wifiInfo != null) {
+ final String ssid = wifiInfo.getSSID();
+ final String bssid = wifiInfo.getBSSID();
+ if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE)
+ && !TextUtils.isEmpty(bssid)) {
+ return new NetworkKey(new WifiKey(ssid, bssid));
+ }
+ }
+ return null;
+ }
+
+ /**
* Construct a new {@link NetworkKey} for a Wi-Fi network.
* @param wifiKey the {@link WifiKey} identifying this Wi-Fi network.
*/
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 20de370..e4cdbce 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -44,7 +44,6 @@
// without significantly disrupting other activity launch work.
Thread eglInitThread = new Thread(
() -> {
- Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
},
"EGL Init");
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 1c2588a..12f5396 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -43,6 +43,7 @@
void setUserEnabled(int userHandle);
void evictCredentialEncryptionKey(int userHandle);
boolean removeUser(int userHandle);
+ boolean removeUserEvenWhenDisallowed(int userHandle);
void setUserName(int userHandle, String name);
void setUserIcon(int userHandle, in Bitmap icon);
ParcelFileDescriptor getUserIcon(int userHandle);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index efacb20..c5c380c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2082,6 +2082,22 @@
}
/**
+ * Similar to {@link #removeUser(int)} except bypassing the checking of
+ * {@link UserManager#DISALLOW_REMOVE_USER}
+ * or {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE}.
+ *
+ * @see {@link #removeUser(int)}
+ * @hide
+ */
+ public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+ try {
+ return mService.removeUserEvenWhenDisallowed(userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Updates the user's name.
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
*
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index b03c907..35a266b 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -291,4 +291,6 @@
void fstrim(int flags) = 72;
AppFuseMount mountProxyFileDescriptorBridge() = 73;
ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
+ long getCacheQuotaBytes(String volumeUuid, int uid) = 75;
+ long getCacheSizeBytes(String volumeUuid, int uid) = 76;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index c6ff476..626d6f4 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -24,27 +24,32 @@
import android.app.ActivityThread;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
-import android.os.ProxyFileDescriptorCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.AppFuseMount;
@@ -60,6 +65,7 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -1396,6 +1402,222 @@
}
}
+ /**
+ * Return quota size in bytes for cached data belonging to the calling app.
+ * <p>
+ * If your app goes above this quota, your cached files will be some of the
+ * first to be deleted when additional disk space is needed. Conversely, if
+ * your app stays under this quota, your cached files will be some of the
+ * last to be deleted when additional disk space is needed.
+ * <p>
+ * This quota may change over time depending on how frequently the user
+ * interacts with your app, and depending on how much disk space is used.
+ * <p>
+ * Cached data tracked by this method always includes
+ * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
+ * it also includes {@link Context#getExternalCacheDir()} if the primary
+ * shared/external storage is hosted on the same storage device as your
+ * private data.
+ * <p class="note">
+ * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+ * then cached data for all packages in your shared UID is tracked together
+ * as a single unit.
+ * </p>
+ *
+ * @see #getCacheQuotaBytes()
+ * @see #getCacheSizeBytes()
+ * @see #getExternalCacheQuotaBytes()
+ * @see #getExternalCacheSizeBytes()
+ */
+ public long getCacheQuotaBytes() {
+ try {
+ final ApplicationInfo app = mContext.getApplicationInfo();
+ return mStorageManager.getCacheQuotaBytes(app.volumeUuid, app.uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return total size in bytes of cached data belonging to the calling app.
+ * <p>
+ * Cached data tracked by this method always includes
+ * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
+ * it also includes {@link Context#getExternalCacheDir()} if the primary
+ * shared/external storage is hosted on the same storage device as your
+ * private data.
+ * <p class="note">
+ * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+ * then cached data for all packages in your shared UID is tracked together
+ * as a single unit.
+ * </p>
+ *
+ * @see #getCacheQuotaBytes()
+ * @see #getCacheSizeBytes()
+ * @see #getExternalCacheQuotaBytes()
+ * @see #getExternalCacheSizeBytes()
+ */
+ public long getCacheSizeBytes() {
+ try {
+ final ApplicationInfo app = mContext.getApplicationInfo();
+ return mStorageManager.getCacheSizeBytes(app.volumeUuid, app.uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return quota size in bytes for cached data on primary shared/external
+ * storage belonging to the calling app.
+ * <p>
+ * If primary shared/external storage is hosted on the same storage device
+ * as your private data, this method will return -1, since all data stored
+ * under {@link Context#getExternalCacheDir()} will be counted under
+ * {@link #getCacheQuotaBytes()}.
+ * <p class="note">
+ * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+ * then cached data for all packages in your shared UID is tracked together
+ * as a single unit.
+ * </p>
+ */
+ public long getExternalCacheQuotaBytes() {
+ final ApplicationInfo app = mContext.getApplicationInfo();
+ final String primaryUuid = getPrimaryStorageUuid();
+ if (Objects.equals(app.volumeUuid, primaryUuid)) {
+ return -1;
+ }
+ try {
+ return mStorageManager.getCacheQuotaBytes(primaryUuid, app.uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return total size in bytes of cached data on primary shared/external
+ * storage belonging to the calling app.
+ * <p>
+ * If primary shared/external storage is hosted on the same storage device
+ * as your private data, this method will return -1, since all data stored
+ * under {@link Context#getExternalCacheDir()} will be counted under
+ * {@link #getCacheQuotaBytes()}.
+ * <p class="note">
+ * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+ * then cached data for all packages in your shared UID is tracked together
+ * as a single unit.
+ * </p>
+ */
+ public long getExternalCacheSizeBytes() {
+ final ApplicationInfo app = mContext.getApplicationInfo();
+ final String primaryUuid = getPrimaryStorageUuid();
+ if (Objects.equals(app.volumeUuid, primaryUuid)) {
+ return -1;
+ }
+ try {
+ return mStorageManager.getCacheSizeBytes(primaryUuid, app.uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static final String XATTR_ATOMIC = "user.atomic";
+ private static final String XATTR_TOMBSTONE = "user.tombstone";
+
+ /** {@hide} */
+ private static void setCacheBehavior(File path, String name, boolean enabled)
+ throws IOException {
+ if (!path.isDirectory()) {
+ throw new IOException("Cache behavior can only be set on directories");
+ }
+ if (enabled) {
+ try {
+ Os.setxattr(path.getAbsolutePath(), name,
+ "1".getBytes(StandardCharsets.UTF_8), 0);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ } else {
+ try {
+ Os.removexattr(path.getAbsolutePath(), name);
+ } catch (ErrnoException e) {
+ if (e.errno != OsConstants.ENODATA) {
+ throw e.rethrowAsIOException();
+ }
+ }
+ }
+ }
+
+ /** {@hide} */
+ private static boolean isCacheBehavior(File path, String name) throws IOException {
+ try {
+ Os.getxattr(path.getAbsolutePath(), name);
+ return true;
+ } catch (ErrnoException e) {
+ if (e.errno != OsConstants.ENODATA) {
+ throw e.rethrowAsIOException();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Enable or disable special cache behavior that treats this directory and
+ * its contents as an atomic unit.
+ * <p>
+ * When enabled and this directory is considered for automatic deletion by
+ * the OS, all contained files will either be deleted together, or not at
+ * all. This is useful when you have a directory that contains several
+ * related metadata files that depend on each other, such as movie file and
+ * a subtitle file.
+ * <p>
+ * When enabled, the <em>newest</em> {@link File#lastModified()} value of
+ * any contained files is considered the modified time of the entire
+ * directory.
+ * <p>
+ * This behavior can only be set on a directory, and it applies recursively
+ * to all contained files and directories.
+ */
+ public void setCacheBehaviorAtomic(File path, boolean atomic) throws IOException {
+ setCacheBehavior(path, XATTR_ATOMIC, atomic);
+ }
+
+ /**
+ * Read the current value set by
+ * {@link #setCacheBehaviorAtomic(File, boolean)}.
+ */
+ public boolean isCacheBehaviorAtomic(File path) throws IOException {
+ return isCacheBehavior(path, XATTR_ATOMIC);
+ }
+
+ /**
+ * Enable or disable special cache behavior that leaves deleted cache files
+ * intact as tombstones.
+ * <p>
+ * When enabled and a file contained in this directory is automatically
+ * deleted by the OS, the file will be truncated to have a length of 0 bytes
+ * instead of being fully deleted. This is useful if you need to distinguish
+ * between a file that was deleted versus one that never existed.
+ * <p>
+ * This behavior can only be set on a directory, and it applies recursively
+ * to all contained files and directories.
+ * <p class="note">
+ * Note: this behavior is ignored completely if the user explicitly requests
+ * that all cached data be cleared.
+ * </p>
+ */
+ public void setCacheBehaviorTombstone(File path, boolean tombstone) throws IOException {
+ setCacheBehavior(path, XATTR_TOMBSTONE, tombstone);
+ }
+
+ /**
+ * Read the current value set by
+ * {@link #setCacheBehaviorTombstone(File, boolean)}.
+ */
+ public boolean isCacheBehaviorTombstone(File path) throws IOException {
+ return isCacheBehavior(path, XATTR_TOMBSTONE);
+ }
+
private final Object mFuseAppLoopLock = new Object();
@GuardedBy("mFuseAppLoopLock")
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 8303bca..089eaec 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -145,6 +145,8 @@
private List<Preference> mDependents;
+ private PreferenceGroup mParentGroup;
+
private boolean mBaseMethodCalled;
/**
@@ -1238,6 +1240,16 @@
registerDependency();
}
+ /**
+ * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set null to remove
+ * the current parent.
+ *
+ * @param parentGroup Parent preference group of this Preference or null if none.
+ */
+ void assignParent(@Nullable PreferenceGroup parentGroup) {
+ mParentGroup = parentGroup;
+ }
+
private void registerDependency() {
if (TextUtils.isEmpty(mDependencyKey)) return;
@@ -1401,6 +1413,17 @@
}
/**
+ * Returns the {@link PreferenceGroup} which is this Preference assigned to or null if this
+ * preference is not assigned to any group or is a root Preference.
+ *
+ * @return The parent PreferenceGroup or null if not attached to any.
+ */
+ @Nullable
+ public PreferenceGroup getParent() {
+ return mParentGroup;
+ }
+
+ /**
* Called when this Preference is being removed from the hierarchy. You
* should remove any references to this Preference that you know about. Make
* sure to call through to the superclass implementation.
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
index f17506b..f135b26 100644
--- a/core/java/android/preference/PreferenceGroup.java
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -29,14 +29,14 @@
* A container for multiple
* {@link Preference} objects. It is a base class for Preference objects that are
* parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}.
- *
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For information about building a settings UI with Preferences,
* read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
* guide.</p>
* </div>
- *
+ *
* @attr ref android.R.styleable#PreferenceGroup_orderingFromXml
*/
public abstract class PreferenceGroup extends Preference implements GenericInflater.Parent<Preference> {
@@ -80,7 +80,7 @@
* <p>
* If this is called after preferences are added, they will not be
* re-ordered in the order they were added, hence call this method early on.
- *
+ *
* @param orderingAsAdded Whether to order according to the order added.
* @see Preference#setOrder(int)
*/
@@ -90,7 +90,7 @@
/**
* Whether this group is ordering preferences in the order they are added.
- *
+ *
* @return Whether this group orders based on the order the children are added.
* @see #setOrderingAsAdded(boolean)
*/
@@ -115,7 +115,7 @@
/**
* Returns the {@link Preference} at a particular index.
- *
+ *
* @param index The index of the {@link Preference} to retrieve.
* @return The {@link Preference}.
*/
@@ -126,7 +126,7 @@
/**
* Adds a {@link Preference} at the correct position based on the
* preference's order.
- *
+ *
* @param preference The preference to add.
* @return Whether the preference is now in this group.
*/
@@ -135,7 +135,7 @@
// Exists
return true;
}
-
+
if (preference.getOrder() == Preference.DEFAULT_ORDER) {
if (mOrderingAsAdded) {
preference.setOrder(mCurrentPreferenceOrder++);
@@ -161,11 +161,12 @@
}
preference.onAttachedToHierarchy(getPreferenceManager());
-
+ preference.assignParent(this);
+
if (mAttachedToActivity) {
preference.onAttachedToActivity();
}
-
+
notifyHierarchyChanged();
return true;
@@ -173,7 +174,7 @@
/**
* Removes a {@link Preference} from this group.
- *
+ *
* @param preference The preference to remove.
* @return Whether the preference was found and removed.
*/
@@ -186,10 +187,13 @@
private boolean removePreferenceInt(Preference preference) {
synchronized(this) {
preference.onPrepareForRemoval();
+ if (preference.getParent() == this) {
+ preference.assignParent(null);
+ }
return mPreferenceList.remove(preference);
}
}
-
+
/**
* Removes all {@link Preference Preferences} from this group.
*/
@@ -202,10 +206,10 @@
}
notifyHierarchyChanged();
}
-
+
/**
* Prepares a {@link Preference} to be added to the group.
- *
+ *
* @param preference The preference to add.
* @return Whether to allow adding the preference (true), or not (false).
*/
@@ -223,7 +227,7 @@
* <p>
* This will recursively search for the preference into children that are
* also {@link PreferenceGroup PreferenceGroups}.
- *
+ *
* @param key The key of the preference to retrieve.
* @return The {@link Preference} with the key, or null.
*/
@@ -239,7 +243,7 @@
if (curKey != null && curKey.equals(key)) {
return preference;
}
-
+
if (preference instanceof PreferenceGroup) {
final Preference returnedPreference = ((PreferenceGroup)preference)
.findPreference(key);
@@ -255,14 +259,14 @@
/**
* Whether this preference group should be shown on the same screen as its
* contained preferences.
- *
+ *
* @return True if the contained preferences should be shown on the same
* screen as this preference.
*/
protected boolean isOnSameScreenAsChildren() {
return true;
}
-
+
@Override
protected void onAttachedToActivity() {
super.onAttachedToActivity();
@@ -270,7 +274,7 @@
// Mark as attached so if a preference is later added to this group, we
// can tell it we are already attached
mAttachedToActivity = true;
-
+
// Dispatch to all contained preferences
final int preferenceCount = getPreferenceCount();
for (int i = 0; i < preferenceCount; i++) {
@@ -281,7 +285,7 @@
@Override
protected void onPrepareForRemoval() {
super.onPrepareForRemoval();
-
+
// We won't be attached to the activity anymore
mAttachedToActivity = false;
}
@@ -297,7 +301,7 @@
getPreference(i).onParentChanged(this, disableDependents);
}
}
-
+
void sortPreferences() {
synchronized (this) {
Collections.sort(mPreferenceList);
@@ -314,7 +318,7 @@
getPreference(i).dispatchSaveInstanceState(container);
}
}
-
+
@Override
protected void dispatchRestoreInstanceState(Bundle container) {
super.dispatchRestoreInstanceState(container);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 6170eb4..f5e558a 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -678,8 +678,18 @@
throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
}
+ /**
+ * WARNING: Sub-classes should not override this method. This method is non-final
+ * solely for the purposes of backwards compatibility.
+ *
+ * @see #queryChildDocuments(String, String[], Bundle),
+ * {@link #queryDocument(String, String[])},
+ * {@link #queryRecentDocuments(String, String[])},
+ * {@link #queryRoots(String[])}, and
+ * {@link #querySearchDocuments(String, String, String[])}.
+ */
@Override
- public final Cursor query(Uri uri, String[] projection, String selection,
+ public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
// As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
// transport method. We override that, and don't ever delegate to this metohd.
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
new file mode 100644
index 0000000..90e710f
--- /dev/null
+++ b/core/java/android/provider/FontsContract.java
@@ -0,0 +1,197 @@
+/*
+ * 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.provider;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.graphics.fonts.FontRequest;
+import android.graphics.fonts.FontResult;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * Utility class to deal with Font ContentProviders.
+ */
+public class FontsContract {
+ private static final String TAG = "FontsContract";
+
+ /**
+ * Defines the constants used in a response from a Font Provider. The cursor returned from the
+ * query should have the ID column populated with the content uri ID for the resulting font.
+ * This should point to a real file or shared memory, as the client will mmap the given file
+ * descriptor. Pipes, sockets and other non-mmap-able file descriptors will fail to load in the
+ * client application.
+ */
+ public static final class Columns implements BaseColumns {
+ /**
+ * Constant used to request data from a font provider. The cursor returned from the query
+ * should have this column populated with an int for the ttc index for the resulting font.
+ */
+ public static final String TTC_INDEX = "font_ttc_index";
+ /**
+ * Constant used to request data from a font provider. The cursor returned from the query
+ * may populate this column with the font variation settings String information for the
+ * font.
+ */
+ public static final String VARIATION_SETTINGS = "font_variation_settings";
+ /**
+ * Constant used to request data from a font provider. The cursor returned from the query
+ * should have this column populated with the int style for the resulting font. This should
+ * be one of {@link android.graphics.Typeface#NORMAL},
+ * {@link android.graphics.Typeface#BOLD}, {@link android.graphics.Typeface#ITALIC} or
+ * {@link android.graphics.Typeface#BOLD_ITALIC}
+ */
+ public static final String STYLE = "font_style";
+ }
+
+ /**
+ * Constant used to identify the List of {@link ParcelFileDescriptor} item in the Bundle
+ * returned to the ResultReceiver in getFont.
+ * @hide
+ */
+ public static final String PARCEL_FONT_RESULTS = "font_results";
+
+ /** @hide */
+ public static final int RESULT_CODE_OK = 0;
+ /** @hide */
+ public static final int RESULT_CODE_FONT_NOT_FOUND = 1;
+ /** @hide */
+ public static final int RESULT_CODE_PROVIDER_NOT_FOUND = 2;
+
+ private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000;
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private Handler mHandler;
+ @GuardedBy("mLock")
+ private HandlerThread mThread;
+
+ /** @hide */
+ public FontsContract() {
+ // TODO: investigate if the system context is the best option here. ApplicationContext or
+ // the one passed by developer?
+ // TODO: Looks like ActivityThread.currentActivityThread() can return null. Check when it
+ // returns null and check if we need to handle null case.
+ mContext = ActivityThread.currentActivityThread().getSystemContext();
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ // We use a background thread to post the content resolving work for all requests on. This
+ // thread should be quit/stopped after all requests are done.
+ private final Runnable mReplaceDispatcherThreadRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mThread != null) {
+ mThread.quitSafely();
+ mThread = null;
+ mHandler = null;
+ }
+ }
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public void getFont(FontRequest request, ResultReceiver receiver) {
+ synchronized (mLock) {
+ if (mHandler == null) {
+ mThread = new HandlerThread("fonts", Process.THREAD_PRIORITY_BACKGROUND);
+ mThread.start();
+ mHandler = new Handler(mThread.getLooper());
+ }
+ mHandler.post(() -> {
+ String providerAuthority = request.getProviderAuthority();
+ // TODO: Implement cert checking for non-system apps
+ ProviderInfo providerInfo = mPackageManager.resolveContentProvider(
+ providerAuthority, PackageManager.MATCH_SYSTEM_ONLY);
+ if (providerInfo == null) {
+ receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
+ return;
+ }
+ Bundle result = getFontFromProvider(request, receiver, providerInfo);
+ if (result == null) {
+ receiver.send(RESULT_CODE_FONT_NOT_FOUND, null);
+ return;
+ }
+ receiver.send(RESULT_CODE_OK, result);
+ });
+ mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable);
+ mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
+ }
+ }
+
+ private Bundle getFontFromProvider(FontRequest request, ResultReceiver receiver,
+ ProviderInfo providerInfo) {
+ ArrayList<FontResult> result = null;
+ Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(providerInfo.authority)
+ .build();
+ try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID,
+ Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.STYLE },
+ "query = ?", new String[] { request.getQuery() }, null);) {
+ // TODO: Should we restrict the amount of fonts that can be returned?
+ // TODO: Write documentation explaining that all results should be from the same family.
+ if (cursor != null && cursor.getCount() > 0) {
+ result = new ArrayList<>();
+ final int idColumnIndex = cursor.getColumnIndex(Columns._ID);
+ final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX);
+ final int vsColumnIndex = cursor.getColumnIndex(Columns.VARIATION_SETTINGS);
+ final int styleColumnIndex = cursor.getColumnIndex(Columns.STYLE);
+ while (cursor.moveToNext()) {
+ long id = cursor.getLong(idColumnIndex);
+ Uri fileUri = ContentUris.withAppendedId(uri, id);
+ try {
+ ParcelFileDescriptor pfd =
+ mContext.getContentResolver().openFileDescriptor(fileUri, "r");
+ final int ttcIndex = cursor.getInt(ttcIndexColumnIndex);
+ final String variationSettings = cursor.getString(vsColumnIndex);
+ final int style = cursor.getInt(styleColumnIndex);
+ result.add(new FontResult(pfd, ttcIndex, variationSettings, style));
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "FileNotFoundException raised when interacting with content "
+ + "provider " + providerInfo.authority, e);
+ }
+ }
+ }
+ }
+ if (result != null && !result.isEmpty()) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
+ return bundle;
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5dcc96a..9e2d4a7 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1334,6 +1334,19 @@
= "android.settings.VR_LISTENER_SETTINGS";
/**
+ * Activity Action: Show Picture-in-picture settings.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PICTURE_IN_PICTURE_SETTINGS
+ = "android.settings.PICTURE_IN_PICTURE_SETTINGS";
+
+ /**
* Activity Action: Show Storage Manager settings.
* <p>
* Input: Nothing.
@@ -7601,6 +7614,14 @@
"hdmi_control_auto_device_off_enabled";
/**
+ * The interval in milliseconds at which location requests will be throttled when they are
+ * coming from the background.
+ * @hide
+ */
+ public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
+ "location_background_throttle_interval_ms";
+
+ /**
* Whether TV will switch to MHL port when a mobile device is plugged in.
* (0 = false, 1 = true)
* @hide
@@ -9493,10 +9514,11 @@
DOCK_SOUNDS_ENABLED,
CHARGING_SOUNDS_ENABLED,
USB_MASS_STORAGE_ENABLED,
+ NETWORK_RECOMMENDATIONS_ENABLED,
+ CURATE_SAVED_OPEN_NETWORKS,
+ WIFI_WAKEUP_ENABLED,
WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
- WIFI_NUM_OPEN_NETWORKS_KEPT,
EMERGENCY_TONE,
CALL_AUTO_RETRY,
DOCK_AUDIO_MEDIA_ENABLED,
@@ -10007,6 +10029,17 @@
EPHEMERAL_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
}
+ /**
+ * Whether to show the high temperature warning notification.
+ * @hide
+ */
+ public static final String SHOW_TEMPERATURE_WARNING = "show_temperature_warning";
+
+ /**
+ * Temperature at which the high temperature warning notification should be shown.
+ * @hide
+ */
+ public static final String WARNING_TEMPERATURE = "warning_temperature";
}
/**
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index a4b6807..e48a0d0 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -319,7 +319,7 @@
*
* @hide
*/
- public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicmail";
+ public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
/**
* A convenience method to build voicemail URI specific to a source package by appending
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 22f1bed..5461e6b 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -83,6 +83,12 @@
public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
+ public static final int KM_TAG_ATTESTATION_ID_BRAND = KM_BYTES | 710;
+ public static final int KM_TAG_ATTESTATION_ID_DEVICE = KM_BYTES | 711;
+ public static final int KM_TAG_ATTESTATION_ID_PRODUCT = KM_BYTES | 712;
+ public static final int KM_TAG_ATTESTATION_ID_SERIAL = KM_BYTES | 713;
+ public static final int KM_TAG_ATTESTATION_ID_IMEI = KM_BYTES | 714;
+ public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
@@ -204,6 +210,7 @@
public static final int KM_ERROR_INVALID_MAC_LENGTH = -57;
public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58;
public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59;
+ public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66;
public static final int KM_ERROR_UNIMPLEMENTED = -100;
public static final int KM_ERROR_VERSION_MISMATCH = -101;
public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
@@ -249,6 +256,7 @@
"Caller-provided IV not permitted");
sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH,
"Invalid MAC or authentication tag length");
+ sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids");
sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented");
sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error");
}
diff --git a/core/java/android/service/autofill/AutoFillServiceInfo.java b/core/java/android/service/autofill/AutoFillServiceInfo.java
index ab86580..fd957f1 100644
--- a/core/java/android/service/autofill/AutoFillServiceInfo.java
+++ b/core/java/android/service/autofill/AutoFillServiceInfo.java
@@ -25,15 +25,26 @@
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.RemoteException;
+import android.util.AndroidException;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import com.android.internal.R;
+
import java.io.IOException;
-/** @hide */
+// TODO(b/33197203 , b/33802548): add CTS tests
+/**
+ * {@link ServiceInfo} and meta-data about an {@link AutoFillService}.
+ *
+ * <p>Upon construction, if {@link #getParseError()} is {@code null}, then the service is configured
+ * correctly. Otherwise, {@link #getParseError()} indicates the parsing error.
+ *
+ * @hide
+ */
public final class AutoFillServiceInfo {
static final String TAG = "AutoFillServiceInfo";
@@ -53,67 +64,87 @@
}
@Nullable
- private String mParseError;
+ private final String mParseError;
+ private final ServiceInfo mServiceInfo;
@Nullable
- private ServiceInfo mServiceInfo;
- @Nullable
- private String mSettingsActivity;
+ private final String mSettingsActivity;
public AutoFillServiceInfo(PackageManager pm, ComponentName comp, int userHandle)
throws PackageManager.NameNotFoundException {
this(pm, getServiceInfoOrThrow(comp, userHandle));
}
- public AutoFillServiceInfo(PackageManager pm, ServiceInfo si)
- throws PackageManager.NameNotFoundException{
- if (si == null) {
- mParseError = "Service not available";
- return;
- }
- if (!Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) {
- mParseError = "Service does not require permission "
- + Manifest.permission.BIND_AUTO_FILL;
- return;
- }
-
- XmlResourceParser parser = null;
- try {
- parser = si.loadXmlMetaData(pm, AutoFillService.SERVICE_META_DATA);
- if (parser == null) {
- mParseError = "No " + AutoFillService.SERVICE_META_DATA
- + " meta-data for " + si.packageName;
- return;
- }
-
- Resources res = pm.getResourcesForApplication(si.applicationInfo);
-
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- int type;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && type != XmlPullParser.START_TAG) {
- }
-
- String nodeName = parser.getName();
- if (!"autofill-service".equals(nodeName)) {
- mParseError = "Meta-data does not start with autofill-service tag";
- return;
- }
-
- TypedArray array = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AutoFillService);
- mSettingsActivity = array.getString(
- com.android.internal.R.styleable.AutoFillService_settingsActivity);
- array.recycle();
- } catch (XmlPullParserException | IOException | PackageManager.NameNotFoundException e) {
- mParseError = "Error parsing auto fill service meta-data: " + e;
- Log.w(TAG, "error parsing auto fill service meta-data", e);
- return;
- } finally {
- if (parser != null) parser.close();
- }
+ public AutoFillServiceInfo(PackageManager pm, ServiceInfo si) {
mServiceInfo = si;
+ TypedArray metaDataArray;
+ try {
+ metaDataArray = getMetaDataArray(pm, si);
+ } catch (AndroidException e) {
+ mParseError = e.getMessage();
+ mSettingsActivity = null;
+ Log.w(TAG, mParseError, e);
+ return;
+ }
+
+ mParseError = null;
+ if (metaDataArray != null) {
+ mSettingsActivity =
+ metaDataArray.getString(R.styleable.AutoFillService_settingsActivity);
+ metaDataArray.recycle();
+ } else {
+ mSettingsActivity = null;
+ }
+ }
+
+ /**
+ * Gets the meta-data as a TypedArray, or null if not provided, or throws if invalid.
+ */
+ @Nullable
+ private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si)
+ throws AndroidException {
+ // Check for permissions.
+ if (!Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) {
+ throw new AndroidException(
+ "Service does not require permission " + Manifest.permission.BIND_AUTO_FILL);
+ }
+
+ // Get the AutoFill metadata, if declared.
+ XmlResourceParser parser = si.loadXmlMetaData(pm, AutoFillService.SERVICE_META_DATA);
+ if (parser == null) {
+ return null;
+ }
+
+ // Parse service info and get the <autofill-service> tag as an AttributeSet.
+ AttributeSet attrs;
+ try {
+ // Move the XML parser to the first tag.
+ try {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+ } catch (XmlPullParserException | IOException e) {
+ throw new AndroidException("Error parsing auto fill service meta-data: " + e, e);
+ }
+
+ if (!"autofill-service".equals(parser.getName())) {
+ throw new AndroidException("Meta-data does not start with autofill-service tag");
+ }
+ attrs = Xml.asAttributeSet(parser);
+
+ // Get resources required to read the AttributeSet.
+ Resources res;
+ try {
+ res = pm.getResourcesForApplication(si.applicationInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new AndroidException("Error getting application resources: " + e, e);
+ }
+
+ return res.obtainAttributes(attrs, R.styleable.AutoFillService);
+ } finally {
+ parser.close();
+ }
}
@Nullable
@@ -121,7 +152,6 @@
return mParseError;
}
- @Nullable
public ServiceInfo getServiceInfo() {
return mServiceInfo;
}
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index d7a02a8..cecdbee 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -119,6 +119,24 @@
}
/**
+ * Inform the notification manager about un-snoozing a specific notification.
+ * <p>
+ * This should only be used for notifications snoozed by this listener using
+ * {@link #snoozeNotification(String, String)}. Once un-snoozed, you will get a
+ * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
+ * notification.
+ * @param key The key of the notification to snooze
+ */
+ public final void unsnoozeNotification(String key) {
+ if (!isBound()) return;
+ try {
+ getNotificationInterface().unsnoozeNotificationFromAssistant(mWrapper, key);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ }
+
+ /**
* Creates a notification channel that notifications can be posted to for a given package.
*
* @param pkg The package to create a channel for.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 694837e..417be60 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -183,8 +183,8 @@
public static final int REASON_CHANNEL_BANNED = 17;
/** Notification was snoozed. */
public static final int REASON_SNOOZED = 18;
- /** Notification no longer visible because of user switch */
- public static final int REASON_USER_SWITCH = 19;
+ /** Notification was canceled due to timeout */
+ public static final int REASON_TIMEOUT = 19;
/**
* The full trim of the StatusBarNotification including all its features.
@@ -562,43 +562,6 @@
}
}
- /**
- * Inform the notification manager about snoozing a specific notification.
- * <p>
- * Use this to snooze a notification for an indeterminate time. Upon being informed, the
- * notification manager will actually remove the notification and you will get an
- * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the
- * snoozing period expires, you will get a
- * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
- * notification. Use {@link #unsnoozeNotification(String)} to restore the notification.
- * @param key The key of the notification to snooze
- */
- public final void snoozeNotification(String key) {
- if (!isBound()) return;
- try {
- getNotificationInterface().snoozeNotificationFromListener(mWrapper, key);
- } catch (android.os.RemoteException ex) {
- Log.v(TAG, "Unable to contact notification manager", ex);
- }
- }
-
- /**
- * Inform the notification manager about un-snoozing a specific notification.
- * <p>
- * This should only be used for notifications snoozed by this listener using
- * {@link #snoozeNotification(String)}. Once un-snoozed, you will get a
- * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
- * notification.
- * @param key The key of the notification to snooze
- */
- public final void unsnoozeNotification(String key) {
- if (!isBound()) return;
- try {
- getNotificationInterface().unsnoozeNotificationFromListener(mWrapper, key);
- } catch (android.os.RemoteException ex) {
- Log.v(TAG, "Unable to contact notification manager", ex);
- }
- }
/**
* Inform the notification manager that these notifications have been viewed by the
@@ -663,6 +626,26 @@
}
/**
+ * Like {@link #getActiveNotifications()}, but returns the list of currently snoozed
+ * notifications, for all users this listener has access to.
+ *
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
+ * @return An array of active notifications, sorted in natural order.
+ */
+ public final StatusBarNotification[] getSnoozedNotifications() {
+ try {
+ ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
+ .getSnoozedNotificationsFromListener(mWrapper, TRIM_FULL);
+ return cleanUpNotificationList(parceledList);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ return null;
+ }
+
+ /**
* Request the list of outstanding notifications (that is, those that are visible to the
* current user). Useful when you don't know what's already been posted.
*
@@ -711,36 +694,41 @@
try {
ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
.getActiveNotificationsFromListener(mWrapper, keys, trim);
- List<StatusBarNotification> list = parceledList.getList();
- ArrayList<StatusBarNotification> corruptNotifications = null;
- int N = list.size();
- for (int i = 0; i < N; i++) {
- StatusBarNotification sbn = list.get(i);
- Notification notification = sbn.getNotification();
- try {
- // convert icon metadata to legacy format for older clients
- createLegacyIconExtras(notification);
- // populate remote views for older clients.
- maybePopulateRemoteViews(notification);
- } catch (IllegalArgumentException e) {
- if (corruptNotifications == null) {
- corruptNotifications = new ArrayList<>(N);
- }
- corruptNotifications.add(sbn);
- Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
- sbn.getPackageName());
- }
- }
- if (corruptNotifications != null) {
- list.removeAll(corruptNotifications);
- }
- return list.toArray(new StatusBarNotification[list.size()]);
+ return cleanUpNotificationList(parceledList);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
return null;
}
+ private StatusBarNotification[] cleanUpNotificationList(
+ ParceledListSlice<StatusBarNotification> parceledList) {
+ List<StatusBarNotification> list = parceledList.getList();
+ ArrayList<StatusBarNotification> corruptNotifications = null;
+ int N = list.size();
+ for (int i = 0; i < N; i++) {
+ StatusBarNotification sbn = list.get(i);
+ Notification notification = sbn.getNotification();
+ try {
+ // convert icon metadata to legacy format for older clients
+ createLegacyIconExtras(notification);
+ // populate remote views for older clients.
+ maybePopulateRemoteViews(notification);
+ } catch (IllegalArgumentException e) {
+ if (corruptNotifications == null) {
+ corruptNotifications = new ArrayList<>(N);
+ }
+ corruptNotifications.add(sbn);
+ Log.w(TAG, "get(Active/Snoozed)Notifications: can't rebuild notification from " +
+ sbn.getPackageName());
+ }
+ }
+ if (corruptNotifications != null) {
+ list.removeAll(corruptNotifications);
+ }
+ return list.toArray(new StatusBarNotification[list.size()]);
+ }
+
/**
* Gets the set of hints representing current state.
*
@@ -1166,11 +1154,12 @@
// System specified group key.
private String mOverrideGroupKey;
// Notification assistant channel override.
- private NotificationChannel mOverrideChannel;
+ private NotificationChannel mChannel;
// Notification assistant people override.
private ArrayList<String> mOverridePeople;
// Notification assistant snooze criteria.
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ private boolean mShowBadge;
public Ranking() {}
@@ -1200,7 +1189,7 @@
}
/**
- * Returns the user specificed visibility for the package that posted
+ * Returns the user specified visibility for the package that posted
* this notification, or
* {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
* no such preference has been expressed.
@@ -1233,7 +1222,7 @@
* Returns the importance of the notification, which dictates its
* modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
*
- * @return the rank of the notification
+ * @return the importance of the notification
*/
public @NotificationManager.Importance int getImportance() {
return mImportance;
@@ -1258,12 +1247,11 @@
}
/**
- * If the {@link NotificationAssistantService} has overridden the channel this notification
- * was posted to, then this will not match the channel provided by the posting application
- * and this should be used to determine the interruptiveness of the notification instead.
+ * Returns the notification channel this notification was posted to, which dictates
+ * notification behavior and presentation.
*/
public NotificationChannel getChannel() {
- return mOverrideChannel;
+ return mChannel;
}
/**
@@ -1283,11 +1271,20 @@
return mSnoozeCriteria;
}
+ /**
+ * Returns whether this notification can be displayed as a badge.
+ *
+ * @return true if the notification can be displayed as a badge, false otherwise.
+ */
+ public boolean canShowBadge() {
+ return mShowBadge;
+ }
+
private void populate(String key, int rank, boolean matchesInterruptionFilter,
int visibilityOverride, int suppressedVisualEffects, int importance,
CharSequence explanation, String overrideGroupKey,
- NotificationChannel overrideChannel, ArrayList<String> overridePeople,
- ArrayList<SnoozeCriterion> snoozeCriteria) {
+ NotificationChannel channel, ArrayList<String> overridePeople,
+ ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1297,9 +1294,10 @@
mImportance = importance;
mImportanceExplanation = explanation;
mOverrideGroupKey = overrideGroupKey;
- mOverrideChannel = overrideChannel;
+ mChannel = channel;
mOverridePeople = overridePeople;
mSnoozeCriteria = snoozeCriteria;
+ mShowBadge = showBadge;
}
/**
@@ -1343,9 +1341,10 @@
private ArrayMap<String, Integer> mImportance;
private ArrayMap<String, String> mImportanceExplanation;
private ArrayMap<String, String> mOverrideGroupKeys;
- private ArrayMap<String, NotificationChannel> mOverrideChannels;
+ private ArrayMap<String, NotificationChannel> mChannels;
private ArrayMap<String, ArrayList<String>> mOverridePeople;
private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
+ private ArrayMap<String, Boolean> mShowBadge;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
@@ -1373,7 +1372,8 @@
outRanking.populate(key, rank, !isIntercepted(key),
getVisibilityOverride(key), getSuppressedVisualEffects(key),
getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
- getOverrideChannel(key), getOverridePeople(key), getSnoozeCriteria(key));
+ getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
+ getShowBadge(key));
return rank >= 0;
}
@@ -1453,13 +1453,13 @@
return mOverrideGroupKeys.get(key);
}
- private NotificationChannel getOverrideChannel(String key) {
+ private NotificationChannel getChannel(String key) {
synchronized (this) {
- if (mOverrideChannels == null) {
- buildOverrideChannelsLocked();
+ if (mChannels == null) {
+ buildChannelsLocked();
}
}
- return mOverrideChannels.get(key);
+ return mChannels.get(key);
}
private ArrayList<String> getOverridePeople(String key) {
@@ -1480,6 +1480,16 @@
return mSnoozeCriteria.get(key);
}
+ private boolean getShowBadge(String key) {
+ synchronized (this) {
+ if (mShowBadge == null) {
+ buildShowBadgeLocked();
+ }
+ }
+ Boolean showBadge = mShowBadge.get(key);
+ return showBadge == null ? false : showBadge.booleanValue();
+ }
+
// Locked by 'this'
private void buildRanksLocked() {
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1544,11 +1554,11 @@
}
// Locked by 'this'
- private void buildOverrideChannelsLocked() {
- Bundle overrideChannels = mRankingUpdate.getOverrideChannels();
- mOverrideChannels = new ArrayMap<>(overrideChannels.size());
- for (String key : overrideChannels.keySet()) {
- mOverrideChannels.put(key, overrideChannels.getParcelable(key));
+ private void buildChannelsLocked() {
+ Bundle channels = mRankingUpdate.getChannels();
+ mChannels = new ArrayMap<>(channels.size());
+ for (String key : channels.keySet()) {
+ mChannels.put(key, channels.getParcelable(key));
}
}
@@ -1570,6 +1580,15 @@
}
}
+ // Locked by 'this'
+ private void buildShowBadgeLocked() {
+ Bundle showBadge = mRankingUpdate.getShowBadge();
+ mShowBadge = new ArrayMap<>(showBadge.size());
+ for (String key : showBadge.keySet()) {
+ mShowBadge.put(key, showBadge.getBoolean(key));
+ }
+ }
+
// ----------- Parcelable
@Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index a2cdeff..326b212 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -31,14 +31,16 @@
private final int[] mImportance;
private final Bundle mImportanceExplanation;
private final Bundle mOverrideGroupKeys;
- private final Bundle mOverrideChannels;
+ private final Bundle mChannels;
private final Bundle mOverridePeople;
private final Bundle mSnoozeCriteria;
+ private final Bundle mShowBadge;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
Bundle visibilityOverrides, Bundle suppressedVisualEffects,
int[] importance, Bundle explanation, Bundle overrideGroupKeys,
- Bundle overrideChannels, Bundle overridePeople, Bundle snoozeCriteria) {
+ Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
+ Bundle showBadge) {
mKeys = keys;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
@@ -46,9 +48,10 @@
mImportance = importance;
mImportanceExplanation = explanation;
mOverrideGroupKeys = overrideGroupKeys;
- mOverrideChannels = overrideChannels;
+ mChannels = channels;
mOverridePeople = overridePeople;
mSnoozeCriteria = snoozeCriteria;
+ mShowBadge = showBadge;
}
public NotificationRankingUpdate(Parcel in) {
@@ -60,9 +63,10 @@
in.readIntArray(mImportance);
mImportanceExplanation = in.readBundle();
mOverrideGroupKeys = in.readBundle();
- mOverrideChannels = in.readBundle();
+ mChannels = in.readBundle();
mOverridePeople = in.readBundle();
mSnoozeCriteria = in.readBundle();
+ mShowBadge = in.readBundle();
}
@Override
@@ -79,9 +83,10 @@
out.writeIntArray(mImportance);
out.writeBundle(mImportanceExplanation);
out.writeBundle(mOverrideGroupKeys);
- out.writeBundle(mOverrideChannels);
+ out.writeBundle(mChannels);
out.writeBundle(mOverridePeople);
out.writeBundle(mSnoozeCriteria);
+ out.writeBundle(mShowBadge);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -123,8 +128,8 @@
return mOverrideGroupKeys;
}
- public Bundle getOverrideChannels() {
- return mOverrideChannels;
+ public Bundle getChannels() {
+ return mChannels;
}
public Bundle getOverridePeople() {
@@ -134,4 +139,8 @@
public Bundle getSnoozeCriteria() {
return mSnoozeCriteria;
}
+
+ public Bundle getShowBadge() {
+ return mShowBadge;
+ }
}
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 6276af3..85baf4e 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -43,21 +43,18 @@
private final Notification notification;
private final UserHandle user;
private final long postTime;
- private final NotificationChannel channel;
private Context mContext; // used for inflation & icon expansion
/** @hide */
- public StatusBarNotification(String pkg, String opPkg, NotificationChannel channel, int id,
+ public StatusBarNotification(String pkg, String opPkg, int id,
String tag, int uid, int initialPid, Notification notification, UserHandle user,
String overrideGroupKey, long postTime) {
if (pkg == null) throw new NullPointerException();
if (notification == null) throw new NullPointerException();
- if (channel == null) throw new IllegalArgumentException();
this.pkg = pkg;
this.opPkg = opPkg;
- this.channel = channel;
this.id = id;
this.tag = tag;
this.uid = uid;
@@ -88,7 +85,6 @@
this.postTime = postTime;
this.key = key();
this.groupKey = groupKey();
- this.channel = null;
}
public StatusBarNotification(Parcel in) {
@@ -112,7 +108,6 @@
}
this.key = key();
this.groupKey = groupKey();
- this.channel = NotificationChannel.CREATOR.createFromParcel(in);
}
private String key() {
@@ -182,7 +177,6 @@
} else {
out.writeInt(0);
}
- this.channel.writeToParcel(out, flags);
}
public int describeContents() {
@@ -209,14 +203,14 @@
public StatusBarNotification cloneLight() {
final Notification no = new Notification();
this.notification.cloneInto(no, false); // light copy
- return new StatusBarNotification(this.pkg, this.opPkg, this.channel,
+ return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
no, this.user, this.overrideGroupKey, this.postTime);
}
@Override
public StatusBarNotification clone() {
- return new StatusBarNotification(this.pkg, this.opPkg, this.channel,
+ return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
this.notification.clone(), this.user, this.overrideGroupKey, this.postTime);
}
@@ -336,13 +330,6 @@
}
/**
- * Returns the channel this notification was posted to.
- */
- public NotificationChannel getNotificationChannel() {
- return channel;
- }
-
- /**
* @hide
*/
public Context getPackageContext(Context context) {
diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java
index 9920ea1..be5851c 100644
--- a/core/java/android/speech/tts/BlockingAudioTrack.java
+++ b/core/java/android/speech/tts/BlockingAudioTrack.java
@@ -164,7 +164,7 @@
// all data from the audioTrack has been sent to the mixer, so
// it's safe to release at this point.
if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]");
- synchronized(mAudioTrackLock) {
+ synchronized (mAudioTrackLock) {
mAudioTrack = null;
}
track.release();
@@ -340,4 +340,25 @@
return value < min ? min : (value < max ? value : max);
}
+ /**
+ * @see
+ * AudioTrack#setPlaybackPositionUpdateListener(AudioTrack.OnPlaybackPositionUpdateListener).
+ */
+ public void setPlaybackPositionUpdateListener(
+ AudioTrack.OnPlaybackPositionUpdateListener listener) {
+ synchronized (mAudioTrackLock) {
+ if (mAudioTrack != null) {
+ mAudioTrack.setPlaybackPositionUpdateListener(listener);
+ }
+ }
+ }
+
+ /** @see AudioTrack#setNotificationMarkerPosition(int). */
+ public void setNotificationMarkerPosition(int frames) {
+ synchronized (mAudioTrackLock) {
+ if (mAudioTrack != null) {
+ mAudioTrack.setNotificationMarkerPosition(frames);
+ }
+ }
+ }
}
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index 4e3acf6..edb6e48 100644
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -83,4 +83,19 @@
* callback.
*/
void onAudioAvailable(String utteranceId, in byte[] audio);
+
+ /**
+ * Tells the client that the engine is about to speak the specified range of the utterance.
+ *
+ * <p>
+ * Only called if the engine supplies timing information by calling
+ * {@link SynthesisCallback#rangeStart(int, int, int)} and only when the request is played back
+ * by the service, not when using {@link android.speech.tts.TextToSpeech#synthesizeToFile}.
+ * </p>
+ *
+ * @param utteranceId Unique id identifying the synthesis request.
+ * @param start The start character index of the range in the utterance text.
+ * @param end The end character index of the range (exclusive) in the utterance text.
+ */
+ void onUtteranceRangeStart(String utteranceId, int start, int end);
}
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index 778aa86..9e24b09 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -271,4 +271,12 @@
mStatusCode = errorCode;
}
}
+
+ public void rangeStart(int markerInFrames, int start, int end) {
+ if (mItem == null) {
+ Log.e(TAG, "mItem is null");
+ return;
+ }
+ mItem.rangeStart(markerInFrames, start, end);
+ }
}
diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java
index 2fd8499..8b74ed7 100644
--- a/core/java/android/speech/tts/SynthesisCallback.java
+++ b/core/java/android/speech/tts/SynthesisCallback.java
@@ -142,4 +142,26 @@
* <p>Useful for checking if a fallback from network request is possible.
*/
boolean hasFinished();
+
+ /**
+ * The service may call this method to provide timing information about the spoken text.
+ *
+ * <p>Calling this method means that at the given audio frame, the given range of the input is
+ * about to be spoken. If this method is called the client will receive a callback on the
+ * listener ({@link UtteranceProgressListener#onUtteranceRangeStart}) at the moment that frame
+ * has been reached by the playback head.
+ *
+ * <p>The markerInFrames is a frame index into the audio for this synthesis request, i.e. into
+ * the concatenation of the audio bytes sent to audioAvailable for this synthesis request. The
+ * definition of a frame depends on the format given by {@link #start}. See {@link AudioFormat}
+ * for more information.
+ *
+ * <p>This method should only be called on the synthesis thread, while in {@link
+ * TextToSpeechService#onSynthesizeText}.
+ *
+ * @param markerInFrames The position in frames in the audio where this range is spoken.
+ * @param start The start index of the range in the input text.
+ * @param end The end index (exclusive) of the range in the input text.
+ */
+ default void rangeStart(int markerInFrames, int start, int end) {}
}
diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
index 7423933..cb5f220 100644
--- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
@@ -17,18 +17,21 @@
import android.speech.tts.TextToSpeechService.AudioOutputParams;
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
+import android.media.AudioTrack;
import android.util.Log;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.ConcurrentLinkedQueue;
/**
- * Manages the playback of a list of byte arrays representing audio data
- * that are queued by the engine to an audio track.
+ * Manages the playback of a list of byte arrays representing audio data that are queued by the
+ * engine to an audio track.
*/
-final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
+final class SynthesisPlaybackQueueItem extends PlaybackQueueItem
+ implements AudioTrack.OnPlaybackPositionUpdateListener {
private static final String TAG = "TTS.SynthQueueItem";
private static final boolean DBG = false;
@@ -63,6 +66,10 @@
private final BlockingAudioTrack mAudioTrack;
private final AbstractEventLogger mLogger;
+ // Stores a queue of markers. When the marker in front is reached the client is informed and we
+ // wait for the next one.
+ private ConcurrentLinkedQueue<ProgressMarker> markerList = new ConcurrentLinkedQueue<>();
+
SynthesisPlaybackQueueItem(AudioOutputParams audioParams, int sampleRate,
int audioFormat, int channelCount, UtteranceProgressDispatcher dispatcher,
Object callerIdentity, AbstractEventLogger logger) {
@@ -89,6 +96,8 @@
return;
}
+ mAudioTrack.setPlaybackPositionUpdateListener(this);
+
try {
byte[] buffer = null;
@@ -172,6 +181,55 @@
}
}
+ /** Convenience class for passing around TTS markers. */
+ private class ProgressMarker {
+ // The index in frames of this marker.
+ public final int frames;
+ // The start index in the text of the utterance.
+ public final int start;
+ // The end index (exclusive) in the text of the utterance.
+ public final int end;
+
+ public ProgressMarker(int frames, int start, int end) {
+ this.frames = frames;
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ /** Set a callback for the first marker in the queue. */
+ void updateMarker() {
+ ProgressMarker marker = markerList.peek();
+ if (marker != null) {
+ // Zero is used to disable the marker. The documentation recommends to use a non-zero
+ // position near zero such as 1.
+ int markerInFrames = marker.frames == 0 ? 1 : marker.frames;
+ mAudioTrack.setNotificationMarkerPosition(markerInFrames);
+ }
+ }
+
+ /** Informs us that at markerInFrames, the range between start and end is about to be spoken. */
+ void rangeStart(int markerInFrames, int start, int end) {
+ markerList.add(new ProgressMarker(markerInFrames, start, end));
+ updateMarker();
+ }
+
+ @Override
+ public void onMarkerReached(AudioTrack track) {
+ ProgressMarker marker = markerList.poll();
+ if (marker == null) {
+ Log.e(TAG, "onMarkerReached reached called but no marker in queue");
+ return;
+ }
+ // Inform the client.
+ getDispatcher().dispatchOnUtteranceRangeStart(marker.start, marker.end);
+ // Listen for the next marker.
+ // It's ok if this marker is in the past, in that case onMarkerReached will be called again.
+ updateMarker();
+ }
+
+ @Override
+ public void onPeriodicNotification(AudioTrack track) {}
void put(byte[] buffer) throws InterruptedException {
try {
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 24cad95..9a157b7 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -2103,55 +2103,69 @@
private boolean mEstablished;
- private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
- public void onStop(String utteranceId, boolean isStarted) throws RemoteException {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onStop(utteranceId, isStarted);
- }
- };
+ private final ITextToSpeechCallback.Stub mCallback =
+ new ITextToSpeechCallback.Stub() {
+ public void onStop(String utteranceId, boolean isStarted)
+ throws RemoteException {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onStop(utteranceId, isStarted);
+ }
+ };
- @Override
- public void onSuccess(String utteranceId) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onDone(utteranceId);
- }
- }
+ @Override
+ public void onSuccess(String utteranceId) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onDone(utteranceId);
+ }
+ }
- @Override
- public void onError(String utteranceId, int errorCode) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onError(utteranceId);
- }
- }
+ @Override
+ public void onError(String utteranceId, int errorCode) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onError(utteranceId);
+ }
+ }
- @Override
- public void onStart(String utteranceId) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onStart(utteranceId);
- }
- }
+ @Override
+ public void onStart(String utteranceId) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onStart(utteranceId);
+ }
+ }
- @Override
- public void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat,
- int channelCount) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
- }
- }
+ @Override
+ public void onBeginSynthesis(
+ String utteranceId,
+ int sampleRateInHz,
+ int audioFormat,
+ int channelCount) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onBeginSynthesis(
+ utteranceId, sampleRateInHz, audioFormat, channelCount);
+ }
+ }
- @Override
- public void onAudioAvailable(String utteranceId, byte[] audio) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onAudioAvailable(utteranceId, audio);
- }
- }
- };
+ @Override
+ public void onAudioAvailable(String utteranceId, byte[] audio) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onAudioAvailable(utteranceId, audio);
+ }
+ }
+
+ @Override
+ public void onUtteranceRangeStart(String utteranceId, int start, int end) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onUtteranceRangeStart(utteranceId, start, end);
+ }
+ }
+ };
private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
private final ComponentName mName;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 55da52b..80d3c8a 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -663,6 +663,8 @@
void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);
void dispatchOnAudioAvailable(byte[] audio);
+
+ public void dispatchOnUtteranceRangeStart(int start, int end);
}
/** Set of parameters affecting audio output. */
@@ -882,6 +884,15 @@
}
}
+ @Override
+ public void dispatchOnUtteranceRangeStart(int start, int end) {
+ final String utteranceId = getUtteranceId();
+ if (utteranceId != null) {
+ mCallbacks.dispatchOnUtteranceRangeStart(
+ getCallerIdentity(), utteranceId, start, end);
+ }
+ }
+
abstract public String getUtteranceId();
String getStringParam(Bundle params, String key, String defaultValue) {
@@ -1559,6 +1570,17 @@
}
}
+ public void dispatchOnUtteranceRangeStart(
+ Object callerIdentity, String utteranceId, int start, int end) {
+ ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
+ if (cb == null) return;
+ try {
+ cb.onUtteranceRangeStart(utteranceId, start, end);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback dispatchOnUtteranceRangeStart(String, int, int) failed: " + e);
+ }
+ }
+
@Override
public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
IBinder caller = (IBinder) cookie;
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index 72a5228..0ee3769 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -122,8 +122,24 @@
}
/**
- * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
- * progress listener.
+ * This is called when the TTS service is about to speak the specified range of the utterance
+ * with the given utteranceId.
+ *
+ * <p>This method is called when the audio is expected to start playing on the speaker. Note
+ * that this is different from {@link #onAudioAvailable} which is called as soon as the audio is
+ * generated.
+ *
+ * <p>Only called if the engine supplies timing information by calling {@link
+ * SynthesisCallback#rangeStart(int, int, int)}.
+ *
+ * @param utteranceId Unique id identifying the synthesis request.
+ * @param start The start index of the range in the utterance text.
+ * @param end The end index of the range (exclusive) in the utterance text.
+ */
+ public void onUtteranceRangeStart(String utteranceId, int start, int end) {}
+
+ /**
+ * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new progress listener.
*
* @hide
*/
diff --git a/core/java/android/text/FontConfig.aidl b/core/java/android/text/FontConfig.aidl
new file mode 100644
index 0000000..17a5ca2
--- /dev/null
+++ b/core/java/android/text/FontConfig.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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.text;
+
+/** @hide */
+parcelable FontConfig;
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
new file mode 100644
index 0000000..df694ff
--- /dev/null
+++ b/core/java/android/text/FontConfig.java
@@ -0,0 +1,466 @@
+/*
+ * 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.text;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Font configuration descriptions for System fonts.
+ */
+public final class FontConfig implements Parcelable {
+ private final List<Family> mFamilies = new ArrayList<>();
+ private final List<Alias> mAliases = new ArrayList<>();
+
+ public FontConfig() {
+ }
+
+ public FontConfig(FontConfig config) {
+ for (int i = 0; i < config.mFamilies.size(); i++) {
+ mFamilies.add(new Family(config.mFamilies.get(i)));
+ }
+ mAliases.addAll(config.mAliases);
+ }
+
+ /**
+ * Returns the ordered list of families included in the system fonts.
+ */
+ public List<Family> getFamilies() {
+ return mFamilies;
+ }
+
+ /**
+ * Returns the list of aliases defined for the font families in the system fonts.
+ */
+ public List<Alias> getAliases() {
+ return mAliases;
+ }
+
+ /**
+ * @hide
+ */
+ public FontConfig(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeInt(mFamilies.size());
+ for (int i = 0; i < mFamilies.size(); i++) {
+ mFamilies.get(i).writeToParcel(out, flag);
+ }
+ out.writeInt(mAliases.size());
+ for (int i = 0; i < mAliases.size(); i++) {
+ mAliases.get(i).writeToParcel(out, flag);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void readFromParcel(Parcel in) {
+ int size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ mFamilies.add(new Family(in));
+ }
+ size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ mAliases.add(new Alias(in));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<FontConfig> CREATOR = new Parcelable.Creator() {
+ public FontConfig createFromParcel(Parcel in) {
+ return new FontConfig(in);
+ }
+ public FontConfig[] newArray(int size) {
+ return new FontConfig[size];
+ }
+ };
+
+ /**
+ * Class that holds information about a Font axis.
+ */
+ public static final class Axis implements Parcelable {
+ private final int mTag;
+ private final float mStyleValue;
+
+ public Axis(int tag, float styleValue) {
+ this.mTag = tag;
+ this.mStyleValue = styleValue;
+ }
+
+ /**
+ * Returns the variable font axis tag associated to this axis.
+ */
+ public int getTag() {
+ return mTag;
+ }
+
+ /**
+ * Returns the style value associated to the given axis for this font.
+ */
+ public float getStyleValue() {
+ return mStyleValue;
+ }
+
+ /**
+ * @hide
+ */
+ public Axis(Parcel in) {
+ mTag = in.readInt();
+ mStyleValue = in.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeInt(mTag);
+ out.writeFloat(mStyleValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Axis> CREATOR = new Creator<Axis>() {
+ @Override
+ public Axis createFromParcel(Parcel in) {
+ return new Axis(in);
+ }
+
+ @Override
+ public Axis[] newArray(int size) {
+ return new Axis[size];
+ }
+ };
+ }
+
+ /**
+ * Class that holds information about a Font.
+ */
+ public static final class Font implements Parcelable {
+ private final String mFontName;
+ private final int mTtcIndex;
+ private final List<Axis> mAxes;
+ private final int mWeight;
+ private final boolean mIsItalic;
+ private ParcelFileDescriptor mFd;
+
+ public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) {
+ mFontName = fontName;
+ mTtcIndex = ttcIndex;
+ mAxes = axes;
+ mWeight = weight;
+ mIsItalic = isItalic;
+ mFd = null;
+ }
+
+ public Font(Font origin) {
+ mFontName = origin.mFontName;
+ mTtcIndex = origin.mTtcIndex;
+ mAxes = new ArrayList<>(origin.mAxes);
+ mWeight = origin.mWeight;
+ mIsItalic = origin.mIsItalic;
+ if (origin.mFd != null) {
+ try {
+ mFd = origin.mFd.dup();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Returns the name associated by the system to this font.
+ */
+ public String getFontName() {
+ return mFontName;
+ }
+
+ /**
+ * Returns the index to be used to access this font when accessing a TTC file.
+ */
+ public int getTtcIndex() {
+ return mTtcIndex;
+ }
+
+ /**
+ * Returns the list of axes associated to this font.
+ */
+ public List<Axis> getAxes() {
+ return mAxes;
+ }
+
+ /**
+ * Returns the weight value for this font.
+ */
+ public int getWeight() {
+ return mWeight;
+ }
+
+ /**
+ * Returns whether this font is italic.
+ */
+ public boolean isItalic() {
+ return mIsItalic;
+ }
+
+ /**
+ * Returns a file descriptor to access the specified font. This should be closed after use.
+ */
+ public ParcelFileDescriptor getFd() {
+ return mFd;
+ }
+
+ /**
+ * @hide
+ */
+ public void setFd(ParcelFileDescriptor fd) {
+ mFd = fd;
+ }
+
+ /**
+ * @hide
+ */
+ public Font(Parcel in) {
+ mFontName = in.readString();
+ mTtcIndex = in.readInt();
+ final int numAxes = in.readInt();
+ mAxes = new ArrayList<>();
+ for (int i = 0; i < numAxes; i++) {
+ mAxes.add(new Axis(in));
+ }
+ mWeight = in.readInt();
+ mIsItalic = in.readInt() == 1;
+ if (in.readInt() == 1) { /* has FD */
+ mFd = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ } else {
+ mFd = null;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeString(mFontName);
+ out.writeInt(mTtcIndex);
+ out.writeInt(mAxes.size());
+ for (int i = 0; i < mAxes.size(); i++) {
+ mAxes.get(i).writeToParcel(out, flag);
+ }
+ out.writeInt(mWeight);
+ out.writeInt(mIsItalic ? 1 : 0);
+ out.writeInt(mFd == null ? 0 : 1);
+ if (mFd != null) {
+ mFd.writeToParcel(out, flag);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Font> CREATOR = new Creator<Font>() {
+ @Override
+ public Font createFromParcel(Parcel in) {
+ return new Font(in);
+ }
+
+ @Override
+ public Font[] newArray(int size) {
+ return new Font[size];
+ }
+ };
+ }
+
+ /**
+ * Class that holds information about a Font alias.
+ */
+ public static final class Alias implements Parcelable {
+ private final String mName;
+ private final String mToName;
+ private final int mWeight;
+
+ public Alias(String name, String toName, int weight) {
+ this.mName = name;
+ this.mToName = toName;
+ this.mWeight = weight;
+ }
+
+ /**
+ * Returns the new name for the alias.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the existing name to which this alias points to.
+ */
+ public String getToName() {
+ return mToName;
+ }
+
+ /**
+ * Returns the weight associated with this alias.
+ */
+ public int getWeight() {
+ return mWeight;
+ }
+
+ /**
+ * @hide
+ */
+ public Alias(Parcel in) {
+ mName = in.readString();
+ mToName = in.readString();
+ mWeight = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeString(mName);
+ out.writeString(mToName);
+ out.writeInt(mWeight);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Alias> CREATOR = new Creator<Alias>() {
+ @Override
+ public Alias createFromParcel(Parcel in) {
+ return new Alias(in);
+ }
+
+ @Override
+ public Alias[] newArray(int size) {
+ return new Alias[size];
+ }
+ };
+ }
+
+ /**
+ * Class that holds information about a Font family.
+ */
+ public static final class Family implements Parcelable {
+ private final String mName;
+ private final List<Font> mFonts;
+ private final String mLanguage;
+ private final String mVariant;
+
+ public Family(String name, List<Font> fonts, String language, String variant) {
+ this.mName = name;
+ this.mFonts = fonts;
+ this.mLanguage = language;
+ this.mVariant = variant;
+ }
+
+ public Family(Family origin) {
+ this.mName = origin.mName;
+ this.mLanguage = origin.mLanguage;
+ this.mVariant = origin.mVariant;
+ this.mFonts = new ArrayList<>();
+ for (int i = 0; i < origin.mFonts.size(); i++) {
+ mFonts.add(new Font(origin.mFonts.get(i)));
+ }
+ }
+
+ /**
+ * Returns the name given by the system to this font family.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the list of fonts included in this family.
+ */
+ public List<Font> getFonts() {
+ return mFonts;
+ }
+
+ /**
+ * Returns the language for this family. May be null.
+ */
+ public String getLanguage() {
+ return mLanguage;
+ }
+
+ /**
+ * Returns the font variant for this family, e.g. "elegant" or "compact". May be null.
+ */
+ public String getVariant() {
+ return mVariant;
+ }
+
+ /**
+ * @hide
+ */
+ public Family(Parcel in) {
+ mName = in.readString();
+ final int size = in.readInt();
+ mFonts = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ mFonts.add(new Font(in));
+ }
+ mLanguage = in.readString();
+ mVariant = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeString(mName);
+ out.writeInt(mFonts.size());
+ for (int i = 0; i < mFonts.size(); i++) {
+ mFonts.get(i).writeToParcel(out, flag);
+ }
+ out.writeString(mLanguage);
+ out.writeString(mVariant);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Family> CREATOR = new Creator<Family>() {
+ @Override
+ public Family createFromParcel(Parcel in) {
+ return new Family(in);
+ }
+
+ @Override
+ public Family[] newArray(int size) {
+ return new Family[size];
+ }
+ };
+ }
+}
diff --git a/core/java/android/text/FontManager.java b/core/java/android/text/FontManager.java
new file mode 100644
index 0000000..b61cbf3
--- /dev/null
+++ b/core/java/android/text/FontManager.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.os.RemoteException;
+
+import com.android.internal.font.IFontManager;
+
+/**
+ * Interact with the Font service.
+ */
+public final class FontManager {
+ private static final String TAG = "FontManager";
+
+ private final IFontManager mService;
+
+ /**
+ * @hide
+ */
+ public FontManager(IFontManager service) {
+ mService = service;
+ }
+
+ /**
+ * Retrieve the system fonts data. This loads the fonts.xml data if needed and loads all system
+ * fonts in to memory, providing file descriptors for them.
+ */
+ public FontConfig getSystemFonts() {
+ try {
+ return mService.getSystemFonts();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/text/LangId.java b/core/java/android/text/LangId.java
new file mode 100644
index 0000000..ed6e909
--- /dev/null
+++ b/core/java/android/text/LangId.java
@@ -0,0 +1,60 @@
+/*
+ * 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.text;
+
+/**
+ * Java wrapper for LangId native library interface.
+ * This class is used to detect languages in text.
+ * @hide
+ */
+public final class LangId {
+ // TODO: Move this to android.view.textclassifier and make it package-private.
+ // We'll have to update the native library code to do this.
+
+ static {
+ System.loadLibrary("smart-selection_jni");
+ }
+
+ private final long mModelPtr;
+
+ /**
+ * Creates a new instance of LangId predictor, using the provided model image.
+ */
+ public LangId(int fd) {
+ mModelPtr = nativeNew(fd);
+ }
+
+ /**
+ * Detects the language for given text.
+ */
+ public String findLanguage(String text) {
+ return nativeFindLanguage(mModelPtr, text);
+ }
+
+ /**
+ * Frees up the allocated memory.
+ */
+ public void close() {
+ nativeClose(mModelPtr);
+ }
+
+ private static native long nativeNew(int fd);
+
+ private static native String nativeFindLanguage(long context, String text);
+
+ private static native void nativeClose(long context);
+}
+
diff --git a/core/java/android/text/SmartSelection.java b/core/java/android/text/SmartSelection.java
new file mode 100644
index 0000000..97ef514
--- /dev/null
+++ b/core/java/android/text/SmartSelection.java
@@ -0,0 +1,84 @@
+/*
+ * 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.text;
+
+/**
+ * Java wrapper for SmartSelection native library interface.
+ * This library is used for detecting entities in text.
+ * @hide
+ */
+public final class SmartSelection {
+ // TODO: Move this to android.view.textclassifier and make it package-private.
+ // We'll have to update the native library code to do this.
+
+ static {
+ System.loadLibrary("smart-selection_jni");
+ }
+
+ private final long mCtx;
+
+ /**
+ * Creates a new instance of SmartSelect predictor, using the provided model image,
+ * given as a file descriptor.
+ */
+ public SmartSelection(int fd) {
+ mCtx = nativeNew(fd);
+ }
+
+ /**
+ * Given a string context and current selection, computes the SmartSelection suggestion.
+ *
+ * The begin and end are character indices into the context UTF8 string. selectionBegin is the
+ * character index where the selection begins, and selectionEnd is the index of one character
+ * past the selection span.
+ *
+ * The return value is an array of two ints: suggested selection beginning and end, with the
+ * same semantics as the input selectionBeginning and selectionEnd.
+ */
+ public int[] suggest(String context, int selectionBegin, int selectionEnd) {
+ return nativeSuggest(mCtx, context, selectionBegin, selectionEnd);
+ }
+
+ /**
+ * Given a string context and current selection, classifies the type of the selected text.
+ *
+ * The begin and end params are character indices in the context string.
+ *
+ * Returns the type of the selection, e.g. "email", "address", "phone".
+ */
+ public String classifyText(String context, int selectionBegin, int selectionEnd) {
+ return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd);
+ }
+
+ /**
+ * Frees up the allocated memory.
+ */
+ public void close() {
+ nativeClose(mCtx);
+ }
+
+ private static native long nativeNew(int fd);
+
+ private static native int[] nativeSuggest(
+ long context, String text, int selectionBegin, int selectionEnd);
+
+ private static native String nativeClassifyText(
+ long context, String text, int selectionBegin, int selectionEnd);
+
+ private static native void nativeClose(long context);
+}
+
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 186d96b..5f01f7b 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -21,6 +21,7 @@
import android.graphics.Paint;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -73,8 +74,6 @@
mSpanFlags = EmptyArray.INT;
mSpanMax = EmptyArray.INT;
mSpanOrder = EmptyArray.INT;
- mPrioSortBuffer = EmptyArray.INT;
- mOrderSortBuffer = EmptyArray.INT;
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
@@ -856,14 +855,14 @@
* @param queryStart Start index.
* @param queryEnd End index.
* @param kind Class type to search for.
- * @param sort If true the results are sorted by the insertion order.
+ * @param sortByInsertionOrder If true the results are sorted by the insertion order.
* @param <T>
* @return Array of the spans. Empty array if no results are found.
*
* @hide
*/
public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind,
- boolean sort) {
+ boolean sortByInsertionOrder) {
if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
int count = countSpans(queryStart, queryEnd, kind, treeRoot());
@@ -873,13 +872,15 @@
// Safe conversion, but requires a suppressWarning
T[] ret = (T[]) Array.newInstance(kind, count);
- if (sort) {
- mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count);
- mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count);
+ final int[] prioSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
+ final int[] orderSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
+ getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, prioSortBuffer,
+ orderSortBuffer, 0, sortByInsertionOrder);
+ if (sortByInsertionOrder) {
+ sort(ret, prioSortBuffer, orderSortBuffer);
+ recycle(prioSortBuffer);
+ recycle(orderSortBuffer);
}
- getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer,
- mOrderSortBuffer, 0, sort);
- if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer);
return ret;
}
@@ -992,15 +993,63 @@
}
/**
+ * Obtain a temporary sort buffer.
+ *
+ * @param elementCount the size of the int[] to be returned
+ * @return an int[] with elementCount length
+ */
+ private static int[] obtain(final int elementCount) {
+ int[] result = null;
+ synchronized (sCachedIntBuffer) {
+ // try finding a tmp buffer with length of at least elementCount
+ // if not get the first available one
+ int candidateIndex = -1;
+ for (int i = sCachedIntBuffer.length - 1; i >= 0; i--) {
+ if (sCachedIntBuffer[i] != null) {
+ if (sCachedIntBuffer[i].length >= elementCount) {
+ candidateIndex = i;
+ break;
+ } else if (candidateIndex == -1) {
+ candidateIndex = i;
+ }
+ }
+ }
+
+ if (candidateIndex != -1) {
+ result = sCachedIntBuffer[candidateIndex];
+ sCachedIntBuffer[candidateIndex] = null;
+ }
+ }
+ result = checkSortBuffer(result, elementCount);
+ return result;
+ }
+
+ /**
+ * Recycle sort buffer.
+ *
+ * @param buffer buffer to be recycled
+ */
+ private static void recycle(int[] buffer) {
+ synchronized (sCachedIntBuffer) {
+ for (int i = 0; i < sCachedIntBuffer.length; i++) {
+ if (sCachedIntBuffer[i] == null || buffer.length > sCachedIntBuffer[i].length) {
+ sCachedIntBuffer[i] = buffer;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
* Check the size of the buffer and grow if required.
*
- * @param buffer Buffer to be checked.
- * @param size Required size.
+ * @param buffer buffer to be checked.
+ * @param size required size.
* @return Same buffer instance if the current size is greater than required size. Otherwise a
* new instance is created and returned.
*/
- private final int[] checkSortBuffer(int[] buffer, int size) {
- if(size > buffer.length) {
+ private static int[] checkSortBuffer(int[] buffer, int size) {
+ if (buffer == null || size > buffer.length) {
return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size));
}
return buffer;
@@ -1025,16 +1074,19 @@
}
for (int i = size - 1; i > 0; i--) {
- T v = array[0];
- int prio = priority[0];
- int insertOrder = insertionOrder[0];
+ final T tmpSpan = array[0];
array[0] = array[i];
+ array[i] = tmpSpan;
+
+ final int tmpPriority = priority[0];
priority[0] = priority[i];
+ priority[i] = tmpPriority;
+
+ final int tmpOrder = insertionOrder[0];
insertionOrder[0] = insertionOrder[i];
+ insertionOrder[i] = tmpOrder;
+
siftDown(0, array, i, priority, insertionOrder);
- array[i] = v;
- priority[i] = prio;
- insertionOrder[i] = insertOrder;
}
}
@@ -1050,10 +1102,6 @@
*/
private final <T> void siftDown(int index, T[] array, int size, int[] priority,
int[] insertionOrder) {
- T v = array[index];
- int prio = priority[index];
- int insertOrder = insertionOrder[index];
-
int left = 2 * index + 1;
while (left < size) {
if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) {
@@ -1062,15 +1110,22 @@
if (compareSpans(index, left, priority, insertionOrder) >= 0) {
break;
}
+
+ final T tmpSpan = array[index];
array[index] = array[left];
+ array[left] = tmpSpan;
+
+ final int tmpPriority = priority[index];
priority[index] = priority[left];
+ priority[left] = tmpPriority;
+
+ final int tmpOrder = insertionOrder[index];
insertionOrder[index] = insertionOrder[left];
+ insertionOrder[left] = tmpOrder;
+
index = left;
left = 2 * index + 1;
}
- array[index] = v;
- priority[index] = prio;
- insertionOrder[index] = insertOrder;
}
/**
@@ -1704,6 +1759,10 @@
}
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
+
+ @GuardedBy("sCachedIntBuffer")
+ private static final int[][] sCachedIntBuffer = new int[6][0];
+
private InputFilter[] mFilters = NO_FILTERS;
private char[] mText;
@@ -1717,8 +1776,6 @@
private int[] mSpanFlags;
private int[] mSpanOrder; // store the order of span insertion
private int mSpanInsertCount; // counter for the span insertion
- private int[] mPrioSortBuffer; // buffer used to sort getSpans result
- private int[] mOrderSortBuffer; // buffer used to sort getSpans result
private int mSpanCount;
private IdentityHashMap<Object, Integer> mIndexOfSpan;
diff --git a/core/java/android/text/TextAssistant.java b/core/java/android/text/TextAssistant.java
deleted file mode 100644
index b044981..0000000
--- a/core/java/android/text/TextAssistant.java
+++ /dev/null
@@ -1,56 +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.text;
-
-/**
- * Interface for providing text assistant features.
- */
-public interface TextAssistant {
-
- /**
- * NO_OP TextAssistant. This will act as the default TextAssistant until we implement a
- * TextClassificationManager.
- * @hide
- */
- TextAssistant NO_OP = new TextAssistant() {
-
- private final TextSelection mTextSelection = new TextSelection();
-
- @Override
- public TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- mTextSelection.mStartIndex = selectionStartIndex;
- mTextSelection.mEndIndex = selectionEndIndex;
- return mTextSelection;
- }
-
- @Override
- public void addLinks(Spannable text, int linkMask) {}
- };
-
- /**
- * Returns suggested text selection indices, recognized types and their associated confidence
- * scores. The selections are ordered from highest to lowest scoring.
- */
- TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex);
-
- /**
- * Adds assistance clickable spans to the provided text.
- */
- void addLinks(Spannable text, int linkMask);
-}
diff --git a/core/java/android/text/TextClassification.java b/core/java/android/text/TextClassification.java
deleted file mode 100644
index bb226da..0000000
--- a/core/java/android/text/TextClassification.java
+++ /dev/null
@@ -1,40 +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.text;
-
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Information about entities that a specific piece of text is classified as.
- */
-public class TextClassification {
-
- /** @hide */
- public static final TextClassification NO_OP = new TextClassification();
-
- private Map<String, Float> mTypeConfidence = Collections.unmodifiableMap(Collections.EMPTY_MAP);
-
- /**
- * Returns a map of text classification types to their respective confidence scores.
- * The scores range from 0 (low confidence) to 1 (high confidence). The items are ordered from
- * high scoring items to low scoring items.
- */
- public Map<String, Float> getTypeConfidence() {
- return mTypeConfidence;
- }
-}
diff --git a/core/java/android/text/TextClassificationManager.java b/core/java/android/text/TextClassificationManager.java
deleted file mode 100644
index d4548f0..0000000
--- a/core/java/android/text/TextClassificationManager.java
+++ /dev/null
@@ -1,62 +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.text;
-
-import android.annotation.NonNull;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Interface to the text classification service.
- * This class uses machine learning techniques to infer things about text.
- * Unless otherwise stated, methods of this class are blocking operations and should most likely not
- * be called on the UI thread.
- *
- * <p> You do not instantiate this class directly; instead, retrieve it through
- * {@link android.content.Context#getSystemService}.
- *
- * The TextClassificationManager serves as the default TextAssistant if none has been set.
- * @see android.app.Activity#setTextAssistant(TextAssistant).
- */
-public final class TextClassificationManager implements TextAssistant {
- // TODO: Consider not making this class implement TextAssistant.
-
- /** @hide */
- public TextClassificationManager() {}
-
- /**
- * Returns information containing languages that were detected in the provided text.
- * This is a blocking operation and should most likely not be called on the UI thread.
- */
- public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
- // TODO: Implement this using the cld3 library.
- return Collections.emptyList();
- }
-
- @Override
- public TextSelection suggestSelection(
- @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- // TODO: Implement.
- return TextAssistant.NO_OP.suggestSelection(text, selectionStartIndex, selectionEndIndex);
- }
-
- @Override
- public void addLinks(@NonNull Spannable text, int linkMask) {
- // TODO: Implement.
- }
-}
diff --git a/core/java/android/text/TextLanguage.java b/core/java/android/text/TextLanguage.java
deleted file mode 100644
index eb834f1..0000000
--- a/core/java/android/text/TextLanguage.java
+++ /dev/null
@@ -1,85 +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.text;
-
-import android.annotation.NonNull;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * Specifies detected languages for a section of text indicated by a start and end index.
- */
-public final class TextLanguage {
-
- private final int mStartIndex;
- private final int mEndIndex;
- private final Map<String, Float> mLanguageConfidence;
-
- /**
- * Initializes a TextLanguage object.
- *
- * @param startIndex the start index of the detected languages in the text provided to generate
- * this object.
- * @param endIndex the end index of the detected languages in the text provided to generate this
- * object.
- * @param languageConfidence a map of detected language to confidence score. The language string
- * is a BCP-47 language tag.
- * @throws NullPointerException if languageConfidence is null or contains a null key or value.
- */
- public TextLanguage(int startIndex, int endIndex,
- @NonNull Map<String, Float> languageConfidence) {
- mStartIndex = startIndex;
- mEndIndex = endIndex;
-
- Map<String, Float> map = new LinkedHashMap<>();
- Preconditions.checkNotNull(languageConfidence).entrySet().stream()
- .sorted(Map.Entry.comparingByValue())
- .forEach(entry -> map.put(
- Preconditions.checkNotNull(entry.getKey()),
- Preconditions.checkNotNull(entry.getValue())));
- mLanguageConfidence = Collections.unmodifiableMap(map);
- }
-
- /**
- * Returns the start index of the detected languages in the text provided to generate this
- * object.
- */
- public int getStartIndex() {
- return mStartIndex;
- }
-
- /**
- * Returns the end index of the detected languages in the text provided to generate this object.
- */
- public int getEndIndex() {
- return mEndIndex;
- }
-
- /**
- * Returns an unmodifiable map of detected language to confidence score. The map entries are
- * ordered from high confidence score (1) to low confidence score (0). The language string is a
- * BCP-47 language tag.
- */
- @NonNull
- public Map<String, Float> getLanguageConfidence() {
- return mLanguageConfidence;
- }
-}
diff --git a/core/java/android/text/TextSelection.java b/core/java/android/text/TextSelection.java
deleted file mode 100644
index 9400458..0000000
--- a/core/java/android/text/TextSelection.java
+++ /dev/null
@@ -1,51 +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.text;
-
-/**
- * Text selection information.
- */
-public class TextSelection {
-
- /** @hide */
- int mStartIndex;
- /** @hide */
- int mEndIndex;
-
- private TextClassification mTextClassification = TextClassification.NO_OP;
-
- /**
- * Returns the start index of the text selection.
- */
- public int getSelectionStartIndex() {
- return mStartIndex;
- }
-
- /**
- * Returns the end index of the text selection.
- */
- public int getSelectionEndIndex() {
- return mEndIndex;
- }
-
- /**
- * Returns information about what the text selection is classified as.
- */
- public TextClassification getTextClassification() {
- return mTextClassification;
- }
-}
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 79d16fb..92c70bd 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -16,6 +16,8 @@
package android.util;
+import android.annotation.SystemApi;
+
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
@@ -253,6 +255,19 @@
throws IOException;
/**
+ * Read events from the log, filtered by type, blocking until logs are about to be overwritten.
+ * @param tags to search for
+ * @param timestamp timestamp allow logs before this time to be overwritten.
+ * @param output container to add events into
+ * @throws IOException if something goes wrong reading events
+ * @hide
+ */
+ @SystemApi
+ public static native void readEventsOnWrapping(int[] tags, long timestamp,
+ Collection<Event> output)
+ throws IOException;
+
+ /**
* Get the name associated with an event type tag code.
* @param tag code to look up
* @return the name of the tag, or null if no tag has that number
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index b37ea8e..105cc47 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -292,7 +292,7 @@
public static final int STATE_VR = 5;
/* The color mode constants defined below must be kept in sync with the ones in
- * system/graphics.h */
+ * system/core/include/system/graphics-base.h */
/**
* Display color mode: The current color mode is unknown or invalid.
@@ -306,11 +306,24 @@
*/
public static final int COLOR_MODE_DEFAULT = 0;
- /**
- * Display color mode: SRGB
- * @hide
- */
+ /** @hide */
+ public static final int COLOR_MODE_BT601_625 = 1;
+ /** @hide */
+ public static final int COLOR_MODE_BT601_625_UNADJUSTED = 2;
+ /** @hide */
+ public static final int COLOR_MODE_BT601_525 = 3;
+ /** @hide */
+ public static final int COLOR_MODE_BT601_525_UNADJUSTED = 4;
+ /** @hide */
+ public static final int COLOR_MODE_BT709 = 5;
+ /** @hide */
+ public static final int COLOR_MODE_DCI_P3 = 6;
+ /** @hide */
public static final int COLOR_MODE_SRGB = 7;
+ /** @hide */
+ public static final int COLOR_MODE_ADOBE_RGB = 8;
+ /** @hide */
+ public static final int COLOR_MODE_DISPLAY_P3 = 9;
/**
* Internal method to create a display.
@@ -745,6 +758,8 @@
/**
* Returns the display's HDR capabilities.
+ *
+ * @see #isHdr()
*/
public HdrCapabilities getHdrCapabilities() {
synchronized (this) {
@@ -754,6 +769,35 @@
}
/**
+ * Returns whether this display supports any HDR type.
+ *
+ * @see #getHdrCapabilities()
+ * @see HdrCapabilities#getSupportedHdrTypes()
+ */
+ public boolean isHdr() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ int[] types = mDisplayInfo.hdrCapabilities.getSupportedHdrTypes();
+ return types != null && types.length > 0;
+ }
+ }
+
+ /**
+ * Returns whether this display can be used to display wide color gamut content.
+ */
+ public boolean isWideColorGamut() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ for (int colorMode : mDisplayInfo.supportedColorModes) {
+ if (colorMode == COLOR_MODE_DCI_P3 || colorMode > COLOR_MODE_SRGB) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
* Gets the supported color modes of this device.
* @hide
*/
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index a07a7ef..41a13cf 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -16,16 +16,12 @@
package android.view;
-import static android.view.View.KEYBOARD_NAVIGATION_GROUP_CLUSTER;
-import static android.view.View.KEYBOARD_NAVIGATION_GROUP_SECTION;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.view.View.KeyboardNavigationGroupType;
import java.util.ArrayList;
import java.util.Collections;
@@ -110,31 +106,28 @@
}
/**
- * Find the root of the next keyboard navigation group after the current one. The group type can
- * be either a cluster or a section.
- * @param groupType Type of the keyboard navigation group
+ * Find the root of the next keyboard navigation cluster after the current one.
* @param root The view tree to look inside. Cannot be null
- * @param currentGroup The starting point of the search. Null means the default group
+ * @param currentCluster The starting point of the search. Null means the default cluster
* @param direction Direction to look
- * @return The next group, or null if none exists
+ * @return The next cluster, or null if none exists
*/
- public View findNextKeyboardNavigationGroup(
- @KeyboardNavigationGroupType int groupType,
+ public View findNextKeyboardNavigationCluster(
@NonNull View root,
- @Nullable View currentGroup,
+ @Nullable View currentCluster,
int direction) {
View next = null;
- final ArrayList<View> groups = mTempList;
+ final ArrayList<View> clusters = mTempList;
try {
- groups.clear();
- root.addKeyboardNavigationGroups(groupType, groups, direction);
- if (!groups.isEmpty()) {
- next = findNextKeyboardNavigationGroup(
- groupType, root, currentGroup, groups, direction);
+ clusters.clear();
+ root.addKeyboardNavigationClusters(clusters, direction);
+ if (!clusters.isEmpty()) {
+ next = findNextKeyboardNavigationCluster(
+ root, currentCluster, clusters, direction);
}
} finally {
- groups.clear();
+ clusters.clear();
}
return next;
}
@@ -207,25 +200,22 @@
}
}
- private View findNextKeyboardNavigationGroup(
- @KeyboardNavigationGroupType int groupType,
+ private View findNextKeyboardNavigationCluster(
View root,
- View currentGroup,
- List<View> groups,
+ View currentCluster,
+ List<View> clusters,
int direction) {
- final int count = groups.size();
+ final int count = clusters.size();
switch (direction) {
case View.FOCUS_FORWARD:
case View.FOCUS_DOWN:
case View.FOCUS_RIGHT:
- return getNextKeyboardNavigationGroup(
- groupType, root, currentGroup, groups, count);
+ return getNextKeyboardNavigationCluster(root, currentCluster, clusters, count);
case View.FOCUS_BACKWARD:
case View.FOCUS_UP:
case View.FOCUS_LEFT:
- return getPreviousKeyboardNavigationGroup(
- groupType, root, currentGroup, groups, count);
+ return getPreviousKeyboardNavigationCluster(root, currentCluster, clusters, count);
default:
throw new IllegalArgumentException("Unknown direction: " + direction);
}
@@ -331,70 +321,50 @@
return null;
}
- private static View getNextKeyboardNavigationGroup(
- @KeyboardNavigationGroupType int groupType,
+ private static View getNextKeyboardNavigationCluster(
View root,
- View currentGroup,
- List<View> groups,
+ View currentCluster,
+ List<View> clusters,
int count) {
- if (currentGroup == null) {
- // The current group is the default one.
- // The next group after the default one is the first one.
- // Note that the caller guarantees that 'group' is not empty.
- return groups.get(0);
+ if (currentCluster == null) {
+ // The current cluster is the default one.
+ // The next cluster after the default one is the first one.
+ // Note that the caller guarantees that 'clusters' is not empty.
+ return clusters.get(0);
}
- final int position = groups.lastIndexOf(currentGroup);
+ final int position = clusters.lastIndexOf(currentCluster);
if (position >= 0 && position + 1 < count) {
- // Return the next non-default group if we can find it.
- return groups.get(position + 1);
+ // Return the next non-default cluster if we can find it.
+ return clusters.get(position + 1);
}
- switch (groupType) {
- case KEYBOARD_NAVIGATION_GROUP_CLUSTER:
- // The current cluster is the last one. The next one is the default one, i.e. the
- // root.
- return root;
- case KEYBOARD_NAVIGATION_GROUP_SECTION:
- // There is no "default section", hence returning the first one.
- return groups.get(0);
- default:
- throw new IllegalArgumentException(
- "Unknown keyboard navigation group type: " + groupType);
- }
+ // The current cluster is the last one. The next one is the default one, i.e. the
+ // root.
+ return root;
}
- private static View getPreviousKeyboardNavigationGroup(
- @KeyboardNavigationGroupType int groupType,
+ private static View getPreviousKeyboardNavigationCluster(
View root,
- View currentGroup,
- List<View> groups,
+ View currentCluster,
+ List<View> clusters,
int count) {
- if (currentGroup == null) {
- // The current group is the default one.
- // The previous group before the default one is the last one.
- // Note that the caller guarantees that 'groups' is not empty.
- return groups.get(count - 1);
+ if (currentCluster == null) {
+ // The current cluster is the default one.
+ // The previous cluster before the default one is the last one.
+ // Note that the caller guarantees that 'clusters' is not empty.
+ return clusters.get(count - 1);
}
- final int position = groups.indexOf(currentGroup);
+ final int position = clusters.indexOf(currentCluster);
if (position > 0) {
- // Return the previous non-default group if we can find it.
- return groups.get(position - 1);
+ // Return the previous non-default cluster if we can find it.
+ return clusters.get(position - 1);
}
- switch (groupType) {
- case KEYBOARD_NAVIGATION_GROUP_CLUSTER:
- // The current cluster is the first one. The previous one is the default one, i.e.
- // the root.
- return root;
- case KEYBOARD_NAVIGATION_GROUP_SECTION:
- // There is no "default section", hence returning the last one.
- return groups.get(count - 1);
- default:
- throw new IllegalArgumentException(
- "Unknown keyboard navigation group type: " + groupType);
- }
+ // The current cluster is the first one. The previous one is the default one, i.e.
+ // the root.
+ return root;
}
/**
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 19edb5c..c789f8c 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -135,9 +135,6 @@
// ids that were affected by the update, ActivityManager should resize these stacks.
int[] setNewDisplayOverrideConfiguration(in Configuration overrideConfig, int displayId);
- // Retrieves the new bounds after the configuration update evaluated by window manager.
- Rect getBoundsForNewConfiguration(int stackId);
-
void startFreezingScreen(int exitAnim, int enterAnim);
void stopFreezingScreen();
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index b0826a8..f3ebcb4 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -360,7 +360,6 @@
void destroy() {
mInitialized = false;
updateEnabledState(null);
- mRootNode.discardDisplayList();
nDestroy(mNativeProxy, mRootNode.mNativeRenderNode);
}
@@ -492,12 +491,20 @@
*/
void destroyHardwareResources(View view) {
destroyResources(view);
- mRootNode.discardDisplayList();
nDestroyHardwareResources(mNativeProxy);
}
private static void destroyResources(View view) {
view.destroyHardwareResources();
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+
+ int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ destroyResources(group.getChildAt(i));
+ }
+ }
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 26e311c..547f7d8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -857,22 +857,39 @@
*/
static boolean sCascadedDragDrop;
- /**
- * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
- * calling setFlags.
- */
- private static final int NOT_FOCUSABLE = 0x00000000;
+ /** @hide */
+ @IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Focusable {}
/**
- * This view wants keystrokes. Use with TAKES_FOCUS_MASK when calling
- * setFlags.
+ * This view does not want keystrokes.
+ * <p>
+ * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+ * android:focusable}.
*/
- private static final int FOCUSABLE = 0x00000001;
+ public static final int NOT_FOCUSABLE = 0x00000000;
+
+ /**
+ * This view wants keystrokes.
+ * <p>
+ * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+ * android:focusable}.
+ */
+ public static final int FOCUSABLE = 0x00000001;
+
+ /**
+ * This view determines focusability automatically. This is the default.
+ * <p>
+ * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+ * android:focusable}.
+ */
+ public static final int FOCUSABLE_AUTO = 0x00000010;
/**
* Mask for use with setFlags indicating bits used for focus.
*/
- private static final int FOCUSABLE_MASK = 0x00000001;
+ private static final int FOCUSABLE_MASK = 0x00000011;
/**
* This view will adjust its padding to fit sytem windows (e.g. status bar)
@@ -1252,14 +1269,6 @@
@Retention(RetentionPolicy.SOURCE)
public @interface FocusRealDirection {} // Like @FocusDirection, but without forward/backward
- /** @hide */
- @IntDef({
- KEYBOARD_NAVIGATION_GROUP_CLUSTER,
- KEYBOARD_NAVIGATION_GROUP_SECTION
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface KeyboardNavigationGroupType {}
-
/**
* Use with {@link #focusSearch(int)}. Move focus to the previous selectable
* item.
@@ -1293,18 +1302,6 @@
public static final int FOCUS_DOWN = 0x00000082;
/**
- * Use with {@link #keyboardNavigationGroupSearch(int, View, int)}. Search for a keyboard
- * navigation cluster.
- */
- public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1;
-
- /**
- * Use with {@link #keyboardNavigationGroupSearch(int, View, int)}. Search for a keyboard
- * navigation section.
- */
- public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2;
-
- /**
* Bits of {@link #getMeasuredWidthAndState()} and
* {@link #getMeasuredWidthAndState()} that provide the actual measured size.
*/
@@ -2500,7 +2497,7 @@
* 1 PFLAG3_SCROLL_INDICATOR_END
* 1 PFLAG3_ASSIST_BLOCKED
* 1 PFLAG3_CLUSTER
- * 1 PFLAG3_SECTION
+ * x * NO LONGER NEEDED, SHOULD BE REUSED *
* 1 PFLAG3_FINGER_DOWN
* 1 PFLAG3_FOCUSED_BY_DEFAULT
* xxxx * NO LONGER NEEDED, SHOULD BE REUSED *
@@ -2710,14 +2707,6 @@
private static final int PFLAG3_CLUSTER = 0x8000;
/**
- * Flag indicating that the view is a root of a keyboard navigation section.
- *
- * @see #isKeyboardNavigationSection()
- * @see #setKeyboardNavigationSection(boolean)
- */
- private static final int PFLAG3_SECTION = 0x10000;
-
- /**
* Indicates that the user is currently touching the screen.
* Currently used for the tooltip positioning only.
*/
@@ -3807,11 +3796,6 @@
*/
int mNextClusterForwardId = View.NO_ID;
- /**
- * User-specified next keyboard navigation section.
- */
- int mNextSectionForwardId = View.NO_ID;
-
private CheckForLongPress mPendingCheckForLongPress;
private CheckForTap mPendingCheckForTap = null;
private PerformClick mPerformClick;
@@ -4169,7 +4153,7 @@
public View(Context context) {
mContext = context;
mResources = context != null ? context.getResources() : null;
- mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
+ mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO;
// Set some flags defaults
mPrivateFlags2 =
(LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
@@ -4355,6 +4339,10 @@
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+ // Set default values.
+ viewFlagValues |= FOCUSABLE_AUTO;
+ viewFlagMasks |= FOCUSABLE_AUTO;
+
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
@@ -4467,8 +4455,8 @@
}
break;
case com.android.internal.R.styleable.View_focusable:
- if (a.getBoolean(attr, false)) {
- viewFlagValues |= FOCUSABLE;
+ viewFlagValues = (viewFlagValues & ~FOCUSABLE_MASK) | getFocusableAttribute(a);
+ if ((viewFlagValues & FOCUSABLE_AUTO) == 0) {
viewFlagMasks |= FOCUSABLE_MASK;
}
break;
@@ -4622,9 +4610,6 @@
case R.styleable.View_nextClusterForward:
mNextClusterForwardId = a.getResourceId(attr, View.NO_ID);
break;
- case R.styleable.View_nextSectionForward:
- mNextSectionForwardId = a.getResourceId(attr, View.NO_ID);
- break;
case R.styleable.View_minWidth:
mMinWidth = a.getDimensionPixelSize(attr, 0);
break;
@@ -4769,11 +4754,6 @@
setKeyboardNavigationCluster(a.getBoolean(attr, true));
}
break;
- case R.styleable.View_keyboardNavigationSection:
- if (a.peekValue(attr) != null) {
- setKeyboardNavigationSection(a.getBoolean(attr, true));
- }
- break;
case R.styleable.View_focusedByDefault:
if (a.peekValue(attr) != null) {
setFocusedByDefault(a.getBoolean(attr, true));
@@ -5047,7 +5027,7 @@
case GONE: out.append('G'); break;
default: out.append('.'); break;
}
- out.append((mViewFlags&FOCUSABLE_MASK) == FOCUSABLE ? 'F' : '.');
+ out.append((mViewFlags & FOCUSABLE) == FOCUSABLE ? 'F' : '.');
out.append((mViewFlags&ENABLED_MASK) == ENABLED ? 'E' : '.');
out.append((mViewFlags&DRAW_MASK) == WILL_NOT_DRAW ? '.' : 'D');
out.append((mViewFlags&SCROLLBARS_HORIZONTAL) != 0 ? 'H' : '.');
@@ -8043,28 +8023,6 @@
}
/**
- * Gets the id of the root of the next keyboard navigation section.
- * @return The next keyboard navigation section ID, or {@link #NO_ID} if the framework should
- * decide automatically.
- *
- * @attr ref android.R.styleable#View_nextSectionForward
- */
- public int getNextSectionForwardId() {
- return mNextSectionForwardId;
- }
-
- /**
- * Sets the id of the view to use as the root of the next keyboard navigation section.
- * @param nextSectionForwardId The next section ID, or {@link #NO_ID} if the framework should
- * decide automatically.
- *
- * @attr ref android.R.styleable#View_nextSectionForward
- */
- public void setNextSectionForwardId(int nextSectionForwardId) {
- mNextSectionForwardId = nextSectionForwardId;
- }
-
- /**
* Returns the visibility of this view and all of its ancestors
*
* @return True if this view and all of its ancestors are {@link #VISIBLE}
@@ -8516,20 +8474,39 @@
/**
* Set whether this view can receive the focus.
- *
+ * <p>
* Setting this to false will also ensure that this view is not focusable
* in touch mode.
*
* @param focusable If true, this view can receive the focus.
*
* @see #setFocusableInTouchMode(boolean)
+ * @see #setFocusable(int)
* @attr ref android.R.styleable#View_focusable
*/
public void setFocusable(boolean focusable) {
- if (!focusable) {
+ setFocusable(focusable ? FOCUSABLE : NOT_FOCUSABLE);
+ }
+
+ /**
+ * Sets whether this view can receive focus.
+ * <p>
+ * Setting this to {@link #FOCUSABLE_AUTO} tells the framework to determine focusability
+ * automatically based on the view's interactivity. This is the default.
+ * <p>
+ * Setting this to NOT_FOCUSABLE will ensure that this view is also not focusable
+ * in touch mode.
+ *
+ * @param focusable One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE},
+ * or {@link #FOCUSABLE_AUTO}.
+ * @see #setFocusableInTouchMode(boolean)
+ * @attr ref android.R.styleable#View_focusable
+ */
+ public void setFocusable(@Focusable int focusable) {
+ if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
}
- setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK);
+ setFlags(focusable, FOCUSABLE_MASK);
}
/**
@@ -9119,14 +9096,29 @@
/**
- * Returns whether this View is able to take focus.
+ * Returns whether this View is currently able to take focus.
*
* @return True if this view can take focus, or false otherwise.
- * @attr ref android.R.styleable#View_focusable
*/
@ViewDebug.ExportedProperty(category = "focus")
public final boolean isFocusable() {
- return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK);
+ return FOCUSABLE == (mViewFlags & FOCUSABLE);
+ }
+
+ /**
+ * Returns the focusable setting for this view.
+ *
+ * @return One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE}, or {@link #FOCUSABLE_AUTO}.
+ * @attr ref android.R.styleable#View_focusable
+ */
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = NOT_FOCUSABLE, to = "NOT_FOCUSABLE"),
+ @ViewDebug.IntToString(from = FOCUSABLE, to = "FOCUSABLE"),
+ @ViewDebug.IntToString(from = FOCUSABLE_AUTO, to = "FOCUSABLE_AUTO")
+ })
+ @Focusable
+ public int getFocusable() {
+ return (mViewFlags & FOCUSABLE_AUTO) > 0 ? FOCUSABLE_AUTO : mViewFlags & FOCUSABLE;
}
/**
@@ -9186,49 +9178,11 @@
}
/**
- * Returns whether this View is a root of a keyboard navigation section.
- *
- * @return True if this view is a root of a section, or false otherwise.
- * @attr ref android.R.styleable#View_keyboardNavigationSection
- */
- @ViewDebug.ExportedProperty(category = "keyboardNavigationSection")
- public final boolean isKeyboardNavigationSection() {
- return (mPrivateFlags3 & PFLAG3_SECTION) != 0;
- }
-
- /**
- * Set whether this view is a root of a keyboard navigation section.
- *
- * @param isSection If true, this view is a root of a section.
- *
- * @attr ref android.R.styleable#View_keyboardNavigationSection
- */
- public void setKeyboardNavigationSection(boolean isSection) {
- if (isSection) {
- mPrivateFlags3 |= PFLAG3_SECTION;
- } else {
- mPrivateFlags3 &= ~PFLAG3_SECTION;
- }
- }
-
- final boolean isKeyboardNavigationGroupOfType(@KeyboardNavigationGroupType int groupType) {
- switch (groupType) {
- case KEYBOARD_NAVIGATION_GROUP_CLUSTER:
- return isKeyboardNavigationCluster();
- case KEYBOARD_NAVIGATION_GROUP_SECTION:
- return isKeyboardNavigationSection();
- default:
- throw new IllegalArgumentException(
- "Unknown keyboard navigation group type: " + groupType);
- }
- }
-
- /**
* Returns whether this View should receive focus when the focus is restored for the view
* hierarchy containing this view.
* <p>
* Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
- * window or serves as a target of cluster or section navigation.
+ * window or serves as a target of cluster navigation.
*
* @see #restoreDefaultFocus(int)
*
@@ -9245,7 +9199,7 @@
* hierarchy containing this view.
* <p>
* Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
- * window or serves as a target of cluster or section navigation.
+ * window or serves as a target of cluster navigation.
*
* @param isFocusedByDefault {@code true} to set this view as the default-focus view,
* {@code false} otherwise.
@@ -9284,35 +9238,28 @@
}
/**
- * Find the nearest keyboard navigation group in the specified direction. The group type can be
- * either a cluster or a section.
- * This does not actually give focus to that group.
+ * Find the nearest keyboard navigation cluster in the specified direction.
+ * This does not actually give focus to that cluster.
*
- * @param groupType Type of the keyboard navigation group
- * @param currentGroup The starting point of the search. Null means the current group is not
- * found yet
+ * @param currentCluster The starting point of the search. Null means the current cluster is not
+ * found yet
* @param direction Direction to look
*
- * @return The nearest keyboard navigation group in the specified direction, or null if none
+ * @return The nearest keyboard navigation cluster in the specified direction, or null if none
* can be found
*/
- public View keyboardNavigationGroupSearch(
- @KeyboardNavigationGroupType int groupType, View currentGroup, int direction) {
- if (isKeyboardNavigationGroupOfType(groupType)) {
- currentGroup = this;
+ public View keyboardNavigationClusterSearch(View currentCluster, int direction) {
+ if (isKeyboardNavigationCluster()) {
+ currentCluster = this;
}
- if (isRootNamespace()
- || (groupType == KEYBOARD_NAVIGATION_GROUP_SECTION
- && isKeyboardNavigationCluster())) {
+ if (isRootNamespace()) {
// Root namespace means we should consider ourselves the top of the
// tree for group searching; otherwise we could be group searching
// into other tabs. see LocalActivityManager and TabHost for more info.
- // In addition, a cluster node works as a root for section searches.
- return FocusFinder.getInstance().findNextKeyboardNavigationGroup(
- groupType, this, currentGroup, direction);
+ return FocusFinder.getInstance().findNextKeyboardNavigationCluster(
+ this, currentCluster, direction);
} else if (mParent != null) {
- return mParent.keyboardNavigationGroupSearch(
- groupType, currentGroup, direction);
+ return mParent.keyboardNavigationClusterSearch(currentCluster, direction);
}
return null;
}
@@ -9440,19 +9387,16 @@
}
/**
- * Adds any keyboard navigation group roots that are descendants of this view (possibly
- * including this view if it is a group root itself) to views. The group type can be either a
- * cluster or a section.
+ * Adds any keyboard navigation cluster roots that are descendants of this view (possibly
+ * including this view if it is a cluster root itself) to views.
*
- * @param groupType Type of the keyboard navigation group
- * @param views Keyboard navigation group roots found so far
+ * @param views Keyboard navigation cluster roots found so far
* @param direction Direction to look
*/
- public void addKeyboardNavigationGroups(
- @KeyboardNavigationGroupType int groupType,
+ public void addKeyboardNavigationClusters(
@NonNull Collection<View> views,
int direction) {
- if (!(isKeyboardNavigationGroupOfType(groupType))) {
+ if (!(isKeyboardNavigationCluster())) {
return;
}
views.add(this);
@@ -9726,8 +9670,8 @@
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
- if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
- (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+ if ((mViewFlags & FOCUSABLE) != FOCUSABLE
+ || (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
@@ -12081,14 +12025,27 @@
}
int privateFlags = mPrivateFlags;
+ // If focusable is auto, update the FOCUSABLE bit.
+ if (((mViewFlags & FOCUSABLE_AUTO) != 0)
+ && (changed & (FOCUSABLE_MASK | CLICKABLE | FOCUSABLE_IN_TOUCH_MODE)) != 0) {
+ int newFocus = NOT_FOCUSABLE;
+ if ((mViewFlags & (CLICKABLE | FOCUSABLE_IN_TOUCH_MODE)) != 0) {
+ newFocus = FOCUSABLE;
+ } else {
+ mViewFlags = (mViewFlags & ~FOCUSABLE_IN_TOUCH_MODE);
+ }
+ mViewFlags = (mViewFlags & ~FOCUSABLE) | newFocus;
+ int focusChanged = (old & FOCUSABLE) ^ (newFocus & FOCUSABLE);
+ changed = (changed & ~FOCUSABLE) | focusChanged;
+ }
+
/* Check if the FOCUSABLE bit has changed */
- if (((changed & FOCUSABLE_MASK) != 0) &&
- ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) {
- if (((old & FOCUSABLE_MASK) == FOCUSABLE)
+ if (((changed & FOCUSABLE) != 0) && ((privateFlags & PFLAG_HAS_BOUNDS) != 0)) {
+ if (((old & FOCUSABLE) == FOCUSABLE)
&& ((privateFlags & PFLAG_FOCUSED) != 0)) {
/* Give up focus if we are no longer focusable */
clearFocus();
- } else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE)
+ } else if (((old & FOCUSABLE) == NOT_FOCUSABLE)
&& ((privateFlags & PFLAG_FOCUSED) == 0)) {
/*
* Tell the view system that we are now available to take focus
@@ -12231,7 +12188,7 @@
}
if (accessibilityEnabled) {
- if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0
+ if ((changed & FOCUSABLE) != 0 || (changed & VISIBILITY_MASK) != 0
|| (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0
|| (changed & CONTEXT_CLICKABLE) != 0) {
if (oldIncludeForAccessibility != includeForAccessibility()) {
@@ -16641,12 +16598,6 @@
// safe to free its copy of the display list as it knows that we will
// push an updated DisplayList if we try to draw again
resetDisplayList();
- if (mOverlay != null) {
- mOverlay.getOverlayView().destroyHardwareResources();
- }
- if (mGhostView != null) {
- mGhostView.destroyHardwareResources();
- }
}
/**
@@ -16817,9 +16768,11 @@
}
private void resetDisplayList() {
- mRenderNode.discardDisplayList();
+ if (mRenderNode.isValid()) {
+ mRenderNode.discardDisplayList();
+ }
- if (mBackgroundRenderNode != null) {
+ if (mBackgroundRenderNode != null && mBackgroundRenderNode.isValid()) {
mBackgroundRenderNode.discardDisplayList();
}
}
@@ -18160,7 +18113,7 @@
private static String printFlags(int flags) {
String output = "";
int numFlags = 0;
- if ((flags & FOCUSABLE_MASK) == FOCUSABLE) {
+ if ((flags & FOCUSABLE) == FOCUSABLE) {
output += "TAKES_FOCUS";
numFlags++;
}
@@ -24806,6 +24759,19 @@
ViewConfiguration.getLongPressTooltipHideTimeout());
}
+ private int getFocusableAttribute(TypedArray attributes) {
+ TypedValue val = new TypedValue();
+ if (attributes.getValue(com.android.internal.R.styleable.View_focusable, val)) {
+ if (val.type == TypedValue.TYPE_INT_BOOLEAN) {
+ return (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE);
+ } else {
+ return val.data;
+ }
+ } else {
+ return FOCUSABLE_AUTO;
+ }
+ }
+
/**
* @return The content view of the tooltip popup currently being shown, or null if the tooltip
* is not showing.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d252d75..ba73c5f 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -915,13 +915,10 @@
*/
@Override
public View focusSearch(View focused, int direction) {
- if (isRootNamespace()
- || isKeyboardNavigationCluster()
- && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD)) {
+ if (isRootNamespace()) {
// root namespace means we should consider ourselves the top of the
// tree for focus searching; otherwise we could be focus searching
// into other tabs. see LocalActivityManager and TabHost for more info.
- // Cluster's root works same way for the forward and backward navigation.
return FocusFinder.getInstance().findNextFocus(this, focused, direction);
} else if (mParent != null) {
return mParent.focusSearch(focused, direction);
@@ -1136,12 +1133,6 @@
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- if (isKeyboardNavigationCluster()
- && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) && !hasFocus()) {
- // A cluster cannot be focus-entered from outside using forward/backward navigation.
- return;
- }
-
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
@@ -1175,11 +1166,10 @@
}
@Override
- public void addKeyboardNavigationGroups(
- @KeyboardNavigationGroupType int groupType, Collection<View> views, int direction) {
+ public void addKeyboardNavigationClusters(Collection<View> views, int direction) {
final int focusableCount = views.size();
- super.addKeyboardNavigationGroups(groupType, views, direction);
+ super.addKeyboardNavigationClusters(views, direction);
if (focusableCount != views.size()) {
// No need to look for groups inside a group.
@@ -1195,14 +1185,8 @@
for (int i = 0; i < count; i++) {
final View child = children[i];
- if (groupType == KEYBOARD_NAVIGATION_GROUP_SECTION
- && child.isKeyboardNavigationCluster()) {
- // When the current cluster is the default cluster, and we are searching for
- // sections, ignore sections inside non-default clusters.
- continue;
- }
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
- child.addKeyboardNavigationGroups(groupType, views, direction);
+ child.addKeyboardNavigationClusters(views, direction);
}
}
}
@@ -3072,8 +3056,7 @@
final View[] children = mChildren;
for (int i = index; i != end; i += increment) {
View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- && !child.isKeyboardNavigationCluster()) {
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
if (child.requestFocus(direction, previouslyFocusedRect)) {
return true;
}
@@ -3450,16 +3433,6 @@
super.dispatchDetachedFromWindow();
}
- /** @hide */
- @Override
- protected void destroyHardwareResources() {
- super.destroyHardwareResources();
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- getChildAt(i).destroyHardwareResources();
- }
- }
-
/**
* @hide
*/
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index c9277ca..79b05cd 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -18,7 +18,6 @@
import android.graphics.Rect;
import android.os.Bundle;
-import android.view.View.KeyboardNavigationGroupType;
import android.view.accessibility.AccessibilityEvent;
/**
@@ -148,20 +147,17 @@
public View focusSearch(View v, int direction);
/**
- * Find the nearest keyboard navigation group in the specified direction. The group type can be
- * either a cluster or a section.
- * This does not actually give focus to that group.
+ * Find the nearest keyboard navigation cluster in the specified direction.
+ * This does not actually give focus to that cluster.
*
- * @param groupType Type of the keyboard navigation group
- * @param currentGroup The starting point of the search. Null means the current group is not
- * found yet
+ * @param currentCluster The starting point of the search. Null means the current cluster is not
+ * found yet
* @param direction Direction to look
*
- * @return The nearest keyboard navigation group in the specified direction, or null if none
+ * @return The nearest keyboard navigation cluster in the specified direction, or null if none
* can be found
*/
- View keyboardNavigationGroupSearch(
- @KeyboardNavigationGroupType int groupType, View currentGroup, int direction);
+ View keyboardNavigationClusterSearch(View currentCluster, int direction);
/**
* Change the z order of the child so it's on top of all other children.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c0f2c37..3cbe82e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,8 +16,6 @@
package android.view;
-import static android.view.View.KEYBOARD_NAVIGATION_GROUP_CLUSTER;
-import static android.view.View.KEYBOARD_NAVIGATION_GROUP_SECTION;
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
@@ -73,7 +71,6 @@
import android.util.TypedValue;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
-import android.view.View.KeyboardNavigationGroupType;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -4397,14 +4394,13 @@
return false;
}
- private boolean performKeyboardGroupNavigation(
- @KeyboardNavigationGroupType int groupType, int direction) {
+ private boolean performKeyboardGroupNavigation(int direction) {
final View focused = mView.findFocus();
- final View group = focused != null
- ? focused.keyboardNavigationGroupSearch(groupType, null, direction)
- : keyboardNavigationGroupSearch(groupType, null, direction);
+ final View cluster = focused != null
+ ? focused.keyboardNavigationClusterSearch(null, direction)
+ : keyboardNavigationClusterSearch(null, direction);
- if (group != null && group.restoreDefaultFocus(View.FOCUS_DOWN)) {
+ if (cluster != null && cluster.restoreDefaultFocus(View.FOCUS_DOWN)) {
return true;
}
@@ -4424,32 +4420,15 @@
}
int groupNavigationDirection = 0;
- @KeyboardNavigationGroupType int groupType = 0;
if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed()) {
final int character =
event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK);
if (character == '+') {
- groupType = KEYBOARD_NAVIGATION_GROUP_CLUSTER;
groupNavigationDirection = View.FOCUS_FORWARD;
}
if (character == '_') {
- groupType = KEYBOARD_NAVIGATION_GROUP_CLUSTER;
- groupNavigationDirection = View.FOCUS_BACKWARD;
- }
- }
-
- if (event.getAction() == KeyEvent.ACTION_DOWN && event.isAltPressed()) {
- final int character =
- event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_ALT_MASK);
- if (character == '+') {
- groupType = KEYBOARD_NAVIGATION_GROUP_SECTION;
- groupNavigationDirection = View.FOCUS_FORWARD;
- }
-
- if (character == '_') {
- groupType = KEYBOARD_NAVIGATION_GROUP_SECTION;
groupNavigationDirection = View.FOCUS_BACKWARD;
}
}
@@ -4479,7 +4458,7 @@
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (groupNavigationDirection != 0) {
- if (performKeyboardGroupNavigation(groupType, groupNavigationDirection)) {
+ if (performKeyboardGroupNavigation(groupNavigationDirection)) {
return FINISH_HANDLED;
}
} else {
@@ -5910,11 +5889,10 @@
* {@inheritDoc}
*/
@Override
- public View keyboardNavigationGroupSearch(
- @KeyboardNavigationGroupType int groupType, View currentGroup, int direction) {
+ public View keyboardNavigationClusterSearch(View currentCluster, int direction) {
checkThread();
- return FocusFinder.getInstance().findNextKeyboardNavigationGroup(groupType,
- mView, currentGroup, direction);
+ return FocusFinder.getInstance().findNextKeyboardNavigationCluster(
+ mView, currentCluster, direction);
}
public void debug() {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8bc988d..e2bdd97 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -27,6 +27,7 @@
import android.annotation.StyleRes;
import android.annotation.SystemApi;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -1128,6 +1129,28 @@
}
/**
+ * <p>Set the color mode of the window. Setting the color mode might
+ * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p>
+ *
+ * <p>The color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+ * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.</p>
+ */
+ public void setColorMode(@ActivityInfo.ColorMode int colorMode) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.setColorMode(colorMode);
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Returns the color mode of the window, one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+ * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.
+ */
+ @ActivityInfo.ColorMode
+ public int getColorMode() {
+ return getAttributes().getColorMode();
+ }
+
+ /**
* Set the amount of dim behind the window when using
* {@link WindowManager.LayoutParams#FLAG_DIM_BEHIND}. This overrides
* the default dim amount of that is selected by the Window based on
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e5a6ebd..bf840e5 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -22,7 +22,6 @@
import android.app.Presentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
-import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.IBinder;
@@ -1577,7 +1576,8 @@
/**
* The desired bitmap format. May be one of the constants in
- * {@link android.graphics.PixelFormat}. Default is OPAQUE.
+ * {@link android.graphics.PixelFormat}. The choice of format
+ * might be overridden by {@link #setColorMode(int)}. Default is OPAQUE.
*/
public int format;
@@ -1833,6 +1833,17 @@
*/
public long hideTimeoutMilliseconds = -1;
+ /**
+ * The color mode requested by this window. The target display may
+ * not be able to honor the request. When the color mode is not set
+ * to {@link ActivityInfo#COLOR_MODE_DEFAULT}, it might override the
+ * pixel format specified in {@link #format}.
+ *
+ * @hide
+ */
+ @ActivityInfo.ColorMode
+ private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
@@ -1912,6 +1923,31 @@
preservePreviousSurfaceInsets = preservePrevious;
}
+ /**
+ * <p>Set the color mode of the window. Setting the color mode might
+ * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p>
+ *
+ * <p>The color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+ * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or
+ * {@link ActivityInfo#COLOR_MODE_HDR}.</p>
+ *
+ * @see #getColorMode()
+ */
+ public void setColorMode(@ActivityInfo.ColorMode int colorMode) {
+ mColorMode = colorMode;
+ }
+
+ /**
+ * Returns the color mode of the window, one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+ * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.
+ *
+ * @see #setColorMode(int)
+ */
+ @ActivityInfo.ColorMode
+ public int getColorMode() {
+ return mColorMode;
+ }
+
/** @hide */
@SystemApi
public final void setUserActivityTimeout(long timeout) {
@@ -2268,9 +2304,11 @@
sb.append(',');
sb.append(y);
sb.append(")(");
- sb.append((width== MATCH_PARENT ?"fill":(width==WRAP_CONTENT?"wrap":width)));
+ sb.append((width == MATCH_PARENT ? "fill" : (width == WRAP_CONTENT
+ ? "wrap" : String.valueOf(width))));
sb.append('x');
- sb.append((height== MATCH_PARENT ?"fill":(height==WRAP_CONTENT?"wrap":height)));
+ sb.append((height == MATCH_PARENT ? "fill" : (height == WRAP_CONTENT
+ ? "wrap" : String.valueOf(height))));
sb.append(")");
if (horizontalMargin != 0) {
sb.append(" hm=");
@@ -2367,6 +2405,7 @@
sb.append(" needsMenuKey=");
sb.append(needsMenuKey);
}
+ sb.append(" colorMode=").append(mColorMode);
sb.append('}');
return sb.toString();
}
diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java
new file mode 100644
index 0000000..7aab71f
--- /dev/null
+++ b/core/java/android/view/textclassifier/EntityConfidence.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper object for setting and getting entity scores for classified text.
+ *
+ * @param <T> the entity type.
+ * @hide
+ */
+final class EntityConfidence<T> {
+
+ private final Map<T, Float> mEntityConfidence = new HashMap<>();
+
+ private final Comparator<T> mEntityComparator = (e1, e2) -> {
+ float score1 = mEntityConfidence.get(e1);
+ float score2 = mEntityConfidence.get(e2);
+ if (score1 > score2) {
+ return 1;
+ }
+ if (score1 < score2) {
+ return -1;
+ }
+ return 0;
+ };
+
+ EntityConfidence() {}
+
+ EntityConfidence(@NonNull EntityConfidence<T> source) {
+ Preconditions.checkNotNull(source);
+ mEntityConfidence.putAll(source.mEntityConfidence);
+ }
+
+ /**
+ * Sets an entity type for the classified text and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public void setEntityType(
+ @NonNull T type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ Preconditions.checkNotNull(type);
+ if (confidenceScore > 0) {
+ mEntityConfidence.put(type, Math.min(1, confidenceScore));
+ } else {
+ mEntityConfidence.remove(type);
+ }
+ }
+
+ /**
+ * Returns an immutable list of entities found in the classified text ordered from
+ * high confidence to low confidence.
+ */
+ @NonNull
+ public List<T> getEntities() {
+ List<T> entities = new ArrayList<>(mEntityConfidence.size());
+ entities.addAll(mEntityConfidence.keySet());
+ entities.sort(mEntityComparator);
+ return Collections.unmodifiableList(entities);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(T entity) {
+ if (mEntityConfidence.containsKey(entity)) {
+ return mEntityConfidence.get(entity);
+ }
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return mEntityConfidence.toString();
+ }
+}
diff --git a/core/java/android/view/textclassifier/LinksInfo.java b/core/java/android/view/textclassifier/LinksInfo.java
new file mode 100644
index 0000000..3acbdc0
--- /dev/null
+++ b/core/java/android/view/textclassifier/LinksInfo.java
@@ -0,0 +1,41 @@
+/*
+ * 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.textclassifier;
+
+import android.annotation.NonNull;
+
+/**
+ * Link information that can be applied to text. See: {@link #apply(CharSequence)}.
+ * Typical implementations of this interface will annotate spannable text with e.g
+ * {@link android.text.style.ClickableSpan}s or other annotations.
+ */
+public interface LinksInfo {
+
+ /**
+ * @hide
+ */
+ LinksInfo NO_OP = text -> false;
+
+ /**
+ * Applies link annotations to the specified text.
+ * These annotations are not guaranteed to be applied. For example, the annotations may not be
+ * applied if the text has changed from what it was when the link spec was generated for it.
+ *
+ * @return Whether or not the link annotations were successfully applied.
+ */
+ boolean apply(@NonNull CharSequence text);
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
new file mode 100644
index 0000000..4673c50
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.text.LangId;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Interface to the text classification service.
+ *
+ * <p>You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService}.
+ */
+public final class TextClassificationManager {
+
+ private static final String LOG_TAG = "TextClassificationManager";
+
+ private final Context mContext;
+ // TODO: Implement a way to close the file descriptor.
+ private ParcelFileDescriptor mFd;
+ private TextClassifier mDefault;
+ private LangId mLangId;
+
+ /** @hide */
+ public TextClassificationManager(Context context) {
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ /**
+ * Returns the default text classifier.
+ */
+ public TextClassifier getDefaultTextClassifier() {
+ if (mDefault == null) {
+ try {
+ mFd = ParcelFileDescriptor.open(
+ new File("/etc/assistant/smart-selection.model"),
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ mDefault = new TextClassifierImpl(mContext, mFd);
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, "Error accessing 'text classifier selection' model file.", e);
+ mDefault = TextClassifier.NO_OP;
+ }
+ }
+ return mDefault;
+ }
+
+ /**
+ * Returns information containing languages that were detected in the provided text.
+ * This is a blocking operation you should avoid calling it on the UI thread.
+ *
+ * @throws IllegalArgumentException if text is null
+ */
+ public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
+ Preconditions.checkArgument(text != null);
+ try {
+ if (text.length() > 0) {
+ final String language = getLanguageDetector().findLanguage(text.toString());
+ final Locale locale = new Locale.Builder().setLanguageTag(language).build();
+ return Collections.unmodifiableList(Arrays.asList(
+ new TextLanguage.Builder(0, text.length())
+ .setLanguage(locale, 1.0f /* confidence */)
+ .build()));
+ }
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG, "Error detecting languages for text. Returning empty result.", t);
+ }
+ // Getting here means something went wrong. Return an empty result.
+ return Collections.emptyList();
+ }
+
+ private LangId getLanguageDetector() {
+ if (mLangId == null) {
+ // TODO: Use a file descriptor as soon as we start to depend on a model file
+ // for language detection.
+ mLangId = new LangId(0);
+ }
+ return mLangId;
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationResult.java b/core/java/android/view/textclassifier/TextClassificationResult.java
new file mode 100644
index 0000000..6af0efb
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationResult.java
@@ -0,0 +1,233 @@
+/*
+ * 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.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.View.OnClickListener;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information for generating a widget to handle classified text.
+ */
+public final class TextClassificationResult {
+
+ /**
+ * @hide
+ */
+ static final TextClassificationResult EMPTY = new TextClassificationResult.Builder().build();
+
+ @NonNull private final String mText;
+ @Nullable private final Drawable mIcon;
+ @Nullable private final String mLabel;
+ @Nullable private final Intent mIntent;
+ @Nullable private final OnClickListener mOnClickListener;
+ @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final List<String> mEntities;
+
+ private TextClassificationResult(
+ @NonNull String text,
+ Drawable icon,
+ String label,
+ Intent intent,
+ OnClickListener onClickListener,
+ @NonNull EntityConfidence<String> entityConfidence) {
+ mText = text;
+ mIcon = icon;
+ mLabel = label;
+ mIntent = intent;
+ mOnClickListener = onClickListener;
+ mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntities = mEntityConfidence.getEntities();
+ }
+
+ /**
+ * Gets the classified text.
+ */
+ @NonNull
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Returns the number of entities found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getEntityCount() {
+ return mEntities.size();
+ }
+
+ /**
+ * Returns the entity at the specified index. Entities are ordered from high confidence
+ * to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getEntityCount() for the number of entities available.
+ */
+ @NonNull
+ public @EntityType String getEntity(int index) {
+ return mEntities.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@EntityType String entity) {
+ return mEntityConfidence.getConfidenceScore(entity);
+ }
+
+ /**
+ * Returns an icon that may be rendered on a widget used to act on the classified text.
+ */
+ @Nullable
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns a label that may be rendered on a widget used to act on the classified text.
+ */
+ @Nullable
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Returns an intent that may be fired to act on the classified text.
+ */
+ @Nullable
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Returns an OnClickListener that may be triggered to act on the classified text.
+ */
+ @Nullable
+ public OnClickListener getOnClickListener() {
+ return mOnClickListener;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextClassificationResult {"
+ + "text=%s, entities=%s, label=%s, intent=%s}",
+ mText, mEntityConfidence, mLabel, mIntent);
+ }
+
+ /**
+ * Creates an OnClickListener that starts an activity with the specified intent.
+ *
+ * @throws IllegalArgumentException if context or intent is null
+ * @hide
+ */
+ @NonNull
+ public static OnClickListener createStartActivityOnClick(
+ @NonNull final Context context, @NonNull final Intent intent) {
+ Preconditions.checkArgument(context != null);
+ Preconditions.checkArgument(intent != null);
+ return v -> context.startActivity(intent);
+ }
+
+ /**
+ * Builder for building {@link TextClassificationResult}s.
+ */
+ public static final class Builder {
+
+ @NonNull private String mText;
+ @Nullable private Drawable mIcon;
+ @Nullable private String mLabel;
+ @Nullable private Intent mIntent;
+ @Nullable private OnClickListener mOnClickListener;
+ @NonNull private final EntityConfidence<String> mEntityConfidence =
+ new EntityConfidence<>();
+
+ /**
+ * Sets the classified text.
+ */
+ public Builder setText(@NonNull String text) {
+ mText = Preconditions.checkNotNull(text);
+ return this;
+ }
+
+ /**
+ * Sets an entity type for the classification result and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public Builder setEntityType(
+ @NonNull @EntityType String type,
+ @FloatRange(from = 0.0, to = 1.0)float confidenceScore) {
+ mEntityConfidence.setEntityType(type, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Sets an icon that may be rendered on a widget used to act on the classified text.
+ */
+ public Builder setIcon(@Nullable Drawable icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets a label that may be rendered on a widget used to act on the classified text.
+ */
+ public Builder setLabel(@Nullable String label) {
+ mLabel = label;
+ return this;
+ }
+
+ /**
+ * Sets an intent that may be fired to act on the classified text.
+ */
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets an OnClickListener that may be triggered to act on the classified text.
+ */
+ public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
+ mOnClickListener = onClickListener;
+ return this;
+ }
+
+ /**
+ * Builds an returns a {@link TextClassificationResult}.
+ */
+ public TextClassificationResult build() {
+ return new TextClassificationResult(
+ mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence);
+ }
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
new file mode 100644
index 0000000..b84e2ae
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -0,0 +1,114 @@
+/*
+ * 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.textclassifier;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for providing text classification related features.
+ *
+ * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
+ * avoid calling them on the UI thread.
+ */
+public interface TextClassifier {
+
+ String TYPE_OTHER = "other";
+ String TYPE_EMAIL = "email";
+ String TYPE_PHONE = "phone";
+ String TYPE_ADDRESS = "address";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ TYPE_OTHER, TYPE_EMAIL, TYPE_PHONE, TYPE_ADDRESS
+ })
+ @interface EntityType {}
+
+ /**
+ * No-op TextClassifier.
+ * This may be used to turn off TextClassifier features.
+ */
+ TextClassifier NO_OP = new TextClassifier() {
+
+ @Override
+ public TextSelection suggestSelection(
+ CharSequence text, int selectionStartIndex, int selectionEndIndex) {
+ return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+ }
+
+ @Override
+ public TextClassificationResult getTextClassificationResult(
+ CharSequence text, int startIndex, int endIndex) {
+ return TextClassificationResult.EMPTY;
+ }
+
+ @Override
+ public LinksInfo getLinks(CharSequence text, int linkMask) {
+ return LinksInfo.NO_OP;
+ }
+ };
+
+ /**
+ * Returns suggested text selection indices, recognized types and their associated confidence
+ * scores. The selections are ordered from highest to lowest scoring.
+ *
+ * @param text text providing context for the selected text (which is specified
+ * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+ * @param selectionStartIndex start index of the selected part of text
+ * @param selectionEndIndex end index of the selected part of text
+ *
+ * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
+ * selectionEndIndex is greater than text.length() or less than selectionStartIndex
+ */
+ @NonNull
+ TextSelection suggestSelection(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int selectionStartIndex,
+ @IntRange(from = 0) int selectionEndIndex);
+
+ /**
+ * Returns a {@link TextClassificationResult} object that can be used to generate a widget for
+ * handling the classified text.
+ *
+ * @param text text providing context for the text to classify (which is specified
+ * by the sub sequence starting at startIndex and ending at endIndex)
+ * @param startIndex start index of the text to classify
+ * @param endIndex end index of the text to classify
+ *
+ * @throws IllegalArgumentException if text is null; startIndex is negative;
+ * endIndex is greater than text.length() or less than startIndex
+ */
+ @NonNull
+ TextClassificationResult getTextClassificationResult(
+ @NonNull CharSequence text, int startIndex, int endIndex);
+
+ /**
+ * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
+ * information.
+ *
+ * @param text the text to generate annotations for
+ * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
+ * specified. Subclasses of this interface may specify additional linkMasks
+ *
+ * @throws IllegalArgumentException if text is null
+ */
+ LinksInfo getLinks(@NonNull CharSequence text, int linkMask);
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
new file mode 100644
index 0000000..72796cf
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -0,0 +1,180 @@
+/*
+ * 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.textclassifier;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.SmartSelection;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Default implementation of the {@link TextClassifier} interface.
+ *
+ * <p>This class uses machine learning to recognize entities in text.
+ * Unless otherwise stated, methods of this class are blocking operations and should most
+ * likely not be called on the UI thread.
+ *
+ * @hide
+ */
+final class TextClassifierImpl implements TextClassifier {
+
+ private static final String LOG_TAG = "TextClassifierImpl";
+
+ private final Context mContext;
+ private final ParcelFileDescriptor mFd;
+ private SmartSelection mSmartSelection;
+
+ TextClassifierImpl(Context context, ParcelFileDescriptor fd) {
+ mContext = Preconditions.checkNotNull(context);
+ mFd = Preconditions.checkNotNull(fd);
+ }
+
+ @Override
+ public TextSelection suggestSelection(
+ @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) {
+ validateInput(text, selectionStartIndex, selectionEndIndex);
+ try {
+ if (text.length() > 0) {
+ final String string = text.toString();
+ final int[] startEnd = getSmartSelection()
+ .suggest(string, selectionStartIndex, selectionEndIndex);
+ final int start = startEnd[0];
+ final int end = startEnd[1];
+ if (start >= 0 && end <= string.length() && start <= end) {
+ final String type = getSmartSelection().classifyText(string, start, end);
+ return new TextSelection.Builder(start, end)
+ .setEntityType(type, 1.0f)
+ .build();
+ } else {
+ // We can not trust the result. Log the issue and ignore the result.
+ Log.d(LOG_TAG, "Got bad indices for input text. Ignoring result.");
+ }
+ }
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG,
+ "Error suggesting selection for text. No changes to selection suggested.",
+ t);
+ }
+ // Getting here means something went wrong, return a NO_OP result.
+ return TextClassifier.NO_OP.suggestSelection(
+ text, selectionStartIndex, selectionEndIndex);
+ }
+
+ @Override
+ public TextClassificationResult getTextClassificationResult(
+ @NonNull CharSequence text, int startIndex, int endIndex) {
+ validateInput(text, startIndex, endIndex);
+ try {
+ if (text.length() > 0) {
+ final CharSequence classified = text.subSequence(startIndex, endIndex);
+ String type = getSmartSelection()
+ .classifyText(text.toString(), startIndex, endIndex);
+ if (!TextUtils.isEmpty(type)) {
+ type = type.toLowerCase().trim();
+ // TODO: Added this log for debug only. Remove before release.
+ Log.d(LOG_TAG, String.format("Classification type: %s", type));
+ final Intent intent;
+ final String title;
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ intent = new Intent(Intent.ACTION_SENDTO);
+ intent.setData(Uri.parse(String.format("mailto:%s", text)));
+ title = mContext.getString(com.android.internal.R.string.email);
+ return createClassificationResult(classified, type, intent, title);
+ case TextClassifier.TYPE_PHONE:
+ intent = new Intent(Intent.ACTION_DIAL);
+ intent.setData(Uri.parse(String.format("tel:%s", text)));
+ title = mContext.getString(com.android.internal.R.string.dial);
+ return createClassificationResult(classified, type, intent, title);
+ case TextClassifier.TYPE_ADDRESS:
+ intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+ title = mContext.getString(com.android.internal.R.string.map);
+ return createClassificationResult(classified, type, intent, title);
+ default:
+ // No classification type found. Return a no-op result.
+ break;
+ // TODO: Add other classification types.
+ }
+ }
+ }
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG, "Error getting assist info.", t);
+ }
+ // Getting here means something went wrong, return a NO_OP result.
+ return TextClassifier.NO_OP.getTextClassificationResult(text, startIndex, endIndex);
+ }
+
+ @Override
+ public LinksInfo getLinks(@NonNull CharSequence text, int linkMask) {
+ // TODO: Implement
+ return TextClassifier.NO_OP.getLinks(text, linkMask);
+ }
+
+ private synchronized SmartSelection getSmartSelection() throws FileNotFoundException {
+ if (mSmartSelection == null) {
+ mSmartSelection = new SmartSelection(mFd.getFd());
+ }
+ return mSmartSelection;
+ }
+
+ private TextClassificationResult createClassificationResult(
+ CharSequence text, String type, Intent intent, String label) {
+ TextClassificationResult.Builder builder = new TextClassificationResult.Builder()
+ .setText(text.toString())
+ .setEntityType(type, 1.0f /* confidence */)
+ .setIntent(intent)
+ .setOnClickListener(TextClassificationResult.createStartActivityOnClick(
+ mContext, intent))
+ .setLabel(label);
+ PackageManager pm = mContext.getPackageManager();
+ ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+ // TODO: If the resolveInfo is the "chooser", do not set the package name and use a
+ // default icon for this classification type.
+ intent.setPackage(resolveInfo.activityInfo.packageName);
+ Drawable icon = resolveInfo.activityInfo.loadIcon(pm);
+ if (icon == null) {
+ icon = resolveInfo.loadIcon(pm);
+ }
+ builder.setIcon(icon);
+ return builder.build();
+ }
+
+ /**
+ * @throws IllegalArgumentException if text is null; startIndex is negative;
+ * endIndex is greater than text.length() or less than startIndex
+ */
+ private static void validateInput(@NonNull CharSequence text, int startIndex, int endIndex) {
+ Preconditions.checkArgument(text != null);
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex <= text.length());
+ Preconditions.checkArgument(endIndex >= startIndex);
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
new file mode 100644
index 0000000..d94d163
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -0,0 +1,139 @@
+/*
+ * 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.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Specifies detected languages for a section of text indicated by a start and end index.
+ */
+public final class TextLanguage {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<Locale> mLanguageConfidence;
+ @NonNull private final List<Locale> mLanguages;
+
+ private TextLanguage(
+ int startIndex, int endIndex, @NonNull EntityConfidence<Locale> languageConfidence) {
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mLanguageConfidence = new EntityConfidence<>(languageConfidence);
+ mLanguages = mLanguageConfidence.getEntities();
+ }
+
+ /**
+ * Returns the start index of the detected languages in the text provided to generate this
+ * object.
+ */
+ public int getStartIndex() {
+ return mStartIndex;
+ }
+
+ /**
+ * Returns the end index of the detected languages in the text provided to generate this object.
+ */
+ public int getEndIndex() {
+ return mEndIndex;
+ }
+
+ /**
+ * Returns the number of languages found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getLanguageCount() {
+ return mLanguages.size();
+ }
+
+ /**
+ * Returns the language locale at the specified index.
+ * Language locales are ordered from high confidence to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getLanguageCount() for the number of language locales available.
+ */
+ @NonNull
+ public Locale getLanguage(int index) {
+ return mLanguages.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified language. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the language was
+ * not found for the classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@Nullable Locale language) {
+ return mLanguageConfidence.getConfidenceScore(language);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextLanguage {%d, %d, %s}",
+ mStartIndex, mEndIndex, mLanguageConfidence);
+ }
+
+ /**
+ * Builder to build {@link TextLanguage} objects.
+ */
+ public static final class Builder {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<Locale> mLanguageConfidence =
+ new EntityConfidence<>();
+
+ /**
+ * Creates a builder to build {@link TextLanguage} objects.
+ *
+ * @param startIndex the start index of the detected languages in the text provided
+ * to generate the result
+ * @param endIndex the end index of the detected languages in the text provided
+ * to generate the result. Must be greater than startIndex
+ */
+ public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex > startIndex);
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ }
+
+ /**
+ * Sets a language locale with the associated confidence score.
+ */
+ public Builder setLanguage(
+ @NonNull Locale locale, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ mLanguageConfidence.setEntityType(locale, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link TextLanguage}.
+ */
+ public TextLanguage build() {
+ return new TextLanguage(mStartIndex, mEndIndex, mLanguageConfidence);
+ }
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
new file mode 100644
index 0000000..3172c13
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -0,0 +1,140 @@
+/*
+ * 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.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information about where text selection should be.
+ */
+public final class TextSelection {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final List<String> mEntities;
+
+ private TextSelection(
+ int startIndex, int endIndex, @NonNull EntityConfidence<String> entityConfidence) {
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntities = mEntityConfidence.getEntities();
+ }
+
+ /**
+ * Returns the start index of the text selection.
+ */
+ public int getSelectionStartIndex() {
+ return mStartIndex;
+ }
+
+ /**
+ * Returns the end index of the text selection.
+ */
+ public int getSelectionEndIndex() {
+ return mEndIndex;
+ }
+
+ /**
+ * Returns the number of entities found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getEntityCount() {
+ return mEntities.size();
+ }
+
+ /**
+ * Returns the entity at the specified index. Entities are ordered from high confidence
+ * to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getEntityCount() for the number of entities available.
+ */
+ @NonNull
+ public @EntityType String getEntity(int index) {
+ return mEntities.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@EntityType String entity) {
+ return mEntityConfidence.getConfidenceScore(entity);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextSelection {%d, %d, %s}",
+ mStartIndex, mEndIndex, mEntityConfidence);
+ }
+
+ /**
+ * Builder used to build {@link TextSelection} objects.
+ */
+ public static final class Builder {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<String> mEntityConfidence =
+ new EntityConfidence<>();
+
+ /**
+ * Creates a builder used to build {@link TextSelection} objects.
+ *
+ * @param startIndex the start index of the text selection.
+ * @param endIndex the end index of the text selection. Must be greater than startIndex
+ */
+ public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex > startIndex);
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ }
+
+ /**
+ * Sets an entity type for the classified text and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public Builder setEntityType(
+ @NonNull @EntityType String type,
+ @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ mEntityConfidence.setEntityType(type, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Builds and returns {@link TextSelection} object.
+ */
+ public TextSelection build() {
+ return new TextSelection(mStartIndex, mEndIndex, mEntityConfidence);
+ }
+ }
+}
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
new file mode 100644
index 0000000..404bcf4
--- /dev/null
+++ b/core/java/android/webkit/UserPackage.java
@@ -0,0 +1,94 @@
+/*
+ * 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.webkit;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for storing a (user,PackageInfo) mapping.
+ * @hide
+ */
+public class UserPackage {
+ private final UserInfo mUserInfo;
+ private final PackageInfo mPackageInfo;
+
+ public UserPackage(UserInfo user, PackageInfo packageInfo) {
+ this.mUserInfo = user;
+ this.mPackageInfo = packageInfo;
+ }
+
+ /**
+ * Returns a list of (User,PackageInfo) pairs corresponding to the PackageInfos for all
+ * device users for the package named {@param packageName}.
+ */
+ public static List<UserPackage> getPackageInfosAllUsers(Context context,
+ String packageName, int packageFlags) {
+ List<UserInfo> users = getAllUsers(context);
+ List<UserPackage> userPackages = new ArrayList<UserPackage>(users.size());
+ for (UserInfo user : users) {
+ PackageInfo packageInfo = null;
+ try {
+ packageInfo = context.getPackageManager().getPackageInfoAsUser(
+ packageName, packageFlags, user.id);
+ } catch (NameNotFoundException e) {
+ }
+ userPackages.add(new UserPackage(user, packageInfo));
+ }
+ return userPackages;
+ }
+
+ /**
+ * Returns whether the given package is enabled.
+ * This state can be changed by the user from Settings->Apps
+ */
+ public boolean isEnabledPackage() {
+ if (mPackageInfo == null) return false;
+ return mPackageInfo.applicationInfo.enabled;
+ }
+
+ /**
+ * Return true if the package is installed and not hidden
+ */
+ public boolean isInstalledPackage() {
+ if (mPackageInfo == null) return false;
+ return (((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
+ && ((mPackageInfo.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0));
+ }
+
+ public UserInfo getUserInfo() {
+ return mUserInfo;
+ }
+
+ public PackageInfo getPackageInfo() {
+ return mPackageInfo;
+ }
+
+
+ private static List<UserInfo> getAllUsers(Context context) {
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ return userManager.getUsers(false);
+ }
+
+}
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 2cdff79..92d0d71 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -26,6 +26,7 @@
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.SparseArray;
@@ -206,4 +207,15 @@
appInfo.getBaseResourcePath(), newAssetPath);
}
}
+
+ /**
+ * Returns whether WebView should run in multiprocess mode.
+ */
+ public boolean isMultiProcessEnabled() {
+ try {
+ return WebViewFactory.getUpdateService().isMultiProcessEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2fc8ec9..f7f9a81 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -60,8 +60,6 @@
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.StaticLayout;
-import android.text.TextClassification;
-import android.text.TextSelection;
import android.text.TextUtils;
import android.text.method.KeyListener;
import android.text.method.MetaKeyKeyListener;
@@ -108,6 +106,8 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationResult;
+import android.view.textclassifier.TextSelection;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView.Drawables;
import android.widget.TextView.OnEditorActionListener;
@@ -149,7 +149,7 @@
private static final String UNDO_OWNER_TAG = "Editor";
// Ordering constants used to place the Action Mode or context menu items in their menu.
- // Reserve 1 for the app that the ASSIST logic suggests as the best app to handle the selection.
+ private static final int MENU_ITEM_ORDER_ASSIST = 1;
private static final int MENU_ITEM_ORDER_UNDO = 2;
private static final int MENU_ITEM_ORDER_REDO = 3;
private static final int MENU_ITEM_ORDER_SHARE = 4;
@@ -238,6 +238,8 @@
private boolean mPreserveSelection;
private boolean mRestartActionModeOnNextRefresh;
+ private TextClassificationResult mTextClassificationResult;
+
boolean mIsBeingLongClicked;
private SuggestionsPopupWindow mSuggestionsPopupWindow;
@@ -1889,7 +1891,7 @@
mInsertionPointCursorController.invalidateHandle();
}
if (mTextActionMode != null) {
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
}
}
@@ -1982,12 +1984,12 @@
if (mRestartActionModeOnNextRefresh) {
// To avoid distraction, newly start action mode only when selection action
// mode is being restarted.
- startSelectionActionMode();
+ startSelectionActionMode(getTextClassifierInfo(true));
}
} else if (selectionController == null || !selectionController.isActive()) {
// Insertion action mode is active. Avoid dismissing the selection.
stopTextActionModeWithPreservingSelection();
- startSelectionActionMode();
+ startSelectionActionMode(getTextClassifierInfo(true));
} else {
mTextActionMode.invalidateContentRect();
}
@@ -2031,7 +2033,8 @@
*
* @return true if the selection mode was actually started.
*/
- boolean startSelectionActionMode() {
+ boolean startSelectionActionMode(@Nullable TextClassificationResult textClassificationResult) {
+ mTextClassificationResult = textClassificationResult;
boolean selectionStarted = startSelectionActionModeInternal();
if (selectionStarted) {
getSelectionController().show();
@@ -2040,6 +2043,40 @@
return selectionStarted;
}
+ private boolean startSelectionActionModeWithTextAssistant() {
+ return startSelectionActionMode(getTextClassifierInfo(true));
+ }
+
+ private void invalidateActionMode(TextClassificationResult textClassificationResult) {
+ mTextClassificationResult = textClassificationResult;
+ mTextActionMode.invalidate();
+ }
+
+ // TODO: Make this a non-blocking call.
+ private TextClassificationResult getTextClassifierInfo(boolean updateSelection) {
+ // TODO: Trim the text so that only text necessary to provide context of the selected
+ // text is sent to the assistant.
+ final int trimStartIndex = 0;
+ final int trimEndIndex = mTextView.getText().length();
+ CharSequence trimmedText =
+ mTextView.getText().subSequence(trimStartIndex, trimEndIndex);
+ int startIndex = mTextView.getSelectionStart() - trimStartIndex;
+ int endIndex = mTextView.getSelectionEnd() - trimStartIndex;
+
+ if (updateSelection) {
+ TextSelection textSelection = mTextView.getTextClassifier()
+ .suggestSelection(trimmedText, startIndex, endIndex);
+ startIndex = Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex);
+ endIndex = Math.min(mTextView.getText().length(),
+ textSelection.getSelectionEndIndex() + trimStartIndex);
+ Selection.setSelection((Spannable) mTextView.getText(), startIndex, endIndex);
+ return getTextClassifierInfo(false);
+ }
+
+ return mTextView.getTextClassifier()
+ .getTextClassificationResult(trimmedText, startIndex, endIndex);
+ }
+
/**
* If the TextView allows text selection, selects the current word when no existing selection
* was available and starts a drag.
@@ -2086,7 +2123,7 @@
}
if (mTextActionMode != null) {
// Text action mode is already started
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
return false;
}
@@ -3744,8 +3781,7 @@
private final Path mSelectionPath = new Path();
private final RectF mSelectionBounds = new RectF();
private final boolean mHasSelection;
-
- private int mHandleHeight;
+ private final int mHandleHeight;
public TextActionModeCallback(boolean hasSelection) {
mHasSelection = hasSelection;
@@ -3765,18 +3801,19 @@
if (insertionController != null) {
insertionController.getHandle();
mHandleHeight = mSelectHandleCenter.getMinimumHeight();
+ } else {
+ mHandleHeight = 0;
}
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- TextClassification textClassification = updateSelectionWithTextAssistant();
-
mode.setTitle(null);
mode.setSubtitle(null);
mode.setTitleOptionalHint(true);
- populateMenuWithItems(menu, textClassification);
+ populateMenuWithItems(menu);
+ updateAssistMenuItem(menu, mTextClassificationResult);
Callback customCallback = getCustomCallback();
if (customCallback != null) {
@@ -3802,30 +3839,13 @@
}
}
- private TextClassification updateSelectionWithTextAssistant() {
- // Trim the text so that only text necessary to provide context of the selected text is
- // sent to the assistant.
- CharSequence trimmedText = mTextView.getText();
- int textLength = mTextView.getText().length();
- int trimStartIndex = 0;
- int startIndex = mTextView.getSelectionStart() - trimStartIndex;
- int endIndex = mTextView.getSelectionEnd() - trimStartIndex;
- TextSelection textSelection = mTextView.getTextAssistant()
- .suggestSelection(trimmedText, startIndex, endIndex);
- Selection.setSelection(
- (Spannable) mTextView.getText(),
- Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex),
- Math.min(textLength, textSelection.getSelectionEndIndex() + trimStartIndex));
- return textSelection.getTextClassification();
- }
-
private Callback getCustomCallback() {
return mHasSelection
? mCustomSelectionActionModeCallback
: mCustomInsertionActionModeCallback;
}
- private void populateMenuWithItems(Menu menu, TextClassification textClassification) {
+ private void populateMenuWithItems(Menu menu) {
if (mTextView.canCut()) {
menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
com.android.internal.R.string.cut)
@@ -3855,13 +3875,13 @@
updateSelectAllItem(menu);
updateReplaceItem(menu);
- updateAssistMenuItem(menu, textClassification);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateSelectAllItem(menu);
updateReplaceItem(menu);
+ updateAssistMenuItem(menu, mTextClassificationResult);
Callback customCallback = getCustomCallback();
if (customCallback != null) {
@@ -3894,10 +3914,16 @@
}
}
- private void updateAssistMenuItem(Menu menu, TextClassification textClassification) {
- // TODO: Find the best app available to handle the selected text based on information in
- // the TextClassification object.
- // Add app icon + intent to trigger app to the menu.
+ private void updateAssistMenuItem(
+ Menu menu, TextClassificationResult textClassificationResult) {
+ menu.removeItem(TextView.ID_ASSIST);
+ if (textClassificationResult != null
+ && textClassificationResult.getIcon() != null
+ && textClassificationResult.getOnClickListener() != null) {
+ menu.add(Menu.NONE, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, null)
+ .setIcon(textClassificationResult.getIcon())
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ }
}
@Override
@@ -3909,6 +3935,10 @@
if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
return true;
}
+ if (TextView.ID_ASSIST == item.getItemId() && mTextClassificationResult != null) {
+ mTextClassificationResult.getOnClickListener().onClick(mTextView);
+ stopTextActionMode();
+ }
return mTextView.onTextContextMenuItem(item.getItemId());
}
@@ -3916,6 +3946,7 @@
public void onDestroyActionMode(ActionMode mode) {
// Clear mTextActionMode not to recursively destroy action mode by clearing selection.
mTextActionMode = null;
+ mTextClassificationResult = null;
Callback customCallback = getCustomCallback();
if (customCallback != null) {
customCallback.onDestroyActionMode(mode);
@@ -4733,7 +4764,7 @@
}
positionAtCursorOffset(offset, false);
if (mTextActionMode != null) {
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
}
}
@@ -4817,7 +4848,7 @@
}
updateDrawable();
if (mTextActionMode != null) {
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
}
}
@@ -5465,7 +5496,8 @@
resetDragAcceleratorState();
if (mTextView.hasSelection()) {
- startSelectionActionMode();
+ // TODO: Do not invoke the text assistant if this was a drag selection.
+ startSelectionActionMode(getTextClassifierInfo(true));
}
break;
}
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 3b7fe86..70b70bc 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -150,7 +150,11 @@
*/
public void setIsIndicator(boolean isIndicator) {
mIsUserSeekable = !isIndicator;
- setFocusable(!isIndicator);
+ if (isIndicator) {
+ setFocusable(FOCUSABLE_AUTO);
+ } else {
+ setFocusable(FOCUSABLE);
+ }
}
/**
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 9139361..3822138 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -357,9 +357,9 @@
setInputType(inputType);
}
- boolean focusable = true;
- focusable = a.getBoolean(R.styleable.SearchView_focusable, focusable);
- setFocusable(focusable);
+ if (getFocusable() == FOCUSABLE_AUTO) {
+ setFocusable(FOCUSABLE);
+ }
a.recycle();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b1fc67e..a11ece6 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -77,8 +77,6 @@
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
-import android.text.TextAssistant;
-import android.text.TextClassificationManager;
import android.text.TextDirectionHeuristic;
import android.text.TextDirectionHeuristics;
import android.text.TextPaint;
@@ -146,6 +144,8 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
import android.view.textservice.SpellCheckerSubtype;
import android.view.textservice.TextServicesManager;
import android.widget.RemoteViews.RemoteView;
@@ -352,6 +352,8 @@
private boolean mPreDrawRegistered;
private boolean mPreDrawListenerDetached;
+ private TextClassifier mTextClassifier;
+
// A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
// that if a user is holding down a movement key to traverse text, we shouldn't also traverse
// the view hierarchy. On the other hand, if the user is using the movement key to traverse
@@ -1513,26 +1515,22 @@
if (hint != null) setHint(hint);
/*
- * Views are not normally focusable unless specified to be.
+ * Views are not normally clickable unless specified to be.
* However, TextViews that have input or movement methods *are*
- * focusable by default.
+ * clickable by default. By setting clickable here, we implicitly set focusable as well
+ * if not overridden by the developer.
*/
a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
-
- boolean focusable = mMovement != null || getKeyListener() != null;
- boolean clickable = focusable || isClickable();
- boolean longClickable = focusable || isLongClickable();
+ boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
+ boolean clickable = canInputOrMove || isClickable();
+ boolean longClickable = canInputOrMove || isLongClickable();
n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
- case com.android.internal.R.styleable.View_focusable:
- focusable = a.getBoolean(attr, focusable);
- break;
-
case com.android.internal.R.styleable.View_clickable:
clickable = a.getBoolean(attr, clickable);
break;
@@ -1544,7 +1542,6 @@
}
a.recycle();
- setFocusable(focusable);
setClickable(clickable);
setLongClickable(longClickable);
@@ -2153,11 +2150,11 @@
private void fixFocusableAndClickableSettings() {
if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
- setFocusable(true);
+ setFocusable(FOCUSABLE);
setClickable(true);
setLongClickable(true);
} else {
- setFocusable(false);
+ setFocusable(FOCUSABLE_AUTO);
setClickable(false);
setLongClickable(false);
}
@@ -6124,7 +6121,7 @@
mEditor.mTextIsSelectable = selectable;
setFocusableInTouchMode(selectable);
- setFocusable(selectable);
+ setFocusable(FOCUSABLE_AUTO);
setClickable(selectable);
setLongClickable(selectable);
@@ -9890,7 +9887,7 @@
Selection.setSelection((Spannable) text, start, end);
// Make sure selection mode is engaged.
if (mEditor != null) {
- mEditor.startSelectionActionMode();
+ mEditor.startSelectionActionMode(null);
}
return true;
}
@@ -10034,6 +10031,7 @@
static final int ID_SHARE = android.R.id.shareText;
static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
static final int ID_REPLACE = android.R.id.replaceText;
+ static final int ID_ASSIST = android.R.id.textAssist;
/**
* Called when a context menu option for the text view is selected. Currently
@@ -10258,33 +10256,30 @@
return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
}
- private TextAssistant mTextAssistant;
-
/**
- * Sets the {@link TextAssistant} for this TextView.
- * If null, this TextView uses the default TextAssistant which comes from the Activity.
+ * Sets the {@link TextClassifier} for this TextView.
*/
- public void setTextAssistant(TextAssistant textAssistant) {
- mTextAssistant = textAssistant;
+ public void setTextClassifier(@Nullable TextClassifier textClassifier) {
+ mTextClassifier = textClassifier;
}
/**
- * Returns the {@link TextAssistant} used by this TextView.
- * If no TextAssistant is set, it'll use the one from this TextView's {@link Activity} or
- * {@link Context}. If no TextAssistant is found, it'll use a no-op TextAssistant.
+ * Returns the {@link TextClassifier} used by this TextView.
+ * If no TextClassifier has been set, this TextView uses the default set by the
+ * {@link TextClassificationManager}.
*/
- public TextAssistant getTextAssistant() {
- if (mTextAssistant != null) {
- return mTextAssistant;
+ @NonNull
+ public TextClassifier getTextClassifier() {
+ if (mTextClassifier == null) {
+ TextClassificationManager tcm =
+ mContext.getSystemService(TextClassificationManager.class);
+ if (tcm != null) {
+ mTextClassifier = tcm.getDefaultTextClassifier();
+ } else {
+ mTextClassifier = TextClassifier.NO_OP;
+ }
}
- if (mContext instanceof Activity) {
- mTextAssistant = ((Activity) mContext).getTextAssistant();
- } else {
- // The context of this TextView should be an Activity. If it is not and no
- // text assistant has been set, return the TextClassificationManager.
- mTextAssistant = mContext.getSystemService(TextClassificationManager.class);
- }
- return mTextAssistant;
+ return mTextClassifier;
}
/**
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index d734d17..ab1d9b9 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -749,10 +749,7 @@
}
} else {
try {
- AppGlobals.getPackageManager().setLastChosenActivity(intent,
- intent.resolveType(getContentResolver()),
- PackageManager.MATCH_DEFAULT_ONLY,
- filter, bestMatch, intent.getComponent());
+ mAdapter.mResolverListController.setLastChosen(intent, filter, bestMatch);
} catch (RemoteException re) {
Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
}
@@ -1312,10 +1309,7 @@
protected boolean rebuildList() {
List<ResolvedComponentInfo> currentResolveList = null;
try {
- final Intent primaryIntent = getTargetIntent();
- mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity(
- primaryIntent, primaryIntent.resolveTypeIfNeeded(getContentResolver()),
- PackageManager.MATCH_DEFAULT_ONLY);
+ mLastChosen = mResolverListController.getLastChosen();
} catch (RemoteException re) {
Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
}
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index f88f6f9..00faf65 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -19,13 +19,16 @@
import android.annotation.WorkerThread;
import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,6 +69,22 @@
}
@VisibleForTesting
+ ResolveInfo getLastChosen() throws RemoteException {
+ return AppGlobals.getPackageManager().getLastChosenActivity(
+ mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.MATCH_DEFAULT_ONLY);
+ }
+
+ @VisibleForTesting
+ void setLastChosen(Intent intent, IntentFilter filter, int match)
+ throws RemoteException {
+ AppGlobals.getPackageManager().setLastChosenActivity(intent,
+ intent.resolveType(mContext.getContentResolver()),
+ PackageManager.MATCH_DEFAULT_ONLY,
+ filter, match, intent.getComponent());
+ }
+
+ @VisibleForTesting
public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
boolean shouldGetResolvedFilter,
boolean shouldGetActivityMetadata,
diff --git a/core/java/com/android/internal/font/IFontManager.aidl b/core/java/com/android/internal/font/IFontManager.aidl
new file mode 100644
index 0000000..52a6262
--- /dev/null
+++ b/core/java/com/android/internal/font/IFontManager.aidl
@@ -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.internal.font;
+
+import android.text.FontConfig;
+
+/**
+ * Interface to the font manager.
+ * @hide
+ */
+interface IFontManager {
+ FontConfig getSystemFonts();
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
index 8d11783..a94b161 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -95,39 +95,32 @@
}
}
+ private static int compareNullableCharSequences(@Nullable CharSequence c1,
+ @Nullable CharSequence c2) {
+ // For historical reasons, an empty text needs to put at the last.
+ final boolean empty1 = TextUtils.isEmpty(c1);
+ final boolean empty2 = TextUtils.isEmpty(c2);
+ if (empty1 || empty2) {
+ return (empty1 ? 1 : 0) - (empty2 ? 1 : 0);
+ }
+ return c1.toString().compareTo(c2.toString());
+ }
+
@Override
public int compareTo(ImeSubtypeListItem other) {
- if (TextUtils.isEmpty(mImeName)) {
- return 1;
+ int result = compareNullableCharSequences(mImeName, other.mImeName);
+ if (result != 0) {
+ return result;
}
- if (TextUtils.isEmpty(other.mImeName)) {
- return -1;
+ result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName);
+ if (result != 0) {
+ return result;
}
- if (!TextUtils.equals(mImeName, other.mImeName)) {
- return mImeName.toString().compareTo(other.mImeName.toString());
+ result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0);
+ if (result != 0) {
+ return result;
}
- if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
- return 0;
- }
- if (mIsSystemLocale) {
- return -1;
- }
- if (other.mIsSystemLocale) {
- return 1;
- }
- if (mIsSystemLanguage) {
- return -1;
- }
- if (other.mIsSystemLanguage) {
- return 1;
- }
- if (TextUtils.isEmpty(mSubtypeName)) {
- return 1;
- }
- if (TextUtils.isEmpty(other.mSubtypeName)) {
- return -1;
- }
- return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
+ return (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0);
}
@Override
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 716997f..c08cd72 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -1080,22 +1080,6 @@
return enabledSubtypes;
}
- // At the initial boot, the settings for input methods are not set,
- // so we need to enable IME in that case.
- public void enableAllIMEsIfThereIsNoEnabledIME() {
- if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
- StringBuilder sb = new StringBuilder();
- final int N = mMethodList.size();
- for (int i = 0; i < N; i++) {
- InputMethodInfo imi = mMethodList.get(i);
- Slog.i(TAG, "Adding: " + imi.getId());
- if (i > 0) sb.append(':');
- sb.append(imi.getId());
- }
- putEnabledInputMethodsStr(sb.toString());
- }
- }
-
public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
mInputMethodSplitter,
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 16c2719..c8bf302 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -16,6 +16,7 @@
package com.android.internal.logging;
import android.content.Context;
+import android.metrics.LogMaker;
import android.os.Build;
import android.view.View;
@@ -37,7 +38,7 @@
}
EventLogTags.writeSysuiViewVisibility(category, 100);
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_OPEN)
.serialize());
}
@@ -48,7 +49,7 @@
}
EventLogTags.writeSysuiViewVisibility(category, 0);
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_CLOSE)
.serialize());
}
@@ -70,7 +71,7 @@
public static void action(Context context, int category) {
EventLogTags.writeSysuiAction(category, "");
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.serialize());
}
@@ -78,7 +79,7 @@
public static void action(Context context, int category, int value) {
EventLogTags.writeSysuiAction(category, Integer.toString(value));
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.setSubtype(value)
.serialize());
@@ -87,16 +88,13 @@
public static void action(Context context, int category, boolean value) {
EventLogTags.writeSysuiAction(category, Boolean.toString(value));
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.setSubtype(value ? 1 : 0)
.serialize());
}
- public static void action(LogBuilder content) {
- //EventLog.writeEvent(524292, content.serialize());
- // Below would be the *right* way to do this, using the generated
- // EventLogTags method, but that doesn't work.
+ public static void action(LogMaker content) {
if (content.getType() == MetricsEvent.TYPE_UNKNOWN) {
content.setType(MetricsEvent.TYPE_ACTION);
}
@@ -109,7 +107,7 @@
throw new IllegalArgumentException("Must define metric category");
}
EventLogTags.writeSysuiAction(category, pkg);
- EventLogTags.writeSysuiMultiAction(new LogBuilder(category)
+ EventLogTags.writeSysuiMultiAction(new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.setPackageName(pkg)
.serialize());
@@ -119,7 +117,7 @@
public static void count(Context context, String name, int value) {
EventLogTags.writeSysuiCount(name, value);
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+ new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
.setCounterName(name)
.setCounterValue(value)
.serialize());
@@ -129,7 +127,7 @@
public static void histogram(Context context, String name, int bucket) {
EventLogTags.writeSysuiHistogram(name, bucket);
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+ new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
.setCounterName(name)
.setCounterBucket(bucket)
.setCounterValue(1)
diff --git a/core/java/com/android/internal/logging/legacy/EventLogCollector.java b/core/java/com/android/internal/logging/legacy/EventLogCollector.java
index 952ae23..eba7d0f 100644
--- a/core/java/com/android/internal/logging/legacy/EventLogCollector.java
+++ b/core/java/com/android/internal/logging/legacy/EventLogCollector.java
@@ -178,7 +178,7 @@
public void readEvents(int[] tags, Collection<Event> events) throws IOException {
// Testing in Android: the Static Final Class Strikes Back!
ArrayList<EventLog.Event> nativeEvents = new ArrayList<>();
- EventLog.readEvents(tags, nativeEvents);
+ EventLog.readEventsOnWrapping(tags, 0L, nativeEvents);
for (EventLog.Event nativeEvent : nativeEvents) {
Event event = new Event(nativeEvent);
events.add(event);
diff --git a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java b/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java
index 7381ff0..91e968b 100644
--- a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java
+++ b/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java
@@ -15,9 +15,7 @@
*/
package com.android.internal.logging.legacy;
-import android.os.Bundle;
-
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.HashMap;
@@ -34,20 +32,20 @@
public static final int TYPE_HISTOGRAM = 2;
public static final int TYPE_EVENT = 3;
- private final Queue<LogBuilder> mQueue;
+ private final Queue<LogMaker> mQueue;
private HashMap<String, Boolean> mConfig;
public LegacyConversionLogger() {
mQueue = new LinkedList<>();
}
- public Queue<LogBuilder> getEvents() {
+ public Queue<LogMaker> getEvents() {
return mQueue;
}
@Override
public void increment(String counterName) {
- LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+ LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
.setCounterName(counterName)
.setCounterValue(1);
mQueue.add(b);
@@ -55,7 +53,7 @@
@Override
public void incrementBy(String counterName, int value) {
- LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+ LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
.setCounterName(counterName)
.setCounterValue(value);
mQueue.add(b);
@@ -63,7 +61,7 @@
@Override
public void incrementIntHistogram(String counterName, int bucket) {
- LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+ LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
.setCounterName(counterName)
.setCounterBucket(bucket)
.setCounterValue(1);
@@ -72,7 +70,7 @@
@Override
public void incrementLongHistogram(String counterName, long bucket) {
- LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+ LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
.setCounterName(counterName)
.setCounterBucket(bucket)
.setCounterValue(1);
@@ -80,16 +78,16 @@
}
@Override
- public LogBuilder obtain() {
- return new LogBuilder(MetricsEvent.VIEW_UNKNOWN);
+ public LogMaker obtain() {
+ return new LogMaker(MetricsEvent.VIEW_UNKNOWN);
}
@Override
- public void dispose(LogBuilder proto) {
+ public void dispose(LogMaker proto) {
}
@Override
- public void addEvent(LogBuilder proto) {
+ public void addEvent(LogMaker proto) {
mQueue.add(proto);
}
diff --git a/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java b/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java
index 6bede24..df08ee0 100644
--- a/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java
+++ b/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -62,7 +62,7 @@
category = GESTURE_TYPE_MAP[type];
}
if (category != MetricsEvent.VIEW_UNKNOWN) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(category);
proto.setType(MetricsEvent.TYPE_ACTION);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
index 67b84e9..79f3eb8 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -47,7 +47,7 @@
if (mKey.parse((String) operands[0])) {
int index = (Integer) operands[1];
parseTimes(operands, 2);
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION);
proto.setType(MetricsEvent.TYPE_ACTION);
proto.setSubtype(index);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java b/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
index 761197b..9548fb0 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
@@ -17,8 +17,8 @@
import android.util.Log;
+import android.metrics.LogMaker;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.LogBuilder;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -58,7 +58,7 @@
final boolean blink = ((Integer) operands[3]) == 1;
if (mKey.parse(keyString)) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ALERT);
proto.setType(MetricsEvent.TYPE_OPEN);
proto.setSubtype((buzz ? BUZZ : 0) | (beep ? BEEP : 0) | (blink ? BLINK : 0));
diff --git a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java b/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
index 0cab1a8..80eb004 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -78,7 +78,7 @@
if (mKey.parse(keyString)) {
if (intentional) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
proto.setType(MetricsEvent.TYPE_DISMISS);
proto.setSubtype(reason);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
index eeae0a8..eee4701 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -46,7 +46,7 @@
try {
if (mKey.parse((String) operands[0])) {
parseTimes(operands, 1);
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
proto.setType(MetricsEvent.TYPE_ACTION);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java b/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
index d44b8b1..84cd999 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -52,7 +52,7 @@
if (!byUser || !expanded) {
return;
}
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
proto.setType(MetricsEvent.TYPE_DETAIL);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
index 662295b..a064a2e 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
@@ -15,7 +15,7 @@
*/
package com.android.internal.logging.legacy;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -33,7 +33,7 @@
@Override
public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_PANEL);
proto.setType(MetricsEvent.TYPE_CLOSE);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
index 0566f0b..4d19564e 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -47,7 +47,7 @@
}
}
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_PANEL);
proto.setType(MetricsEvent.TYPE_OPEN);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java b/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
index 9185b91..2d2cd909 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -53,7 +53,7 @@
}
if (mKey.parse(keyString)) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
proto.setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java b/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java
index 3bf0f9d..e9baf9b 100644
--- a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java
+++ b/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -48,7 +48,7 @@
boolean state = (((Integer) operands[0]).intValue()) == 1;
int why = ((Integer) operands[1]).intValue();
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.SCREEN);
proto.setType(state ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java b/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java
index 23abec4..226253f 100644
--- a/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java
+++ b/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -56,7 +56,7 @@
view = MetricsEvent.BOUNCER;
}
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(view);
proto.setType(type);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
index 7f91f92..1148ee5 100644
--- a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
+++ b/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -60,7 +60,7 @@
}
if (operands.length > 0) {
int category = ((Integer) operands[0]).intValue();
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(category);
proto.setType(MetricsEvent.TYPE_ACTION);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java
index f9b2f49..0c77b7a 100644
--- a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java
+++ b/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java
@@ -17,8 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import android.metrics.LogMaker;
/**
* ...and one parser to rule them all.
@@ -39,7 +38,7 @@
public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
final boolean debug = Util.debug();
try {
- logger.addEvent(new LogBuilder(operands).setTimestamp(eventTimeMs));
+ logger.addEvent(new LogMaker(operands).setTimestamp(eventTimeMs));
} catch (ClassCastException e) {
if (debug) {
Log.e(TAG, "unexpected operand type: ", e);
diff --git a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java b/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
index 5d5aec0..1223b8d 100644
--- a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
+++ b/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -41,7 +41,7 @@
int category = ((Integer) operands[0]).intValue();
boolean visibility = ((Integer) operands[1]).intValue() != 0;
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(category);
proto.setType(visibility ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/TagParser.java b/core/java/com/android/internal/logging/legacy/TagParser.java
index c62d084..3bffdd5 100755
--- a/core/java/com/android/internal/logging/legacy/TagParser.java
+++ b/core/java/com/android/internal/logging/legacy/TagParser.java
@@ -17,8 +17,8 @@
import android.util.Log;
+import android.metrics.LogMaker;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.LogBuilder;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -87,7 +87,7 @@
}
}
- public void filltimes(LogBuilder proto) {
+ public void filltimes(LogMaker proto) {
if (mSinceCreationMillis != 0) {
proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS,
mSinceCreationMillis);
diff --git a/core/java/com/android/internal/logging/legacy/TronLogger.java b/core/java/com/android/internal/logging/legacy/TronLogger.java
index dabe314..ee9341a 100644
--- a/core/java/com/android/internal/logging/legacy/TronLogger.java
+++ b/core/java/com/android/internal/logging/legacy/TronLogger.java
@@ -15,7 +15,7 @@
*/
package com.android.internal.logging.legacy;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
/**
* An entity that knows how to log events and counters.
@@ -34,12 +34,12 @@
void incrementLongHistogram(String counterName, long bucket);
/** Obtain a SystemUiEvent proto, must release this with dispose() or addEvent(). */
- LogBuilder obtain();
+ LogMaker obtain();
- void dispose(LogBuilder proto);
+ void dispose(LogMaker proto);
/** Submit an event to be logged. Logger will dispose of proto. */
- void addEvent(LogBuilder proto);
+ void addEvent(LogMaker proto);
/** Get a config flag. */
boolean getConfig(String configName);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5fd68e6..c34c379 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -9713,7 +9713,7 @@
resetAllStatsLocked();
if (chargeUAh > 0) {
// Only use the reported coulomb charge value if it is supported and reported.
- mEstimatedBatteryCapacity = (int) ((level / 100.0) * (chargeUAh / 1000));
+ mEstimatedBatteryCapacity = (int) ((chargeUAh / 1000) / (level / 100.0));
}
mDischargeStartLevel = level;
reset = true;
diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
index fb0edea..ebc2c71 100644
--- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
+++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
@@ -84,7 +84,8 @@
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
- MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
return true;
}
@@ -215,7 +216,8 @@
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (!event.isCanceled()) {
- MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
}
return true;
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index e68ebc4..84195b2 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -1856,27 +1856,25 @@
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
- int direction = 0;
- switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- direction = AudioManager.ADJUST_RAISE;
- break;
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- direction = AudioManager.ADJUST_LOWER;
- break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- direction = AudioManager.ADJUST_TOGGLE_MUTE;
- break;
- }
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
+ int direction = 0;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ direction = AudioManager.ADJUST_RAISE;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ direction = AudioManager.ADJUST_LOWER;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ direction = AudioManager.ADJUST_TOGGLE_MUTE;
+ break;
+ }
mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
} else {
- MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
- mVolumeControlStreamType, direction,
- AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE
- | AudioManager.FLAG_FROM_KEY);
+ MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
+ event, mVolumeControlStreamType, false);
}
return true;
}
@@ -1954,15 +1952,15 @@
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN: {
- final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
- | AudioManager.FLAG_FROM_KEY;
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
+ final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
+ | AudioManager.FLAG_FROM_KEY;
mMediaController.adjustVolume(0, flags);
} else {
- MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
- mVolumeControlStreamType, 0, flags);
+ MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
+ event, mVolumeControlStreamType, false);
}
return true;
}
@@ -1971,7 +1969,8 @@
// doesn't have one of these. In this case, we execute it here and
// eat the event instead, because we have mVolumeControlStreamType
// and they don't.
- getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
+ MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
return true;
}
// These are all the recognized media key codes in
diff --git a/core/java/com/android/internal/widget/AdapterHelper.java b/core/java/com/android/internal/widget/AdapterHelper.java
new file mode 100644
index 0000000..f47d430
--- /dev/null
+++ b/core/java/com/android/internal/widget/AdapterHelper.java
@@ -0,0 +1,775 @@
+/*
+ * 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.internal.widget;
+
+import android.util.Log;
+import android.util.Pools;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Helper class that can enqueue and process adapter update operations.
+ * <p>
+ * To support animations, RecyclerView presents an older version the Adapter to best represent
+ * previous state of the layout. Sometimes, this is not trivial when items are removed that were
+ * not laid out, in which case, RecyclerView has no way of providing that item's view for
+ * animations.
+ * <p>
+ * AdapterHelper creates an UpdateOp for each adapter data change then pre-processes them. During
+ * pre processing, AdapterHelper finds out which UpdateOps can be deferred to second layout pass
+ * and which cannot. For the UpdateOps that cannot be deferred, AdapterHelper will change them
+ * according to previously deferred operation and dispatch them before the first layout pass. It
+ * also takes care of updating deferred UpdateOps since order of operations is changed by this
+ * process.
+ * <p>
+ * Although operations may be forwarded to LayoutManager in different orders, resulting data set
+ * is guaranteed to be the consistent.
+ */
+class AdapterHelper implements OpReorderer.Callback {
+
+ static final int POSITION_TYPE_INVISIBLE = 0;
+
+ static final int POSITION_TYPE_NEW_OR_LAID_OUT = 1;
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "AHT";
+
+ private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
+
+ final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
+
+ final ArrayList<UpdateOp> mPostponedList = new ArrayList<UpdateOp>();
+
+ final Callback mCallback;
+
+ Runnable mOnItemProcessedCallback;
+
+ final boolean mDisableRecycler;
+
+ final OpReorderer mOpReorderer;
+
+ private int mExistingUpdateTypes = 0;
+
+ AdapterHelper(Callback callback) {
+ this(callback, false);
+ }
+
+ AdapterHelper(Callback callback, boolean disableRecycler) {
+ mCallback = callback;
+ mDisableRecycler = disableRecycler;
+ mOpReorderer = new OpReorderer(this);
+ }
+
+ AdapterHelper addUpdateOp(UpdateOp... ops) {
+ Collections.addAll(mPendingUpdates, ops);
+ return this;
+ }
+
+ void reset() {
+ recycleUpdateOpsAndClearList(mPendingUpdates);
+ recycleUpdateOpsAndClearList(mPostponedList);
+ mExistingUpdateTypes = 0;
+ }
+
+ void preProcess() {
+ mOpReorderer.reorderOps(mPendingUpdates);
+ final int count = mPendingUpdates.size();
+ for (int i = 0; i < count; i++) {
+ UpdateOp op = mPendingUpdates.get(i);
+ switch (op.cmd) {
+ case UpdateOp.ADD:
+ applyAdd(op);
+ break;
+ case UpdateOp.REMOVE:
+ applyRemove(op);
+ break;
+ case UpdateOp.UPDATE:
+ applyUpdate(op);
+ break;
+ case UpdateOp.MOVE:
+ applyMove(op);
+ break;
+ }
+ if (mOnItemProcessedCallback != null) {
+ mOnItemProcessedCallback.run();
+ }
+ }
+ mPendingUpdates.clear();
+ }
+
+ void consumePostponedUpdates() {
+ final int count = mPostponedList.size();
+ for (int i = 0; i < count; i++) {
+ mCallback.onDispatchSecondPass(mPostponedList.get(i));
+ }
+ recycleUpdateOpsAndClearList(mPostponedList);
+ mExistingUpdateTypes = 0;
+ }
+
+ private void applyMove(UpdateOp op) {
+ // MOVE ops are pre-processed so at this point, we know that item is still in the adapter.
+ // otherwise, it would be converted into a REMOVE operation
+ postponeAndUpdateViewHolders(op);
+ }
+
+ private void applyRemove(UpdateOp op) {
+ int tmpStart = op.positionStart;
+ int tmpCount = 0;
+ int tmpEnd = op.positionStart + op.itemCount;
+ int type = -1;
+ for (int position = op.positionStart; position < tmpEnd; position++) {
+ boolean typeChanged = false;
+ RecyclerView.ViewHolder vh = mCallback.findViewHolder(position);
+ if (vh != null || canFindInPreLayout(position)) {
+ // If a ViewHolder exists or this is a newly added item, we can defer this update
+ // to post layout stage.
+ // * For existing ViewHolders, we'll fake its existence in the pre-layout phase.
+ // * For items that are added and removed in the same process cycle, they won't
+ // have any effect in pre-layout since their add ops are already deferred to
+ // post-layout pass.
+ if (type == POSITION_TYPE_INVISIBLE) {
+ // Looks like we have other updates that we cannot merge with this one.
+ // Create an UpdateOp and dispatch it to LayoutManager.
+ UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null);
+ dispatchAndUpdateViewHolders(newOp);
+ typeChanged = true;
+ }
+ type = POSITION_TYPE_NEW_OR_LAID_OUT;
+ } else {
+ // This update cannot be recovered because we don't have a ViewHolder representing
+ // this position. Instead, post it to LayoutManager immediately
+ if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
+ // Looks like we have other updates that we cannot merge with this one.
+ // Create UpdateOp op and dispatch it to LayoutManager.
+ UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null);
+ postponeAndUpdateViewHolders(newOp);
+ typeChanged = true;
+ }
+ type = POSITION_TYPE_INVISIBLE;
+ }
+ if (typeChanged) {
+ position -= tmpCount; // also equal to tmpStart
+ tmpEnd -= tmpCount;
+ tmpCount = 1;
+ } else {
+ tmpCount++;
+ }
+ }
+ if (tmpCount != op.itemCount) { // all 1 effect
+ recycleUpdateOp(op);
+ op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null);
+ }
+ if (type == POSITION_TYPE_INVISIBLE) {
+ dispatchAndUpdateViewHolders(op);
+ } else {
+ postponeAndUpdateViewHolders(op);
+ }
+ }
+
+ private void applyUpdate(UpdateOp op) {
+ int tmpStart = op.positionStart;
+ int tmpCount = 0;
+ int tmpEnd = op.positionStart + op.itemCount;
+ int type = -1;
+ for (int position = op.positionStart; position < tmpEnd; position++) {
+ RecyclerView.ViewHolder vh = mCallback.findViewHolder(position);
+ if (vh != null || canFindInPreLayout(position)) { // deferred
+ if (type == POSITION_TYPE_INVISIBLE) {
+ UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount,
+ op.payload);
+ dispatchAndUpdateViewHolders(newOp);
+ tmpCount = 0;
+ tmpStart = position;
+ }
+ type = POSITION_TYPE_NEW_OR_LAID_OUT;
+ } else { // applied
+ if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
+ UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount,
+ op.payload);
+ postponeAndUpdateViewHolders(newOp);
+ tmpCount = 0;
+ tmpStart = position;
+ }
+ type = POSITION_TYPE_INVISIBLE;
+ }
+ tmpCount++;
+ }
+ if (tmpCount != op.itemCount) { // all 1 effect
+ Object payload = op.payload;
+ recycleUpdateOp(op);
+ op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, payload);
+ }
+ if (type == POSITION_TYPE_INVISIBLE) {
+ dispatchAndUpdateViewHolders(op);
+ } else {
+ postponeAndUpdateViewHolders(op);
+ }
+ }
+
+ private void dispatchAndUpdateViewHolders(UpdateOp op) {
+ // tricky part.
+ // traverse all postpones and revert their changes on this op if necessary, apply updated
+ // dispatch to them since now they are after this op.
+ if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) {
+ throw new IllegalArgumentException("should not dispatch add or move for pre layout");
+ }
+ if (DEBUG) {
+ Log.d(TAG, "dispatch (pre)" + op);
+ Log.d(TAG, "postponed state before:");
+ for (UpdateOp updateOp : mPostponedList) {
+ Log.d(TAG, updateOp.toString());
+ }
+ Log.d(TAG, "----");
+ }
+
+ // handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial
+ // TODO Since move ops are pushed to end, we should not need this anymore
+ int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd);
+ if (DEBUG) {
+ Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart);
+ }
+ int tmpCnt = 1;
+ int offsetPositionForPartial = op.positionStart;
+ final int positionMultiplier;
+ switch (op.cmd) {
+ case UpdateOp.UPDATE:
+ positionMultiplier = 1;
+ break;
+ case UpdateOp.REMOVE:
+ positionMultiplier = 0;
+ break;
+ default:
+ throw new IllegalArgumentException("op should be remove or update." + op);
+ }
+ for (int p = 1; p < op.itemCount; p++) {
+ final int pos = op.positionStart + (positionMultiplier * p);
+ int updatedPos = updatePositionWithPostponed(pos, op.cmd);
+ if (DEBUG) {
+ Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos);
+ }
+ boolean continuous = false;
+ switch (op.cmd) {
+ case UpdateOp.UPDATE:
+ continuous = updatedPos == tmpStart + 1;
+ break;
+ case UpdateOp.REMOVE:
+ continuous = updatedPos == tmpStart;
+ break;
+ }
+ if (continuous) {
+ tmpCnt++;
+ } else {
+ // need to dispatch this separately
+ UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, op.payload);
+ if (DEBUG) {
+ Log.d(TAG, "need to dispatch separately " + tmp);
+ }
+ dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
+ recycleUpdateOp(tmp);
+ if (op.cmd == UpdateOp.UPDATE) {
+ offsetPositionForPartial += tmpCnt;
+ }
+ tmpStart = updatedPos; // need to remove previously dispatched
+ tmpCnt = 1;
+ }
+ }
+ Object payload = op.payload;
+ recycleUpdateOp(op);
+ if (tmpCnt > 0) {
+ UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, payload);
+ if (DEBUG) {
+ Log.d(TAG, "dispatching:" + tmp);
+ }
+ dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
+ recycleUpdateOp(tmp);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "post dispatch");
+ Log.d(TAG, "postponed state after:");
+ for (UpdateOp updateOp : mPostponedList) {
+ Log.d(TAG, updateOp.toString());
+ }
+ Log.d(TAG, "----");
+ }
+ }
+
+ void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) {
+ mCallback.onDispatchFirstPass(op);
+ switch (op.cmd) {
+ case UpdateOp.REMOVE:
+ mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount);
+ break;
+ case UpdateOp.UPDATE:
+ mCallback.markViewHoldersUpdated(offsetStart, op.itemCount, op.payload);
+ break;
+ default:
+ throw new IllegalArgumentException("only remove and update ops can be dispatched"
+ + " in first pass");
+ }
+ }
+
+ private int updatePositionWithPostponed(int pos, int cmd) {
+ final int count = mPostponedList.size();
+ for (int i = count - 1; i >= 0; i--) {
+ UpdateOp postponed = mPostponedList.get(i);
+ if (postponed.cmd == UpdateOp.MOVE) {
+ int start, end;
+ if (postponed.positionStart < postponed.itemCount) {
+ start = postponed.positionStart;
+ end = postponed.itemCount;
+ } else {
+ start = postponed.itemCount;
+ end = postponed.positionStart;
+ }
+ if (pos >= start && pos <= end) {
+ //i'm affected
+ if (start == postponed.positionStart) {
+ if (cmd == UpdateOp.ADD) {
+ postponed.itemCount++;
+ } else if (cmd == UpdateOp.REMOVE) {
+ postponed.itemCount--;
+ }
+ // op moved to left, move it right to revert
+ pos++;
+ } else {
+ if (cmd == UpdateOp.ADD) {
+ postponed.positionStart++;
+ } else if (cmd == UpdateOp.REMOVE) {
+ postponed.positionStart--;
+ }
+ // op was moved right, move left to revert
+ pos--;
+ }
+ } else if (pos < postponed.positionStart) {
+ // postponed MV is outside the dispatched OP. if it is before, offset
+ if (cmd == UpdateOp.ADD) {
+ postponed.positionStart++;
+ postponed.itemCount++;
+ } else if (cmd == UpdateOp.REMOVE) {
+ postponed.positionStart--;
+ postponed.itemCount--;
+ }
+ }
+ } else {
+ if (postponed.positionStart <= pos) {
+ if (postponed.cmd == UpdateOp.ADD) {
+ pos -= postponed.itemCount;
+ } else if (postponed.cmd == UpdateOp.REMOVE) {
+ pos += postponed.itemCount;
+ }
+ } else {
+ if (cmd == UpdateOp.ADD) {
+ postponed.positionStart++;
+ } else if (cmd == UpdateOp.REMOVE) {
+ postponed.positionStart--;
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "dispath (step" + i + ")");
+ Log.d(TAG, "postponed state:" + i + ", pos:" + pos);
+ for (UpdateOp updateOp : mPostponedList) {
+ Log.d(TAG, updateOp.toString());
+ }
+ Log.d(TAG, "----");
+ }
+ }
+ for (int i = mPostponedList.size() - 1; i >= 0; i--) {
+ UpdateOp op = mPostponedList.get(i);
+ if (op.cmd == UpdateOp.MOVE) {
+ if (op.itemCount == op.positionStart || op.itemCount < 0) {
+ mPostponedList.remove(i);
+ recycleUpdateOp(op);
+ }
+ } else if (op.itemCount <= 0) {
+ mPostponedList.remove(i);
+ recycleUpdateOp(op);
+ }
+ }
+ return pos;
+ }
+
+ private boolean canFindInPreLayout(int position) {
+ final int count = mPostponedList.size();
+ for (int i = 0; i < count; i++) {
+ UpdateOp op = mPostponedList.get(i);
+ if (op.cmd == UpdateOp.MOVE) {
+ if (findPositionOffset(op.itemCount, i + 1) == position) {
+ return true;
+ }
+ } else if (op.cmd == UpdateOp.ADD) {
+ // TODO optimize.
+ final int end = op.positionStart + op.itemCount;
+ for (int pos = op.positionStart; pos < end; pos++) {
+ if (findPositionOffset(pos, i + 1) == position) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private void applyAdd(UpdateOp op) {
+ postponeAndUpdateViewHolders(op);
+ }
+
+ private void postponeAndUpdateViewHolders(UpdateOp op) {
+ if (DEBUG) {
+ Log.d(TAG, "postponing " + op);
+ }
+ mPostponedList.add(op);
+ switch (op.cmd) {
+ case UpdateOp.ADD:
+ mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
+ break;
+ case UpdateOp.MOVE:
+ mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
+ break;
+ case UpdateOp.REMOVE:
+ mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
+ op.itemCount);
+ break;
+ case UpdateOp.UPDATE:
+ mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown update op type for " + op);
+ }
+ }
+
+ boolean hasPendingUpdates() {
+ return mPendingUpdates.size() > 0;
+ }
+
+ boolean hasAnyUpdateTypes(int updateTypes) {
+ return (mExistingUpdateTypes & updateTypes) != 0;
+ }
+
+ int findPositionOffset(int position) {
+ return findPositionOffset(position, 0);
+ }
+
+ int findPositionOffset(int position, int firstPostponedItem) {
+ int count = mPostponedList.size();
+ for (int i = firstPostponedItem; i < count; ++i) {
+ UpdateOp op = mPostponedList.get(i);
+ if (op.cmd == UpdateOp.MOVE) {
+ if (op.positionStart == position) {
+ position = op.itemCount;
+ } else {
+ if (op.positionStart < position) {
+ position--; // like a remove
+ }
+ if (op.itemCount <= position) {
+ position++; // like an add
+ }
+ }
+ } else if (op.positionStart <= position) {
+ if (op.cmd == UpdateOp.REMOVE) {
+ if (position < op.positionStart + op.itemCount) {
+ return -1;
+ }
+ position -= op.itemCount;
+ } else if (op.cmd == UpdateOp.ADD) {
+ position += op.itemCount;
+ }
+ }
+ }
+ return position;
+ }
+
+ /**
+ * @return True if updates should be processed.
+ */
+ boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ if (itemCount < 1) {
+ return false;
+ }
+ mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
+ mExistingUpdateTypes |= UpdateOp.UPDATE;
+ return mPendingUpdates.size() == 1;
+ }
+
+ /**
+ * @return True if updates should be processed.
+ */
+ boolean onItemRangeInserted(int positionStart, int itemCount) {
+ if (itemCount < 1) {
+ return false;
+ }
+ mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
+ mExistingUpdateTypes |= UpdateOp.ADD;
+ return mPendingUpdates.size() == 1;
+ }
+
+ /**
+ * @return True if updates should be processed.
+ */
+ boolean onItemRangeRemoved(int positionStart, int itemCount) {
+ if (itemCount < 1) {
+ return false;
+ }
+ mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
+ mExistingUpdateTypes |= UpdateOp.REMOVE;
+ return mPendingUpdates.size() == 1;
+ }
+
+ /**
+ * @return True if updates should be processed.
+ */
+ boolean onItemRangeMoved(int from, int to, int itemCount) {
+ if (from == to) {
+ return false; // no-op
+ }
+ if (itemCount != 1) {
+ throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
+ }
+ mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to, null));
+ mExistingUpdateTypes |= UpdateOp.MOVE;
+ return mPendingUpdates.size() == 1;
+ }
+
+ /**
+ * Skips pre-processing and applies all updates in one pass.
+ */
+ void consumeUpdatesInOnePass() {
+ // we still consume postponed updates (if there is) in case there was a pre-process call
+ // w/o a matching consumePostponedUpdates.
+ consumePostponedUpdates();
+ final int count = mPendingUpdates.size();
+ for (int i = 0; i < count; i++) {
+ UpdateOp op = mPendingUpdates.get(i);
+ switch (op.cmd) {
+ case UpdateOp.ADD:
+ mCallback.onDispatchSecondPass(op);
+ mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
+ break;
+ case UpdateOp.REMOVE:
+ mCallback.onDispatchSecondPass(op);
+ mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
+ break;
+ case UpdateOp.UPDATE:
+ mCallback.onDispatchSecondPass(op);
+ mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
+ break;
+ case UpdateOp.MOVE:
+ mCallback.onDispatchSecondPass(op);
+ mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
+ break;
+ }
+ if (mOnItemProcessedCallback != null) {
+ mOnItemProcessedCallback.run();
+ }
+ }
+ recycleUpdateOpsAndClearList(mPendingUpdates);
+ mExistingUpdateTypes = 0;
+ }
+
+ public int applyPendingUpdatesToPosition(int position) {
+ final int size = mPendingUpdates.size();
+ for (int i = 0; i < size; i++) {
+ UpdateOp op = mPendingUpdates.get(i);
+ switch (op.cmd) {
+ case UpdateOp.ADD:
+ if (op.positionStart <= position) {
+ position += op.itemCount;
+ }
+ break;
+ case UpdateOp.REMOVE:
+ if (op.positionStart <= position) {
+ final int end = op.positionStart + op.itemCount;
+ if (end > position) {
+ return RecyclerView.NO_POSITION;
+ }
+ position -= op.itemCount;
+ }
+ break;
+ case UpdateOp.MOVE:
+ if (op.positionStart == position) {
+ position = op.itemCount; //position end
+ } else {
+ if (op.positionStart < position) {
+ position -= 1;
+ }
+ if (op.itemCount <= position) {
+ position += 1;
+ }
+ }
+ break;
+ }
+ }
+ return position;
+ }
+
+ boolean hasUpdates() {
+ return !mPostponedList.isEmpty() && !mPendingUpdates.isEmpty();
+ }
+
+ /**
+ * Queued operation to happen when child views are updated.
+ */
+ static class UpdateOp {
+
+ static final int ADD = 1;
+
+ static final int REMOVE = 1 << 1;
+
+ static final int UPDATE = 1 << 2;
+
+ static final int MOVE = 1 << 3;
+
+ static final int POOL_SIZE = 30;
+
+ int cmd;
+
+ int positionStart;
+
+ Object payload;
+
+ // holds the target position if this is a MOVE
+ int itemCount;
+
+ UpdateOp(int cmd, int positionStart, int itemCount, Object payload) {
+ this.cmd = cmd;
+ this.positionStart = positionStart;
+ this.itemCount = itemCount;
+ this.payload = payload;
+ }
+
+ String cmdToString() {
+ switch (cmd) {
+ case ADD:
+ return "add";
+ case REMOVE:
+ return "rm";
+ case UPDATE:
+ return "up";
+ case MOVE:
+ return "mv";
+ }
+ return "??";
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toHexString(System.identityHashCode(this))
+ + "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount
+ + ",p:" + payload + "]";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ UpdateOp op = (UpdateOp) o;
+
+ if (cmd != op.cmd) {
+ return false;
+ }
+ if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) {
+ // reverse of this is also true
+ if (itemCount == op.positionStart && positionStart == op.itemCount) {
+ return true;
+ }
+ }
+ if (itemCount != op.itemCount) {
+ return false;
+ }
+ if (positionStart != op.positionStart) {
+ return false;
+ }
+ if (payload != null) {
+ if (!payload.equals(op.payload)) {
+ return false;
+ }
+ } else if (op.payload != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = cmd;
+ result = 31 * result + positionStart;
+ result = 31 * result + itemCount;
+ return result;
+ }
+ }
+
+ @Override
+ public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) {
+ UpdateOp op = mUpdateOpPool.acquire();
+ if (op == null) {
+ op = new UpdateOp(cmd, positionStart, itemCount, payload);
+ } else {
+ op.cmd = cmd;
+ op.positionStart = positionStart;
+ op.itemCount = itemCount;
+ op.payload = payload;
+ }
+ return op;
+ }
+
+ @Override
+ public void recycleUpdateOp(UpdateOp op) {
+ if (!mDisableRecycler) {
+ op.payload = null;
+ mUpdateOpPool.release(op);
+ }
+ }
+
+ void recycleUpdateOpsAndClearList(List<UpdateOp> ops) {
+ final int count = ops.size();
+ for (int i = 0; i < count; i++) {
+ recycleUpdateOp(ops.get(i));
+ }
+ ops.clear();
+ }
+
+ /**
+ * Contract between AdapterHelper and RecyclerView.
+ */
+ interface Callback {
+
+ RecyclerView.ViewHolder findViewHolder(int position);
+
+ void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
+
+ void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
+
+ void markViewHoldersUpdated(int positionStart, int itemCount, Object payloads);
+
+ void onDispatchFirstPass(UpdateOp updateOp);
+
+ void onDispatchSecondPass(UpdateOp updateOp);
+
+ void offsetPositionsForAdd(int positionStart, int itemCount);
+
+ void offsetPositionsForMove(int from, int to);
+ }
+}
diff --git a/core/java/com/android/internal/widget/ChildHelper.java b/core/java/com/android/internal/widget/ChildHelper.java
new file mode 100644
index 0000000..e9136d0
--- /dev/null
+++ b/core/java/com/android/internal/widget/ChildHelper.java
@@ -0,0 +1,538 @@
+/*
+ * 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.internal.widget;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to manage children.
+ * <p>
+ * It wraps a RecyclerView and adds ability to hide some children. There are two sets of methods
+ * provided by this class. <b>Regular</b> methods are the ones that replicate ViewGroup methods
+ * like getChildAt, getChildCount etc. These methods ignore hidden children.
+ * <p>
+ * When RecyclerView needs direct access to the view group children, it can call unfiltered
+ * methods like get getUnfilteredChildCount or getUnfilteredChildAt.
+ */
+class ChildHelper {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "ChildrenHelper";
+
+ final Callback mCallback;
+
+ final Bucket mBucket;
+
+ final List<View> mHiddenViews;
+
+ ChildHelper(Callback callback) {
+ mCallback = callback;
+ mBucket = new Bucket();
+ mHiddenViews = new ArrayList<View>();
+ }
+
+ /**
+ * Marks a child view as hidden
+ *
+ * @param child View to hide.
+ */
+ private void hideViewInternal(View child) {
+ mHiddenViews.add(child);
+ mCallback.onEnteredHiddenState(child);
+ }
+
+ /**
+ * Unmarks a child view as hidden.
+ *
+ * @param child View to hide.
+ */
+ private boolean unhideViewInternal(View child) {
+ if (mHiddenViews.remove(child)) {
+ mCallback.onLeftHiddenState(child);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Adds a view to the ViewGroup
+ *
+ * @param child View to add.
+ * @param hidden If set to true, this item will be invisible from regular methods.
+ */
+ void addView(View child, boolean hidden) {
+ addView(child, -1, hidden);
+ }
+
+ /**
+ * Add a view to the ViewGroup at an index
+ *
+ * @param child View to add.
+ * @param index Index of the child from the regular perspective (excluding hidden views).
+ * ChildHelper offsets this index to actual ViewGroup index.
+ * @param hidden If set to true, this item will be invisible from regular methods.
+ */
+ void addView(View child, int index, boolean hidden) {
+ final int offset;
+ if (index < 0) {
+ offset = mCallback.getChildCount();
+ } else {
+ offset = getOffset(index);
+ }
+ mBucket.insert(offset, hidden);
+ if (hidden) {
+ hideViewInternal(child);
+ }
+ mCallback.addView(child, offset);
+ if (DEBUG) {
+ Log.d(TAG, "addViewAt " + index + ",h:" + hidden + ", " + this);
+ }
+ }
+
+ private int getOffset(int index) {
+ if (index < 0) {
+ return -1; //anything below 0 won't work as diff will be undefined.
+ }
+ final int limit = mCallback.getChildCount();
+ int offset = index;
+ while (offset < limit) {
+ final int removedBefore = mBucket.countOnesBefore(offset);
+ final int diff = index - (offset - removedBefore);
+ if (diff == 0) {
+ while (mBucket.get(offset)) { // ensure this offset is not hidden
+ offset++;
+ }
+ return offset;
+ } else {
+ offset += diff;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes the provided View from underlying RecyclerView.
+ *
+ * @param view The view to remove.
+ */
+ void removeView(View view) {
+ int index = mCallback.indexOfChild(view);
+ if (index < 0) {
+ return;
+ }
+ if (mBucket.remove(index)) {
+ unhideViewInternal(view);
+ }
+ mCallback.removeViewAt(index);
+ if (DEBUG) {
+ Log.d(TAG, "remove View off:" + index + "," + this);
+ }
+ }
+
+ /**
+ * Removes the view at the provided index from RecyclerView.
+ *
+ * @param index Index of the child from the regular perspective (excluding hidden views).
+ * ChildHelper offsets this index to actual ViewGroup index.
+ */
+ void removeViewAt(int index) {
+ final int offset = getOffset(index);
+ final View view = mCallback.getChildAt(offset);
+ if (view == null) {
+ return;
+ }
+ if (mBucket.remove(offset)) {
+ unhideViewInternal(view);
+ }
+ mCallback.removeViewAt(offset);
+ if (DEBUG) {
+ Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);
+ }
+ }
+
+ /**
+ * Returns the child at provided index.
+ *
+ * @param index Index of the child to return in regular perspective.
+ */
+ View getChildAt(int index) {
+ final int offset = getOffset(index);
+ return mCallback.getChildAt(offset);
+ }
+
+ /**
+ * Removes all views from the ViewGroup including the hidden ones.
+ */
+ void removeAllViewsUnfiltered() {
+ mBucket.reset();
+ for (int i = mHiddenViews.size() - 1; i >= 0; i--) {
+ mCallback.onLeftHiddenState(mHiddenViews.get(i));
+ mHiddenViews.remove(i);
+ }
+ mCallback.removeAllViews();
+ if (DEBUG) {
+ Log.d(TAG, "removeAllViewsUnfiltered");
+ }
+ }
+
+ /**
+ * This can be used to find a disappearing view by position.
+ *
+ * @param position The adapter position of the item.
+ * @return A hidden view with a valid ViewHolder that matches the position.
+ */
+ View findHiddenNonRemovedView(int position) {
+ final int count = mHiddenViews.size();
+ for (int i = 0; i < count; i++) {
+ final View view = mHiddenViews.get(i);
+ RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view);
+ if (holder.getLayoutPosition() == position
+ && !holder.isInvalid()
+ && !holder.isRemoved()) {
+ return view;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Attaches the provided view to the underlying ViewGroup.
+ *
+ * @param child Child to attach.
+ * @param index Index of the child to attach in regular perspective.
+ * @param layoutParams LayoutParams for the child.
+ * @param hidden If set to true, this item will be invisible to the regular methods.
+ */
+ void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams,
+ boolean hidden) {
+ final int offset;
+ if (index < 0) {
+ offset = mCallback.getChildCount();
+ } else {
+ offset = getOffset(index);
+ }
+ mBucket.insert(offset, hidden);
+ if (hidden) {
+ hideViewInternal(child);
+ }
+ mCallback.attachViewToParent(child, offset, layoutParams);
+ if (DEBUG) {
+ Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + ","
+ + "h:" + hidden + ", " + this);
+ }
+ }
+
+ /**
+ * Returns the number of children that are not hidden.
+ *
+ * @return Number of children that are not hidden.
+ * @see #getChildAt(int)
+ */
+ int getChildCount() {
+ return mCallback.getChildCount() - mHiddenViews.size();
+ }
+
+ /**
+ * Returns the total number of children.
+ *
+ * @return The total number of children including the hidden views.
+ * @see #getUnfilteredChildAt(int)
+ */
+ int getUnfilteredChildCount() {
+ return mCallback.getChildCount();
+ }
+
+ /**
+ * Returns a child by ViewGroup offset. ChildHelper won't offset this index.
+ *
+ * @param index ViewGroup index of the child to return.
+ * @return The view in the provided index.
+ */
+ View getUnfilteredChildAt(int index) {
+ return mCallback.getChildAt(index);
+ }
+
+ /**
+ * Detaches the view at the provided index.
+ *
+ * @param index Index of the child to return in regular perspective.
+ */
+ void detachViewFromParent(int index) {
+ final int offset = getOffset(index);
+ mBucket.remove(offset);
+ mCallback.detachViewFromParent(offset);
+ if (DEBUG) {
+ Log.d(TAG, "detach view from parent " + index + ", off:" + offset);
+ }
+ }
+
+ /**
+ * Returns the index of the child in regular perspective.
+ *
+ * @param child The child whose index will be returned.
+ * @return The regular perspective index of the child or -1 if it does not exists.
+ */
+ int indexOfChild(View child) {
+ final int index = mCallback.indexOfChild(child);
+ if (index == -1) {
+ return -1;
+ }
+ if (mBucket.get(index)) {
+ if (DEBUG) {
+ throw new IllegalArgumentException("cannot get index of a hidden child");
+ } else {
+ return -1;
+ }
+ }
+ // reverse the index
+ return index - mBucket.countOnesBefore(index);
+ }
+
+ /**
+ * Returns whether a View is visible to LayoutManager or not.
+ *
+ * @param view The child view to check. Should be a child of the Callback.
+ * @return True if the View is not visible to LayoutManager
+ */
+ boolean isHidden(View view) {
+ return mHiddenViews.contains(view);
+ }
+
+ /**
+ * Marks a child view as hidden.
+ *
+ * @param view The view to hide.
+ */
+ void hide(View view) {
+ final int offset = mCallback.indexOfChild(view);
+ if (offset < 0) {
+ throw new IllegalArgumentException("view is not a child, cannot hide " + view);
+ }
+ if (DEBUG && mBucket.get(offset)) {
+ throw new RuntimeException("trying to hide same view twice, how come ? " + view);
+ }
+ mBucket.set(offset);
+ hideViewInternal(view);
+ if (DEBUG) {
+ Log.d(TAG, "hiding child " + view + " at offset " + offset + ", " + this);
+ }
+ }
+
+ /**
+ * Moves a child view from hidden list to regular list.
+ * Calling this method should probably be followed by a detach, otherwise, it will suddenly
+ * show up in LayoutManager's children list.
+ *
+ * @param view The hidden View to unhide
+ */
+ void unhide(View view) {
+ final int offset = mCallback.indexOfChild(view);
+ if (offset < 0) {
+ throw new IllegalArgumentException("view is not a child, cannot hide " + view);
+ }
+ if (!mBucket.get(offset)) {
+ throw new RuntimeException("trying to unhide a view that was not hidden" + view);
+ }
+ mBucket.clear(offset);
+ unhideViewInternal(view);
+ }
+
+ @Override
+ public String toString() {
+ return mBucket.toString() + ", hidden list:" + mHiddenViews.size();
+ }
+
+ /**
+ * Removes a view from the ViewGroup if it is hidden.
+ *
+ * @param view The view to remove.
+ * @return True if the View is found and it is hidden. False otherwise.
+ */
+ boolean removeViewIfHidden(View view) {
+ final int index = mCallback.indexOfChild(view);
+ if (index == -1) {
+ if (unhideViewInternal(view) && DEBUG) {
+ throw new IllegalStateException("view is in hidden list but not in view group");
+ }
+ return true;
+ }
+ if (mBucket.get(index)) {
+ mBucket.remove(index);
+ if (!unhideViewInternal(view) && DEBUG) {
+ throw new IllegalStateException(
+ "removed a hidden view but it is not in hidden views list");
+ }
+ mCallback.removeViewAt(index);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Bitset implementation that provides methods to offset indices.
+ */
+ static class Bucket {
+
+ static final int BITS_PER_WORD = Long.SIZE;
+
+ static final long LAST_BIT = 1L << (Long.SIZE - 1);
+
+ long mData = 0;
+
+ Bucket mNext;
+
+ void set(int index) {
+ if (index >= BITS_PER_WORD) {
+ ensureNext();
+ mNext.set(index - BITS_PER_WORD);
+ } else {
+ mData |= 1L << index;
+ }
+ }
+
+ private void ensureNext() {
+ if (mNext == null) {
+ mNext = new Bucket();
+ }
+ }
+
+ void clear(int index) {
+ if (index >= BITS_PER_WORD) {
+ if (mNext != null) {
+ mNext.clear(index - BITS_PER_WORD);
+ }
+ } else {
+ mData &= ~(1L << index);
+ }
+
+ }
+
+ boolean get(int index) {
+ if (index >= BITS_PER_WORD) {
+ ensureNext();
+ return mNext.get(index - BITS_PER_WORD);
+ } else {
+ return (mData & (1L << index)) != 0;
+ }
+ }
+
+ void reset() {
+ mData = 0;
+ if (mNext != null) {
+ mNext.reset();
+ }
+ }
+
+ void insert(int index, boolean value) {
+ if (index >= BITS_PER_WORD) {
+ ensureNext();
+ mNext.insert(index - BITS_PER_WORD, value);
+ } else {
+ final boolean lastBit = (mData & LAST_BIT) != 0;
+ long mask = (1L << index) - 1;
+ final long before = mData & mask;
+ final long after = ((mData & ~mask)) << 1;
+ mData = before | after;
+ if (value) {
+ set(index);
+ } else {
+ clear(index);
+ }
+ if (lastBit || mNext != null) {
+ ensureNext();
+ mNext.insert(0, lastBit);
+ }
+ }
+ }
+
+ boolean remove(int index) {
+ if (index >= BITS_PER_WORD) {
+ ensureNext();
+ return mNext.remove(index - BITS_PER_WORD);
+ } else {
+ long mask = (1L << index);
+ final boolean value = (mData & mask) != 0;
+ mData &= ~mask;
+ mask = mask - 1;
+ final long before = mData & mask;
+ // cannot use >> because it adds one.
+ final long after = Long.rotateRight(mData & ~mask, 1);
+ mData = before | after;
+ if (mNext != null) {
+ if (mNext.get(0)) {
+ set(BITS_PER_WORD - 1);
+ }
+ mNext.remove(0);
+ }
+ return value;
+ }
+ }
+
+ int countOnesBefore(int index) {
+ if (mNext == null) {
+ if (index >= BITS_PER_WORD) {
+ return Long.bitCount(mData);
+ }
+ return Long.bitCount(mData & ((1L << index) - 1));
+ }
+ if (index < BITS_PER_WORD) {
+ return Long.bitCount(mData & ((1L << index) - 1));
+ } else {
+ return mNext.countOnesBefore(index - BITS_PER_WORD) + Long.bitCount(mData);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mNext == null ? Long.toBinaryString(mData)
+ : mNext.toString() + "xx" + Long.toBinaryString(mData);
+ }
+ }
+
+ interface Callback {
+
+ int getChildCount();
+
+ void addView(View child, int index);
+
+ int indexOfChild(View view);
+
+ void removeViewAt(int index);
+
+ View getChildAt(int offset);
+
+ void removeAllViews();
+
+ RecyclerView.ViewHolder getChildViewHolder(View view);
+
+ void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams);
+
+ void detachViewFromParent(int offset);
+
+ void onEnteredHiddenState(View child);
+
+ void onLeftHiddenState(View child);
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/DefaultItemAnimator.java b/core/java/com/android/internal/widget/DefaultItemAnimator.java
new file mode 100644
index 0000000..92345af
--- /dev/null
+++ b/core/java/com/android/internal/widget/DefaultItemAnimator.java
@@ -0,0 +1,668 @@
+/*
+ * 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.internal.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This implementation of {@link RecyclerView.ItemAnimator} provides basic
+ * animations on remove, add, and move events that happen to the items in
+ * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
+ *
+ * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
+ */
+public class DefaultItemAnimator extends SimpleItemAnimator {
+ private static final boolean DEBUG = false;
+
+ private static TimeInterpolator sDefaultInterpolator;
+
+ private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
+ private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
+ private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
+ private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
+
+ ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>();
+ ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
+ ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
+
+ ArrayList<ViewHolder> mAddAnimations = new ArrayList<>();
+ ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>();
+ ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
+ ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>();
+
+ private static class MoveInfo {
+ public ViewHolder holder;
+ public int fromX, fromY, toX, toY;
+
+ MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
+ this.holder = holder;
+ this.fromX = fromX;
+ this.fromY = fromY;
+ this.toX = toX;
+ this.toY = toY;
+ }
+ }
+
+ private static class ChangeInfo {
+ public ViewHolder oldHolder, newHolder;
+ public int fromX, fromY, toX, toY;
+ private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
+ this.oldHolder = oldHolder;
+ this.newHolder = newHolder;
+ }
+
+ ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
+ int fromX, int fromY, int toX, int toY) {
+ this(oldHolder, newHolder);
+ this.fromX = fromX;
+ this.fromY = fromY;
+ this.toX = toX;
+ this.toY = toY;
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeInfo{"
+ + "oldHolder=" + oldHolder
+ + ", newHolder=" + newHolder
+ + ", fromX=" + fromX
+ + ", fromY=" + fromY
+ + ", toX=" + toX
+ + ", toY=" + toY
+ + '}';
+ }
+ }
+
+ @Override
+ public void runPendingAnimations() {
+ boolean removalsPending = !mPendingRemovals.isEmpty();
+ boolean movesPending = !mPendingMoves.isEmpty();
+ boolean changesPending = !mPendingChanges.isEmpty();
+ boolean additionsPending = !mPendingAdditions.isEmpty();
+ if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
+ // nothing to animate
+ return;
+ }
+ // First, remove stuff
+ for (ViewHolder holder : mPendingRemovals) {
+ animateRemoveImpl(holder);
+ }
+ mPendingRemovals.clear();
+ // Next, move stuff
+ if (movesPending) {
+ final ArrayList<MoveInfo> moves = new ArrayList<>();
+ moves.addAll(mPendingMoves);
+ mMovesList.add(moves);
+ mPendingMoves.clear();
+ Runnable mover = new Runnable() {
+ @Override
+ public void run() {
+ for (MoveInfo moveInfo : moves) {
+ animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
+ moveInfo.toX, moveInfo.toY);
+ }
+ moves.clear();
+ mMovesList.remove(moves);
+ }
+ };
+ if (removalsPending) {
+ View view = moves.get(0).holder.itemView;
+ view.postOnAnimationDelayed(mover, getRemoveDuration());
+ } else {
+ mover.run();
+ }
+ }
+ // Next, change stuff, to run in parallel with move animations
+ if (changesPending) {
+ final ArrayList<ChangeInfo> changes = new ArrayList<>();
+ changes.addAll(mPendingChanges);
+ mChangesList.add(changes);
+ mPendingChanges.clear();
+ Runnable changer = new Runnable() {
+ @Override
+ public void run() {
+ for (ChangeInfo change : changes) {
+ animateChangeImpl(change);
+ }
+ changes.clear();
+ mChangesList.remove(changes);
+ }
+ };
+ if (removalsPending) {
+ ViewHolder holder = changes.get(0).oldHolder;
+ holder.itemView.postOnAnimationDelayed(changer, getRemoveDuration());
+ } else {
+ changer.run();
+ }
+ }
+ // Next, add stuff
+ if (additionsPending) {
+ final ArrayList<ViewHolder> additions = new ArrayList<>();
+ additions.addAll(mPendingAdditions);
+ mAdditionsList.add(additions);
+ mPendingAdditions.clear();
+ Runnable adder = new Runnable() {
+ @Override
+ public void run() {
+ for (ViewHolder holder : additions) {
+ animateAddImpl(holder);
+ }
+ additions.clear();
+ mAdditionsList.remove(additions);
+ }
+ };
+ if (removalsPending || movesPending || changesPending) {
+ long removeDuration = removalsPending ? getRemoveDuration() : 0;
+ long moveDuration = movesPending ? getMoveDuration() : 0;
+ long changeDuration = changesPending ? getChangeDuration() : 0;
+ long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
+ View view = additions.get(0).itemView;
+ view.postOnAnimationDelayed(adder, totalDelay);
+ } else {
+ adder.run();
+ }
+ }
+ }
+
+ @Override
+ public boolean animateRemove(final ViewHolder holder) {
+ resetAnimation(holder);
+ mPendingRemovals.add(holder);
+ return true;
+ }
+
+ private void animateRemoveImpl(final ViewHolder holder) {
+ final View view = holder.itemView;
+ final ViewPropertyAnimator animation = view.animate();
+ mRemoveAnimations.add(holder);
+ animation.setDuration(getRemoveDuration()).alpha(0).setListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchRemoveStarting(holder);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animation.setListener(null);
+ view.setAlpha(1);
+ dispatchRemoveFinished(holder);
+ mRemoveAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateAdd(final ViewHolder holder) {
+ resetAnimation(holder);
+ holder.itemView.setAlpha(0);
+ mPendingAdditions.add(holder);
+ return true;
+ }
+
+ void animateAddImpl(final ViewHolder holder) {
+ final View view = holder.itemView;
+ final ViewPropertyAnimator animation = view.animate();
+ mAddAnimations.add(holder);
+ animation.alpha(1).setDuration(getAddDuration())
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchAddStarting(holder);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ view.setAlpha(1);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animation.setListener(null);
+ dispatchAddFinished(holder);
+ mAddAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
+ int toX, int toY) {
+ final View view = holder.itemView;
+ fromX += holder.itemView.getTranslationX();
+ fromY += holder.itemView.getTranslationY();
+ resetAnimation(holder);
+ int deltaX = toX - fromX;
+ int deltaY = toY - fromY;
+ if (deltaX == 0 && deltaY == 0) {
+ dispatchMoveFinished(holder);
+ return false;
+ }
+ if (deltaX != 0) {
+ view.setTranslationX(-deltaX);
+ }
+ if (deltaY != 0) {
+ view.setTranslationY(-deltaY);
+ }
+ mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
+ return true;
+ }
+
+ void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
+ final View view = holder.itemView;
+ final int deltaX = toX - fromX;
+ final int deltaY = toY - fromY;
+ if (deltaX != 0) {
+ view.animate().translationX(0);
+ }
+ if (deltaY != 0) {
+ view.animate().translationY(0);
+ }
+ // TODO: make EndActions end listeners instead, since end actions aren't called when
+ // vpas are canceled (and can't end them. why?)
+ // need listener functionality in VPACompat for this. Ick.
+ final ViewPropertyAnimator animation = view.animate();
+ mMoveAnimations.add(holder);
+ animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchMoveStarting(holder);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ if (deltaX != 0) {
+ view.setTranslationX(0);
+ }
+ if (deltaY != 0) {
+ view.setTranslationY(0);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animation.setListener(null);
+ dispatchMoveFinished(holder);
+ mMoveAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
+ int fromX, int fromY, int toX, int toY) {
+ if (oldHolder == newHolder) {
+ // Don't know how to run change animations when the same view holder is re-used.
+ // run a move animation to handle position changes.
+ return animateMove(oldHolder, fromX, fromY, toX, toY);
+ }
+ final float prevTranslationX = oldHolder.itemView.getTranslationX();
+ final float prevTranslationY = oldHolder.itemView.getTranslationY();
+ final float prevAlpha = oldHolder.itemView.getAlpha();
+ resetAnimation(oldHolder);
+ int deltaX = (int) (toX - fromX - prevTranslationX);
+ int deltaY = (int) (toY - fromY - prevTranslationY);
+ // recover prev translation state after ending animation
+ oldHolder.itemView.setTranslationX(prevTranslationX);
+ oldHolder.itemView.setTranslationY(prevTranslationY);
+ oldHolder.itemView.setAlpha(prevAlpha);
+ if (newHolder != null) {
+ // carry over translation values
+ resetAnimation(newHolder);
+ newHolder.itemView.setTranslationX(-deltaX);
+ newHolder.itemView.setTranslationY(-deltaY);
+ newHolder.itemView.setAlpha(0);
+ }
+ mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
+ return true;
+ }
+
+ void animateChangeImpl(final ChangeInfo changeInfo) {
+ final ViewHolder holder = changeInfo.oldHolder;
+ final View view = holder == null ? null : holder.itemView;
+ final ViewHolder newHolder = changeInfo.newHolder;
+ final View newView = newHolder != null ? newHolder.itemView : null;
+ if (view != null) {
+ final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
+ getChangeDuration());
+ mChangeAnimations.add(changeInfo.oldHolder);
+ oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
+ oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
+ oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchChangeStarting(changeInfo.oldHolder, true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ oldViewAnim.setListener(null);
+ view.setAlpha(1);
+ view.setTranslationX(0);
+ view.setTranslationY(0);
+ dispatchChangeFinished(changeInfo.oldHolder, true);
+ mChangeAnimations.remove(changeInfo.oldHolder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+ if (newView != null) {
+ final ViewPropertyAnimator newViewAnimation = newView.animate();
+ mChangeAnimations.add(changeInfo.newHolder);
+ newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
+ .alpha(1).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchChangeStarting(changeInfo.newHolder, false);
+ }
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ newViewAnimation.setListener(null);
+ newView.setAlpha(1);
+ newView.setTranslationX(0);
+ newView.setTranslationY(0);
+ dispatchChangeFinished(changeInfo.newHolder, false);
+ mChangeAnimations.remove(changeInfo.newHolder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+ }
+
+ private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
+ for (int i = infoList.size() - 1; i >= 0; i--) {
+ ChangeInfo changeInfo = infoList.get(i);
+ if (endChangeAnimationIfNecessary(changeInfo, item)) {
+ if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
+ infoList.remove(changeInfo);
+ }
+ }
+ }
+ }
+
+ private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
+ if (changeInfo.oldHolder != null) {
+ endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
+ }
+ if (changeInfo.newHolder != null) {
+ endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
+ }
+ }
+ private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
+ boolean oldItem = false;
+ if (changeInfo.newHolder == item) {
+ changeInfo.newHolder = null;
+ } else if (changeInfo.oldHolder == item) {
+ changeInfo.oldHolder = null;
+ oldItem = true;
+ } else {
+ return false;
+ }
+ item.itemView.setAlpha(1);
+ item.itemView.setTranslationX(0);
+ item.itemView.setTranslationY(0);
+ dispatchChangeFinished(item, oldItem);
+ return true;
+ }
+
+ @Override
+ public void endAnimation(ViewHolder item) {
+ final View view = item.itemView;
+ // this will trigger end callback which should set properties to their target values.
+ view.animate().cancel();
+ // TODO if some other animations are chained to end, how do we cancel them as well?
+ for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
+ MoveInfo moveInfo = mPendingMoves.get(i);
+ if (moveInfo.holder == item) {
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(item);
+ mPendingMoves.remove(i);
+ }
+ }
+ endChangeAnimation(mPendingChanges, item);
+ if (mPendingRemovals.remove(item)) {
+ view.setAlpha(1);
+ dispatchRemoveFinished(item);
+ }
+ if (mPendingAdditions.remove(item)) {
+ view.setAlpha(1);
+ dispatchAddFinished(item);
+ }
+
+ for (int i = mChangesList.size() - 1; i >= 0; i--) {
+ ArrayList<ChangeInfo> changes = mChangesList.get(i);
+ endChangeAnimation(changes, item);
+ if (changes.isEmpty()) {
+ mChangesList.remove(i);
+ }
+ }
+ for (int i = mMovesList.size() - 1; i >= 0; i--) {
+ ArrayList<MoveInfo> moves = mMovesList.get(i);
+ for (int j = moves.size() - 1; j >= 0; j--) {
+ MoveInfo moveInfo = moves.get(j);
+ if (moveInfo.holder == item) {
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(item);
+ moves.remove(j);
+ if (moves.isEmpty()) {
+ mMovesList.remove(i);
+ }
+ break;
+ }
+ }
+ }
+ for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
+ ArrayList<ViewHolder> additions = mAdditionsList.get(i);
+ if (additions.remove(item)) {
+ view.setAlpha(1);
+ dispatchAddFinished(item);
+ if (additions.isEmpty()) {
+ mAdditionsList.remove(i);
+ }
+ }
+ }
+
+ // animations should be ended by the cancel above.
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mRemoveAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mRemoveAnimations list");
+ }
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mAddAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mAddAnimations list");
+ }
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mChangeAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mChangeAnimations list");
+ }
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mMoveAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mMoveAnimations list");
+ }
+ dispatchFinishedWhenDone();
+ }
+
+ private void resetAnimation(ViewHolder holder) {
+ if (sDefaultInterpolator == null) {
+ sDefaultInterpolator = new ValueAnimator().getInterpolator();
+ }
+ holder.itemView.animate().setInterpolator(sDefaultInterpolator);
+ endAnimation(holder);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return (!mPendingAdditions.isEmpty()
+ || !mPendingChanges.isEmpty()
+ || !mPendingMoves.isEmpty()
+ || !mPendingRemovals.isEmpty()
+ || !mMoveAnimations.isEmpty()
+ || !mRemoveAnimations.isEmpty()
+ || !mAddAnimations.isEmpty()
+ || !mChangeAnimations.isEmpty()
+ || !mMovesList.isEmpty()
+ || !mAdditionsList.isEmpty()
+ || !mChangesList.isEmpty());
+ }
+
+ /**
+ * Check the state of currently pending and running animations. If there are none
+ * pending/running, call {@link #dispatchAnimationsFinished()} to notify any
+ * listeners.
+ */
+ void dispatchFinishedWhenDone() {
+ if (!isRunning()) {
+ dispatchAnimationsFinished();
+ }
+ }
+
+ @Override
+ public void endAnimations() {
+ int count = mPendingMoves.size();
+ for (int i = count - 1; i >= 0; i--) {
+ MoveInfo item = mPendingMoves.get(i);
+ View view = item.holder.itemView;
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(item.holder);
+ mPendingMoves.remove(i);
+ }
+ count = mPendingRemovals.size();
+ for (int i = count - 1; i >= 0; i--) {
+ ViewHolder item = mPendingRemovals.get(i);
+ dispatchRemoveFinished(item);
+ mPendingRemovals.remove(i);
+ }
+ count = mPendingAdditions.size();
+ for (int i = count - 1; i >= 0; i--) {
+ ViewHolder item = mPendingAdditions.get(i);
+ item.itemView.setAlpha(1);
+ dispatchAddFinished(item);
+ mPendingAdditions.remove(i);
+ }
+ count = mPendingChanges.size();
+ for (int i = count - 1; i >= 0; i--) {
+ endChangeAnimationIfNecessary(mPendingChanges.get(i));
+ }
+ mPendingChanges.clear();
+ if (!isRunning()) {
+ return;
+ }
+
+ int listCount = mMovesList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ ArrayList<MoveInfo> moves = mMovesList.get(i);
+ count = moves.size();
+ for (int j = count - 1; j >= 0; j--) {
+ MoveInfo moveInfo = moves.get(j);
+ ViewHolder item = moveInfo.holder;
+ View view = item.itemView;
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(moveInfo.holder);
+ moves.remove(j);
+ if (moves.isEmpty()) {
+ mMovesList.remove(moves);
+ }
+ }
+ }
+ listCount = mAdditionsList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ ArrayList<ViewHolder> additions = mAdditionsList.get(i);
+ count = additions.size();
+ for (int j = count - 1; j >= 0; j--) {
+ ViewHolder item = additions.get(j);
+ View view = item.itemView;
+ view.setAlpha(1);
+ dispatchAddFinished(item);
+ additions.remove(j);
+ if (additions.isEmpty()) {
+ mAdditionsList.remove(additions);
+ }
+ }
+ }
+ listCount = mChangesList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ ArrayList<ChangeInfo> changes = mChangesList.get(i);
+ count = changes.size();
+ for (int j = count - 1; j >= 0; j--) {
+ endChangeAnimationIfNecessary(changes.get(j));
+ if (changes.isEmpty()) {
+ mChangesList.remove(changes);
+ }
+ }
+ }
+
+ cancelAll(mRemoveAnimations);
+ cancelAll(mMoveAnimations);
+ cancelAll(mAddAnimations);
+ cancelAll(mChangeAnimations);
+
+ dispatchAnimationsFinished();
+ }
+
+ void cancelAll(List<ViewHolder> viewHolders) {
+ for (int i = viewHolders.size() - 1; i >= 0; i--) {
+ viewHolders.get(i).itemView.animate().cancel();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
+ * When this is the case:
+ * <ul>
+ * <li>If you override {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, both
+ * ViewHolder arguments will be the same instance.
+ * </li>
+ * <li>
+ * If you are not overriding {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)},
+ * then DefaultItemAnimator will call {@link #animateMove(ViewHolder, int, int, int, int)} and
+ * run a move animation instead.
+ * </li>
+ * </ul>
+ */
+ @Override
+ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
+ @NonNull List<Object> payloads) {
+ return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
+ }
+}
diff --git a/core/java/com/android/internal/widget/GapWorker.java b/core/java/com/android/internal/widget/GapWorker.java
new file mode 100644
index 0000000..5972396
--- /dev/null
+++ b/core/java/com/android/internal/widget/GapWorker.java
@@ -0,0 +1,379 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.Nullable;
+import android.os.Trace;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.concurrent.TimeUnit;
+
+final class GapWorker implements Runnable {
+
+ static final ThreadLocal<GapWorker> sGapWorker = new ThreadLocal<>();
+
+ ArrayList<RecyclerView> mRecyclerViews = new ArrayList<>();
+ long mPostTimeNs;
+ long mFrameIntervalNs;
+
+ static class Task {
+ public boolean immediate;
+ public int viewVelocity;
+ public int distanceToItem;
+ public RecyclerView view;
+ public int position;
+
+ public void clear() {
+ immediate = false;
+ viewVelocity = 0;
+ distanceToItem = 0;
+ view = null;
+ position = 0;
+ }
+ }
+
+ /**
+ * Temporary storage for prefetch Tasks that execute in {@link #prefetch(long)}. Task objects
+ * are pooled in the ArrayList, and never removed to avoid allocations, but always cleared
+ * in between calls.
+ */
+ private ArrayList<Task> mTasks = new ArrayList<>();
+
+ /**
+ * Prefetch information associated with a specific RecyclerView.
+ */
+ static class LayoutPrefetchRegistryImpl
+ implements RecyclerView.LayoutManager.LayoutPrefetchRegistry {
+ int mPrefetchDx;
+ int mPrefetchDy;
+ int[] mPrefetchArray;
+
+ int mCount;
+
+ void setPrefetchVector(int dx, int dy) {
+ mPrefetchDx = dx;
+ mPrefetchDy = dy;
+ }
+
+ void collectPrefetchPositionsFromView(RecyclerView view, boolean nested) {
+ mCount = 0;
+ if (mPrefetchArray != null) {
+ Arrays.fill(mPrefetchArray, -1);
+ }
+
+ final RecyclerView.LayoutManager layout = view.mLayout;
+ if (view.mAdapter != null
+ && layout != null
+ && layout.isItemPrefetchEnabled()) {
+ if (nested) {
+ // nested prefetch, only if no adapter updates pending. Note: we don't query
+ // view.hasPendingAdapterUpdates(), as first layout may not have occurred
+ if (!view.mAdapterHelper.hasPendingUpdates()) {
+ layout.collectInitialPrefetchPositions(view.mAdapter.getItemCount(), this);
+ }
+ } else {
+ // momentum based prefetch, only if we trust current child/adapter state
+ if (!view.hasPendingAdapterUpdates()) {
+ layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy,
+ view.mState, this);
+ }
+ }
+
+ if (mCount > layout.mPrefetchMaxCountObserved) {
+ layout.mPrefetchMaxCountObserved = mCount;
+ layout.mPrefetchMaxObservedInInitialPrefetch = nested;
+ view.mRecycler.updateViewCacheSize();
+ }
+ }
+ }
+
+ @Override
+ public void addPosition(int layoutPosition, int pixelDistance) {
+ if (pixelDistance < 0) {
+ throw new IllegalArgumentException("Pixel distance must be non-negative");
+ }
+
+ // allocate or expand array as needed, doubling when needed
+ final int storagePosition = mCount * 2;
+ if (mPrefetchArray == null) {
+ mPrefetchArray = new int[4];
+ Arrays.fill(mPrefetchArray, -1);
+ } else if (storagePosition >= mPrefetchArray.length) {
+ final int[] oldArray = mPrefetchArray;
+ mPrefetchArray = new int[storagePosition * 2];
+ System.arraycopy(oldArray, 0, mPrefetchArray, 0, oldArray.length);
+ }
+
+ // add position
+ mPrefetchArray[storagePosition] = layoutPosition;
+ mPrefetchArray[storagePosition + 1] = pixelDistance;
+
+ mCount++;
+ }
+
+ boolean lastPrefetchIncludedPosition(int position) {
+ if (mPrefetchArray != null) {
+ final int count = mCount * 2;
+ for (int i = 0; i < count; i += 2) {
+ if (mPrefetchArray[i] == position) return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called when prefetch indices are no longer valid for cache prioritization.
+ */
+ void clearPrefetchPositions() {
+ if (mPrefetchArray != null) {
+ Arrays.fill(mPrefetchArray, -1);
+ }
+ }
+ }
+
+ public void add(RecyclerView recyclerView) {
+ if (RecyclerView.DEBUG && mRecyclerViews.contains(recyclerView)) {
+ throw new IllegalStateException("RecyclerView already present in worker list!");
+ }
+ mRecyclerViews.add(recyclerView);
+ }
+
+ public void remove(RecyclerView recyclerView) {
+ boolean removeSuccess = mRecyclerViews.remove(recyclerView);
+ if (RecyclerView.DEBUG && !removeSuccess) {
+ throw new IllegalStateException("RecyclerView removal failed!");
+ }
+ }
+
+ /**
+ * Schedule a prefetch immediately after the current traversal.
+ */
+ void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
+ if (recyclerView.isAttachedToWindow()) {
+ if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {
+ throw new IllegalStateException("attempting to post unregistered view!");
+ }
+ if (mPostTimeNs == 0) {
+ mPostTimeNs = recyclerView.getNanoTime();
+ recyclerView.post(this);
+ }
+ }
+
+ recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
+ }
+
+ static Comparator<Task> sTaskComparator = new Comparator<Task>() {
+ @Override
+ public int compare(Task lhs, Task rhs) {
+ // first, prioritize non-cleared tasks
+ if ((lhs.view == null) != (rhs.view == null)) {
+ return lhs.view == null ? 1 : -1;
+ }
+
+ // then prioritize immediate
+ if (lhs.immediate != rhs.immediate) {
+ return lhs.immediate ? -1 : 1;
+ }
+
+ // then prioritize _highest_ view velocity
+ int deltaViewVelocity = rhs.viewVelocity - lhs.viewVelocity;
+ if (deltaViewVelocity != 0) return deltaViewVelocity;
+
+ // then prioritize _lowest_ distance to item
+ int deltaDistanceToItem = lhs.distanceToItem - rhs.distanceToItem;
+ if (deltaDistanceToItem != 0) return deltaDistanceToItem;
+
+ return 0;
+ }
+ };
+
+ private void buildTaskList() {
+ // Update PrefetchRegistry in each view
+ final int viewCount = mRecyclerViews.size();
+ int totalTaskCount = 0;
+ for (int i = 0; i < viewCount; i++) {
+ RecyclerView view = mRecyclerViews.get(i);
+ view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false);
+ totalTaskCount += view.mPrefetchRegistry.mCount;
+ }
+
+ // Populate task list from prefetch data...
+ mTasks.ensureCapacity(totalTaskCount);
+ int totalTaskIndex = 0;
+ for (int i = 0; i < viewCount; i++) {
+ RecyclerView view = mRecyclerViews.get(i);
+ LayoutPrefetchRegistryImpl prefetchRegistry = view.mPrefetchRegistry;
+ final int viewVelocity = Math.abs(prefetchRegistry.mPrefetchDx)
+ + Math.abs(prefetchRegistry.mPrefetchDy);
+ for (int j = 0; j < prefetchRegistry.mCount * 2; j += 2) {
+ final Task task;
+ if (totalTaskIndex >= mTasks.size()) {
+ task = new Task();
+ mTasks.add(task);
+ } else {
+ task = mTasks.get(totalTaskIndex);
+ }
+ final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1];
+
+ task.immediate = distanceToItem <= viewVelocity;
+ task.viewVelocity = viewVelocity;
+ task.distanceToItem = distanceToItem;
+ task.view = view;
+ task.position = prefetchRegistry.mPrefetchArray[j];
+
+ totalTaskIndex++;
+ }
+ }
+
+ // ... and priority sort
+ Collections.sort(mTasks, sTaskComparator);
+ }
+
+ static boolean isPrefetchPositionAttached(RecyclerView view, int position) {
+ final int childCount = view.mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View attachedView = view.mChildHelper.getUnfilteredChildAt(i);
+ RecyclerView.ViewHolder holder = RecyclerView.getChildViewHolderInt(attachedView);
+ // Note: can use mPosition here because adapter doesn't have pending updates
+ if (holder.mPosition == position && !holder.isInvalid()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
+ int position, long deadlineNs) {
+ if (isPrefetchPositionAttached(view, position)) {
+ // don't attempt to prefetch attached views
+ return null;
+ }
+
+ RecyclerView.Recycler recycler = view.mRecycler;
+ RecyclerView.ViewHolder holder = recycler.tryGetViewHolderForPositionByDeadline(
+ position, false, deadlineNs);
+
+ if (holder != null) {
+ if (holder.isBound()) {
+ // Only give the view a chance to go into the cache if binding succeeded
+ // Note that we must use public method, since item may need cleanup
+ recycler.recycleView(holder.itemView);
+ } else {
+ // Didn't bind, so we can't cache the view, but it will stay in the pool until
+ // next prefetch/traversal. If a View fails to bind, it means we didn't have
+ // enough time prior to the deadline (and won't for other instances of this
+ // type, during this GapWorker prefetch pass).
+ recycler.addViewHolderToRecycledViewPool(holder, false);
+ }
+ }
+ return holder;
+ }
+
+ private void prefetchInnerRecyclerViewWithDeadline(@Nullable RecyclerView innerView,
+ long deadlineNs) {
+ if (innerView == null) {
+ return;
+ }
+
+ if (innerView.mDataSetHasChangedAfterLayout
+ && innerView.mChildHelper.getUnfilteredChildCount() != 0) {
+ // RecyclerView has new data, but old attached views. Clear everything, so that
+ // we can prefetch without partially stale data.
+ innerView.removeAndRecycleViews();
+ }
+
+ // do nested prefetch!
+ final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry;
+ innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true);
+
+ if (innerPrefetchRegistry.mCount != 0) {
+ try {
+ Trace.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG);
+ innerView.mState.prepareForNestedPrefetch(innerView.mAdapter);
+ for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) {
+ // Note that we ignore immediate flag for inner items because
+ // we have lower confidence they're needed next frame.
+ final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i];
+ prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs);
+ }
+ } finally {
+ Trace.endSection();
+ }
+ }
+ }
+
+ private void flushTaskWithDeadline(Task task, long deadlineNs) {
+ long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
+ RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
+ task.position, taskDeadlineNs);
+ if (holder != null && holder.mNestedRecyclerView != null) {
+ prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
+ }
+ }
+
+ private void flushTasksWithDeadline(long deadlineNs) {
+ for (int i = 0; i < mTasks.size(); i++) {
+ final Task task = mTasks.get(i);
+ if (task.view == null) {
+ break; // done with populated tasks
+ }
+ flushTaskWithDeadline(task, deadlineNs);
+ task.clear();
+ }
+ }
+
+ void prefetch(long deadlineNs) {
+ buildTaskList();
+ flushTasksWithDeadline(deadlineNs);
+ }
+
+ @Override
+ public void run() {
+ try {
+ Trace.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
+
+ if (mRecyclerViews.isEmpty()) {
+ // abort - no work to do
+ return;
+ }
+
+ // Query last vsync so we can predict next one. Note that drawing time not yet
+ // valid in animation/input callbacks, so query it here to be safe.
+ long lastFrameVsyncNs = TimeUnit.MILLISECONDS.toNanos(
+ mRecyclerViews.get(0).getDrawingTime());
+ if (lastFrameVsyncNs == 0) {
+ // abort - couldn't get last vsync for estimating next
+ return;
+ }
+
+ // TODO: consider rebasing deadline if frame was already dropped due to long UI work.
+ // Next frame will still wait for VSYNC, so we can still use the gap if it exists.
+ long nextFrameNs = lastFrameVsyncNs + mFrameIntervalNs;
+
+ prefetch(nextFrameNs);
+
+ // TODO: consider rescheduling self, if there's more work to do
+ } finally {
+ mPostTimeNs = 0;
+ Trace.endSection();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/LinearLayoutManager.java b/core/java/com/android/internal/widget/LinearLayoutManager.java
new file mode 100644
index 0000000..d82c746
--- /dev/null
+++ b/core/java/com/android/internal/widget/LinearLayoutManager.java
@@ -0,0 +1,2398 @@
+/*
+ * 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.internal.widget;
+
+import static com.android.internal.widget.RecyclerView.NO_POSITION;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.widget.RecyclerView.LayoutParams;
+import com.android.internal.widget.helper.ItemTouchHelper;
+
+import java.util.List;
+
+/**
+ * A {@link com.android.internal.widget.RecyclerView.LayoutManager} implementation which provides
+ * similar functionality to {@link android.widget.ListView}.
+ */
+public class LinearLayoutManager extends RecyclerView.LayoutManager implements
+ ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
+
+ private static final String TAG = "LinearLayoutManager";
+
+ static final boolean DEBUG = false;
+
+ public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
+
+ public static final int VERTICAL = OrientationHelper.VERTICAL;
+
+ public static final int INVALID_OFFSET = Integer.MIN_VALUE;
+
+
+ /**
+ * While trying to find next view to focus, LayoutManager will not try to scroll more
+ * than this factor times the total space of the list. If layout is vertical, total space is the
+ * height minus padding, if layout is horizontal, total space is the width minus padding.
+ */
+ private static final float MAX_SCROLL_FACTOR = 1 / 3f;
+
+
+ /**
+ * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
+ */
+ int mOrientation;
+
+ /**
+ * Helper class that keeps temporary layout state.
+ * It does not keep state after layout is complete but we still keep a reference to re-use
+ * the same object.
+ */
+ private LayoutState mLayoutState;
+
+ /**
+ * Many calculations are made depending on orientation. To keep it clean, this interface
+ * helps {@link LinearLayoutManager} make those decisions.
+ * Based on {@link #mOrientation}, an implementation is lazily created in
+ * {@link #ensureLayoutState} method.
+ */
+ OrientationHelper mOrientationHelper;
+
+ /**
+ * We need to track this so that we can ignore current position when it changes.
+ */
+ private boolean mLastStackFromEnd;
+
+
+ /**
+ * Defines if layout should be calculated from end to start.
+ *
+ * @see #mShouldReverseLayout
+ */
+ private boolean mReverseLayout = false;
+
+ /**
+ * This keeps the final value for how LayoutManager should start laying out views.
+ * It is calculated by checking {@link #getReverseLayout()} and View's layout direction.
+ * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run.
+ */
+ boolean mShouldReverseLayout = false;
+
+ /**
+ * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and
+ * it supports both orientations.
+ * see {@link android.widget.AbsListView#setStackFromBottom(boolean)}
+ */
+ private boolean mStackFromEnd = false;
+
+ /**
+ * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
+ * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
+ */
+ private boolean mSmoothScrollbarEnabled = true;
+
+ /**
+ * When LayoutManager needs to scroll to a position, it sets this variable and requests a
+ * layout which will check this variable and re-layout accordingly.
+ */
+ int mPendingScrollPosition = NO_POSITION;
+
+ /**
+ * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
+ * called.
+ */
+ int mPendingScrollPositionOffset = INVALID_OFFSET;
+
+ private boolean mRecycleChildrenOnDetach;
+
+ SavedState mPendingSavedState = null;
+
+ /**
+ * Re-used variable to keep anchor information on re-layout.
+ * Anchor position and coordinate defines the reference point for LLM while doing a layout.
+ * */
+ final AnchorInfo mAnchorInfo = new AnchorInfo();
+
+ /**
+ * Stashed to avoid allocation, currently only used in #fill()
+ */
+ private final LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult();
+
+ /**
+ * Number of items to prefetch when first coming on screen with new data.
+ */
+ private int mInitialItemPrefetchCount = 2;
+
+ /**
+ * Creates a vertical LinearLayoutManager
+ *
+ * @param context Current context, will be used to access resources.
+ */
+ public LinearLayoutManager(Context context) {
+ this(context, VERTICAL, false);
+ }
+
+ /**
+ * @param context Current context, will be used to access resources.
+ * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
+ * #VERTICAL}.
+ * @param reverseLayout When set to true, layouts from end to start.
+ */
+ public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
+ setOrientation(orientation);
+ setReverseLayout(reverseLayout);
+ setAutoMeasureEnabled(true);
+ }
+
+ /**
+ * Constructor used when layout manager is set in XML by RecyclerView attribute
+ * "layoutManager". Defaults to vertical orientation.
+ *
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
+ */
+ public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
+ setOrientation(properties.orientation);
+ setReverseLayout(properties.reverseLayout);
+ setStackFromEnd(properties.stackFromEnd);
+ setAutoMeasureEnabled(true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ /**
+ * Returns whether LayoutManager will recycle its children when it is detached from
+ * RecyclerView.
+ *
+ * @return true if LayoutManager will recycle its children when it is detached from
+ * RecyclerView.
+ */
+ public boolean getRecycleChildrenOnDetach() {
+ return mRecycleChildrenOnDetach;
+ }
+
+ /**
+ * Set whether LayoutManager will recycle its children when it is detached from
+ * RecyclerView.
+ * <p>
+ * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set
+ * this flag to <code>true</code> so that views will be available to other RecyclerViews
+ * immediately.
+ * <p>
+ * Note that, setting this flag will result in a performance drop if RecyclerView
+ * is restored.
+ *
+ * @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
+ */
+ public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
+ mRecycleChildrenOnDetach = recycleChildrenOnDetach;
+ }
+
+ @Override
+ public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+ super.onDetachedFromWindow(view, recycler);
+ if (mRecycleChildrenOnDetach) {
+ removeAndRecycleAllViews(recycler);
+ recycler.clear();
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (getChildCount() > 0) {
+ event.setFromIndex(findFirstVisibleItemPosition());
+ event.setToIndex(findLastVisibleItemPosition());
+ }
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ if (mPendingSavedState != null) {
+ return new SavedState(mPendingSavedState);
+ }
+ SavedState state = new SavedState();
+ if (getChildCount() > 0) {
+ ensureLayoutState();
+ boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
+ state.mAnchorLayoutFromEnd = didLayoutFromEnd;
+ if (didLayoutFromEnd) {
+ final View refChild = getChildClosestToEnd();
+ state.mAnchorOffset = mOrientationHelper.getEndAfterPadding()
+ - mOrientationHelper.getDecoratedEnd(refChild);
+ state.mAnchorPosition = getPosition(refChild);
+ } else {
+ final View refChild = getChildClosestToStart();
+ state.mAnchorPosition = getPosition(refChild);
+ state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild)
+ - mOrientationHelper.getStartAfterPadding();
+ }
+ } else {
+ state.invalidateAnchor();
+ }
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (state instanceof SavedState) {
+ mPendingSavedState = (SavedState) state;
+ requestLayout();
+ if (DEBUG) {
+ Log.d(TAG, "loaded saved state");
+ }
+ } else if (DEBUG) {
+ Log.d(TAG, "invalid saved state class");
+ }
+ }
+
+ /**
+ * @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
+ */
+ @Override
+ public boolean canScrollHorizontally() {
+ return mOrientation == HORIZONTAL;
+ }
+
+ /**
+ * @return true if {@link #getOrientation()} is {@link #VERTICAL}
+ */
+ @Override
+ public boolean canScrollVertically() {
+ return mOrientation == VERTICAL;
+ }
+
+ /**
+ * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)}
+ */
+ public void setStackFromEnd(boolean stackFromEnd) {
+ assertNotInLayoutOrScroll(null);
+ if (mStackFromEnd == stackFromEnd) {
+ return;
+ }
+ mStackFromEnd = stackFromEnd;
+ requestLayout();
+ }
+
+ public boolean getStackFromEnd() {
+ return mStackFromEnd;
+ }
+
+ /**
+ * Returns the current orientation of the layout.
+ *
+ * @return Current orientation, either {@link #HORIZONTAL} or {@link #VERTICAL}
+ * @see #setOrientation(int)
+ */
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ /**
+ * Sets the orientation of the layout. {@link com.android.internal.widget.LinearLayoutManager}
+ * will do its best to keep scroll position.
+ *
+ * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
+ */
+ public void setOrientation(int orientation) {
+ if (orientation != HORIZONTAL && orientation != VERTICAL) {
+ throw new IllegalArgumentException("invalid orientation:" + orientation);
+ }
+ assertNotInLayoutOrScroll(null);
+ if (orientation == mOrientation) {
+ return;
+ }
+ mOrientation = orientation;
+ mOrientationHelper = null;
+ requestLayout();
+ }
+
+ /**
+ * Calculates the view layout order. (e.g. from end to start or start to end)
+ * RTL layout support is applied automatically. So if layout is RTL and
+ * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
+ */
+ private void resolveShouldLayoutReverse() {
+ // A == B is the same result, but we rather keep it readable
+ if (mOrientation == VERTICAL || !isLayoutRTL()) {
+ mShouldReverseLayout = mReverseLayout;
+ } else {
+ mShouldReverseLayout = !mReverseLayout;
+ }
+ }
+
+ /**
+ * Returns if views are laid out from the opposite direction of the layout.
+ *
+ * @return If layout is reversed or not.
+ * @see #setReverseLayout(boolean)
+ */
+ public boolean getReverseLayout() {
+ return mReverseLayout;
+ }
+
+ /**
+ * Used to reverse item traversal and layout order.
+ * This behaves similar to the layout change for RTL views. When set to true, first item is
+ * laid out at the end of the UI, second item is laid out before it etc.
+ *
+ * For horizontal layouts, it depends on the layout direction.
+ * When set to true, If {@link com.android.internal.widget.RecyclerView} is LTR, than it will
+ * layout from RTL, if {@link com.android.internal.widget.RecyclerView}} is RTL, it will layout
+ * from LTR.
+ *
+ * If you are looking for the exact same behavior of
+ * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use
+ * {@link #setStackFromEnd(boolean)}
+ */
+ public void setReverseLayout(boolean reverseLayout) {
+ assertNotInLayoutOrScroll(null);
+ if (reverseLayout == mReverseLayout) {
+ return;
+ }
+ mReverseLayout = reverseLayout;
+ requestLayout();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View findViewByPosition(int position) {
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ return null;
+ }
+ final int firstChild = getPosition(getChildAt(0));
+ final int viewPosition = position - firstChild;
+ if (viewPosition >= 0 && viewPosition < childCount) {
+ final View child = getChildAt(viewPosition);
+ if (getPosition(child) == position) {
+ return child; // in pre-layout, this may not match
+ }
+ }
+ // fallback to traversal. This might be necessary in pre-layout.
+ return super.findViewByPosition(position);
+ }
+
+ /**
+ * <p>Returns the amount of extra space that should be laid out by LayoutManager.</p>
+ *
+ * <p>By default, {@link com.android.internal.widget.LinearLayoutManager} lays out 1 extra page
+ * of items while smooth scrolling and 0 otherwise. You can override this method to implement
+ * your custom layout pre-cache logic.</p>
+ *
+ * <p><strong>Note:</strong>Laying out invisible elements generally comes with significant
+ * performance cost. It's typically only desirable in places like smooth scrolling to an unknown
+ * location, where 1) the extra content helps LinearLayoutManager know in advance when its
+ * target is approaching, so it can decelerate early and smoothly and 2) while motion is
+ * continuous.</p>
+ *
+ * <p>Extending the extra layout space is especially expensive if done while the user may change
+ * scrolling direction. Changing direction will cause the extra layout space to swap to the
+ * opposite side of the viewport, incurring many rebinds/recycles, unless the cache is large
+ * enough to handle it.</p>
+ *
+ * @return The extra space that should be laid out (in pixels).
+ */
+ protected int getExtraLayoutSpace(RecyclerView.State state) {
+ if (state.hasTargetScrollPosition()) {
+ return mOrientationHelper.getTotalSpace();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+ int position) {
+ LinearSmoothScroller linearSmoothScroller =
+ new LinearSmoothScroller(recyclerView.getContext());
+ linearSmoothScroller.setTargetPosition(position);
+ startSmoothScroll(linearSmoothScroller);
+ }
+
+ @Override
+ public PointF computeScrollVectorForPosition(int targetPosition) {
+ if (getChildCount() == 0) {
+ return null;
+ }
+ final int firstChildPos = getPosition(getChildAt(0));
+ final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1;
+ if (mOrientation == HORIZONTAL) {
+ return new PointF(direction, 0);
+ } else {
+ return new PointF(0, direction);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ // layout algorithm:
+ // 1) by checking children and other variables, find an anchor coordinate and an anchor
+ // item position.
+ // 2) fill towards start, stacking from bottom
+ // 3) fill towards end, stacking from top
+ // 4) scroll to fulfill requirements like stack from bottom.
+ // create layout state
+ if (DEBUG) {
+ Log.d(TAG, "is pre layout:" + state.isPreLayout());
+ }
+ if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
+ if (state.getItemCount() == 0) {
+ removeAndRecycleAllViews(recycler);
+ return;
+ }
+ }
+ if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
+ mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
+ }
+
+ ensureLayoutState();
+ mLayoutState.mRecycle = false;
+ // resolve layout direction
+ resolveShouldLayoutReverse();
+
+ if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
+ || mPendingSavedState != null) {
+ mAnchorInfo.reset();
+ mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
+ // calculate anchor position and coordinate
+ updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
+ mAnchorInfo.mValid = true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Anchor info:" + mAnchorInfo);
+ }
+
+ // LLM may decide to layout items for "extra" pixels to account for scrolling target,
+ // caching or predictive animations.
+ int extraForStart;
+ int extraForEnd;
+ final int extra = getExtraLayoutSpace(state);
+ // If the previous scroll delta was less than zero, the extra space should be laid out
+ // at the start. Otherwise, it should be at the end.
+ if (mLayoutState.mLastScrollDelta >= 0) {
+ extraForEnd = extra;
+ extraForStart = 0;
+ } else {
+ extraForStart = extra;
+ extraForEnd = 0;
+ }
+ extraForStart += mOrientationHelper.getStartAfterPadding();
+ extraForEnd += mOrientationHelper.getEndPadding();
+ if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION
+ && mPendingScrollPositionOffset != INVALID_OFFSET) {
+ // if the child is visible and we are going to move it around, we should layout
+ // extra items in the opposite direction to make sure new items animate nicely
+ // instead of just fading in
+ final View existing = findViewByPosition(mPendingScrollPosition);
+ if (existing != null) {
+ final int current;
+ final int upcomingOffset;
+ if (mShouldReverseLayout) {
+ current = mOrientationHelper.getEndAfterPadding()
+ - mOrientationHelper.getDecoratedEnd(existing);
+ upcomingOffset = current - mPendingScrollPositionOffset;
+ } else {
+ current = mOrientationHelper.getDecoratedStart(existing)
+ - mOrientationHelper.getStartAfterPadding();
+ upcomingOffset = mPendingScrollPositionOffset - current;
+ }
+ if (upcomingOffset > 0) {
+ extraForStart += upcomingOffset;
+ } else {
+ extraForEnd -= upcomingOffset;
+ }
+ }
+ }
+ int startOffset;
+ int endOffset;
+ final int firstLayoutDirection;
+ if (mAnchorInfo.mLayoutFromEnd) {
+ firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
+ : LayoutState.ITEM_DIRECTION_HEAD;
+ } else {
+ firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
+ : LayoutState.ITEM_DIRECTION_TAIL;
+ }
+
+ onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
+ detachAndScrapAttachedViews(recycler);
+ mLayoutState.mInfinite = resolveIsInfinite();
+ mLayoutState.mIsPreLayout = state.isPreLayout();
+ if (mAnchorInfo.mLayoutFromEnd) {
+ // fill towards start
+ updateLayoutStateToFillStart(mAnchorInfo);
+ mLayoutState.mExtra = extraForStart;
+ fill(recycler, mLayoutState, state, false);
+ startOffset = mLayoutState.mOffset;
+ final int firstElement = mLayoutState.mCurrentPosition;
+ if (mLayoutState.mAvailable > 0) {
+ extraForEnd += mLayoutState.mAvailable;
+ }
+ // fill towards end
+ updateLayoutStateToFillEnd(mAnchorInfo);
+ mLayoutState.mExtra = extraForEnd;
+ mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
+ fill(recycler, mLayoutState, state, false);
+ endOffset = mLayoutState.mOffset;
+
+ if (mLayoutState.mAvailable > 0) {
+ // end could not consume all. add more items towards start
+ extraForStart = mLayoutState.mAvailable;
+ updateLayoutStateToFillStart(firstElement, startOffset);
+ mLayoutState.mExtra = extraForStart;
+ fill(recycler, mLayoutState, state, false);
+ startOffset = mLayoutState.mOffset;
+ }
+ } else {
+ // fill towards end
+ updateLayoutStateToFillEnd(mAnchorInfo);
+ mLayoutState.mExtra = extraForEnd;
+ fill(recycler, mLayoutState, state, false);
+ endOffset = mLayoutState.mOffset;
+ final int lastElement = mLayoutState.mCurrentPosition;
+ if (mLayoutState.mAvailable > 0) {
+ extraForStart += mLayoutState.mAvailable;
+ }
+ // fill towards start
+ updateLayoutStateToFillStart(mAnchorInfo);
+ mLayoutState.mExtra = extraForStart;
+ mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
+ fill(recycler, mLayoutState, state, false);
+ startOffset = mLayoutState.mOffset;
+
+ if (mLayoutState.mAvailable > 0) {
+ extraForEnd = mLayoutState.mAvailable;
+ // start could not consume all it should. add more items towards end
+ updateLayoutStateToFillEnd(lastElement, endOffset);
+ mLayoutState.mExtra = extraForEnd;
+ fill(recycler, mLayoutState, state, false);
+ endOffset = mLayoutState.mOffset;
+ }
+ }
+
+ // changes may cause gaps on the UI, try to fix them.
+ // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
+ // changed
+ if (getChildCount() > 0) {
+ // because layout from end may be changed by scroll to position
+ // we re-calculate it.
+ // find which side we should check for gaps.
+ if (mShouldReverseLayout ^ mStackFromEnd) {
+ int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
+ startOffset += fixOffset;
+ endOffset += fixOffset;
+ fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
+ startOffset += fixOffset;
+ endOffset += fixOffset;
+ } else {
+ int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
+ startOffset += fixOffset;
+ endOffset += fixOffset;
+ fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
+ startOffset += fixOffset;
+ endOffset += fixOffset;
+ }
+ }
+ layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
+ if (!state.isPreLayout()) {
+ mOrientationHelper.onLayoutComplete();
+ } else {
+ mAnchorInfo.reset();
+ }
+ mLastStackFromEnd = mStackFromEnd;
+ if (DEBUG) {
+ validateChildOrder();
+ }
+ }
+
+ @Override
+ public void onLayoutCompleted(RecyclerView.State state) {
+ super.onLayoutCompleted(state);
+ mPendingSavedState = null; // we don't need this anymore
+ mPendingScrollPosition = NO_POSITION;
+ mPendingScrollPositionOffset = INVALID_OFFSET;
+ mAnchorInfo.reset();
+ }
+
+ /**
+ * Method called when Anchor position is decided. Extending class can setup accordingly or
+ * even update anchor info if necessary.
+ * @param recycler The recycler for the layout
+ * @param state The layout state
+ * @param anchorInfo The mutable POJO that keeps the position and offset.
+ * @param firstLayoutItemDirection The direction of the first layout filling in terms of adapter
+ * indices.
+ */
+ void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
+ AnchorInfo anchorInfo, int firstLayoutItemDirection) {
+ }
+
+ /**
+ * If necessary, layouts new items for predictive animations
+ */
+ private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler,
+ RecyclerView.State state, int startOffset, int endOffset) {
+ // If there are scrap children that we did not layout, we need to find where they did go
+ // and layout them accordingly so that animations can work as expected.
+ // This case may happen if new views are added or an existing view expands and pushes
+ // another view out of bounds.
+ if (!state.willRunPredictiveAnimations() || getChildCount() == 0 || state.isPreLayout()
+ || !supportsPredictiveItemAnimations()) {
+ return;
+ }
+ // to make the logic simpler, we calculate the size of children and call fill.
+ int scrapExtraStart = 0, scrapExtraEnd = 0;
+ final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
+ final int scrapSize = scrapList.size();
+ final int firstChildPos = getPosition(getChildAt(0));
+ for (int i = 0; i < scrapSize; i++) {
+ RecyclerView.ViewHolder scrap = scrapList.get(i);
+ if (scrap.isRemoved()) {
+ continue;
+ }
+ final int position = scrap.getLayoutPosition();
+ final int direction = position < firstChildPos != mShouldReverseLayout
+ ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
+ if (direction == LayoutState.LAYOUT_START) {
+ scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
+ } else {
+ scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart
+ + " towards start and " + scrapExtraEnd + " towards end");
+ }
+ mLayoutState.mScrapList = scrapList;
+ if (scrapExtraStart > 0) {
+ View anchor = getChildClosestToStart();
+ updateLayoutStateToFillStart(getPosition(anchor), startOffset);
+ mLayoutState.mExtra = scrapExtraStart;
+ mLayoutState.mAvailable = 0;
+ mLayoutState.assignPositionFromScrapList();
+ fill(recycler, mLayoutState, state, false);
+ }
+
+ if (scrapExtraEnd > 0) {
+ View anchor = getChildClosestToEnd();
+ updateLayoutStateToFillEnd(getPosition(anchor), endOffset);
+ mLayoutState.mExtra = scrapExtraEnd;
+ mLayoutState.mAvailable = 0;
+ mLayoutState.assignPositionFromScrapList();
+ fill(recycler, mLayoutState, state, false);
+ }
+ mLayoutState.mScrapList = null;
+ }
+
+ private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
+ AnchorInfo anchorInfo) {
+ if (updateAnchorFromPendingData(state, anchorInfo)) {
+ if (DEBUG) {
+ Log.d(TAG, "updated anchor info from pending information");
+ }
+ return;
+ }
+
+ if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
+ if (DEBUG) {
+ Log.d(TAG, "updated anchor info from existing children");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "deciding anchor info for fresh state");
+ }
+ anchorInfo.assignCoordinateFromPadding();
+ anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
+ }
+
+ /**
+ * Finds an anchor child from existing Views. Most of the time, this is the view closest to
+ * start or end that has a valid position (e.g. not removed).
+ * <p>
+ * If a child has focus, it is given priority.
+ */
+ private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
+ RecyclerView.State state, AnchorInfo anchorInfo) {
+ if (getChildCount() == 0) {
+ return false;
+ }
+ final View focused = getFocusedChild();
+ if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
+ anchorInfo.assignFromViewAndKeepVisibleRect(focused);
+ return true;
+ }
+ if (mLastStackFromEnd != mStackFromEnd) {
+ return false;
+ }
+ View referenceChild = anchorInfo.mLayoutFromEnd
+ ? findReferenceChildClosestToEnd(recycler, state)
+ : findReferenceChildClosestToStart(recycler, state);
+ if (referenceChild != null) {
+ anchorInfo.assignFromView(referenceChild);
+ // If all visible views are removed in 1 pass, reference child might be out of bounds.
+ // If that is the case, offset it back to 0 so that we use these pre-layout children.
+ if (!state.isPreLayout() && supportsPredictiveItemAnimations()) {
+ // validate this child is at least partially visible. if not, offset it to start
+ final boolean notVisible =
+ mOrientationHelper.getDecoratedStart(referenceChild) >= mOrientationHelper
+ .getEndAfterPadding()
+ || mOrientationHelper.getDecoratedEnd(referenceChild)
+ < mOrientationHelper.getStartAfterPadding();
+ if (notVisible) {
+ anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
+ ? mOrientationHelper.getEndAfterPadding()
+ : mOrientationHelper.getStartAfterPadding();
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * If there is a pending scroll position or saved states, updates the anchor info from that
+ * data and returns true
+ */
+ private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
+ if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) {
+ return false;
+ }
+ // validate scroll position
+ if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
+ mPendingScrollPosition = NO_POSITION;
+ mPendingScrollPositionOffset = INVALID_OFFSET;
+ if (DEBUG) {
+ Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition);
+ }
+ return false;
+ }
+
+ // if child is visible, try to make it a reference child and ensure it is fully visible.
+ // if child is not visible, align it depending on its virtual position.
+ anchorInfo.mPosition = mPendingScrollPosition;
+ if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
+ // Anchor offset depends on how that child was laid out. Here, we update it
+ // according to our current view bounds
+ anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
+ if (anchorInfo.mLayoutFromEnd) {
+ anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
+ - mPendingSavedState.mAnchorOffset;
+ } else {
+ anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
+ + mPendingSavedState.mAnchorOffset;
+ }
+ return true;
+ }
+
+ if (mPendingScrollPositionOffset == INVALID_OFFSET) {
+ View child = findViewByPosition(mPendingScrollPosition);
+ if (child != null) {
+ final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
+ if (childSize > mOrientationHelper.getTotalSpace()) {
+ // item does not fit. fix depending on layout direction
+ anchorInfo.assignCoordinateFromPadding();
+ return true;
+ }
+ final int startGap = mOrientationHelper.getDecoratedStart(child)
+ - mOrientationHelper.getStartAfterPadding();
+ if (startGap < 0) {
+ anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding();
+ anchorInfo.mLayoutFromEnd = false;
+ return true;
+ }
+ final int endGap = mOrientationHelper.getEndAfterPadding()
+ - mOrientationHelper.getDecoratedEnd(child);
+ if (endGap < 0) {
+ anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding();
+ anchorInfo.mLayoutFromEnd = true;
+ return true;
+ }
+ anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
+ ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper
+ .getTotalSpaceChange())
+ : mOrientationHelper.getDecoratedStart(child);
+ } else { // item is not visible.
+ if (getChildCount() > 0) {
+ // get position of any child, does not matter
+ int pos = getPosition(getChildAt(0));
+ anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos
+ == mShouldReverseLayout;
+ }
+ anchorInfo.assignCoordinateFromPadding();
+ }
+ return true;
+ }
+ // override layout from end values for consistency
+ anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
+ // if this changes, we should update prepareForDrop as well
+ if (mShouldReverseLayout) {
+ anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
+ - mPendingScrollPositionOffset;
+ } else {
+ anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
+ + mPendingScrollPositionOffset;
+ }
+ return true;
+ }
+
+ /**
+ * @return The final offset amount for children
+ */
+ private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler,
+ RecyclerView.State state, boolean canOffsetChildren) {
+ int gap = mOrientationHelper.getEndAfterPadding() - endOffset;
+ int fixOffset = 0;
+ if (gap > 0) {
+ fixOffset = -scrollBy(-gap, recycler, state);
+ } else {
+ return 0; // nothing to fix
+ }
+ // move offset according to scroll amount
+ endOffset += fixOffset;
+ if (canOffsetChildren) {
+ // re-calculate gap, see if we could fix it
+ gap = mOrientationHelper.getEndAfterPadding() - endOffset;
+ if (gap > 0) {
+ mOrientationHelper.offsetChildren(gap);
+ return gap + fixOffset;
+ }
+ }
+ return fixOffset;
+ }
+
+ /**
+ * @return The final offset amount for children
+ */
+ private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler,
+ RecyclerView.State state, boolean canOffsetChildren) {
+ int gap = startOffset - mOrientationHelper.getStartAfterPadding();
+ int fixOffset = 0;
+ if (gap > 0) {
+ // check if we should fix this gap.
+ fixOffset = -scrollBy(gap, recycler, state);
+ } else {
+ return 0; // nothing to fix
+ }
+ startOffset += fixOffset;
+ if (canOffsetChildren) {
+ // re-calculate gap, see if we could fix it
+ gap = startOffset - mOrientationHelper.getStartAfterPadding();
+ if (gap > 0) {
+ mOrientationHelper.offsetChildren(-gap);
+ return fixOffset - gap;
+ }
+ }
+ return fixOffset;
+ }
+
+ private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
+ updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
+ }
+
+ private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
+ mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
+ mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
+ LayoutState.ITEM_DIRECTION_TAIL;
+ mLayoutState.mCurrentPosition = itemPosition;
+ mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
+ mLayoutState.mOffset = offset;
+ mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
+ }
+
+ private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) {
+ updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate);
+ }
+
+ private void updateLayoutStateToFillStart(int itemPosition, int offset) {
+ mLayoutState.mAvailable = offset - mOrientationHelper.getStartAfterPadding();
+ mLayoutState.mCurrentPosition = itemPosition;
+ mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
+ LayoutState.ITEM_DIRECTION_HEAD;
+ mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START;
+ mLayoutState.mOffset = offset;
+ mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
+
+ }
+
+ protected boolean isLayoutRTL() {
+ return getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ }
+
+ void ensureLayoutState() {
+ if (mLayoutState == null) {
+ mLayoutState = createLayoutState();
+ }
+ if (mOrientationHelper == null) {
+ mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
+ }
+ }
+
+ /**
+ * Test overrides this to plug some tracking and verification.
+ *
+ * @return A new LayoutState
+ */
+ LayoutState createLayoutState() {
+ return new LayoutState();
+ }
+
+ /**
+ * <p>Scroll the RecyclerView to make the position visible.</p>
+ *
+ * <p>RecyclerView will scroll the minimum amount that is necessary to make the
+ * target position visible. If you are looking for a similar behavior to
+ * {@link android.widget.ListView#setSelection(int)} or
+ * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
+ * {@link #scrollToPositionWithOffset(int, int)}.</p>
+ *
+ * <p>Note that scroll position change will not be reflected until the next layout call.</p>
+ *
+ * @param position Scroll to this adapter position
+ * @see #scrollToPositionWithOffset(int, int)
+ */
+ @Override
+ public void scrollToPosition(int position) {
+ mPendingScrollPosition = position;
+ mPendingScrollPositionOffset = INVALID_OFFSET;
+ if (mPendingSavedState != null) {
+ mPendingSavedState.invalidateAnchor();
+ }
+ requestLayout();
+ }
+
+ /**
+ * Scroll to the specified adapter position with the given offset from resolved layout
+ * start. Resolved layout start depends on {@link #getReverseLayout()},
+ * {@link View#getLayoutDirection()} and {@link #getStackFromEnd()}.
+ * <p>
+ * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling
+ * <code>scrollToPositionWithOffset(10, 20)</code> will layout such that
+ * <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom.
+ * <p>
+ * Note that scroll position change will not be reflected until the next layout call.
+ * <p>
+ * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
+ *
+ * @param position Index (starting at 0) of the reference item.
+ * @param offset The distance (in pixels) between the start edge of the item view and
+ * start edge of the RecyclerView.
+ * @see #setReverseLayout(boolean)
+ * @see #scrollToPosition(int)
+ */
+ public void scrollToPositionWithOffset(int position, int offset) {
+ mPendingScrollPosition = position;
+ mPendingScrollPositionOffset = offset;
+ if (mPendingSavedState != null) {
+ mPendingSavedState.invalidateAnchor();
+ }
+ requestLayout();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (mOrientation == VERTICAL) {
+ return 0;
+ }
+ return scrollBy(dx, recycler, state);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (mOrientation == HORIZONTAL) {
+ return 0;
+ }
+ return scrollBy(dy, recycler, state);
+ }
+
+ @Override
+ public int computeHorizontalScrollOffset(RecyclerView.State state) {
+ return computeScrollOffset(state);
+ }
+
+ @Override
+ public int computeVerticalScrollOffset(RecyclerView.State state) {
+ return computeScrollOffset(state);
+ }
+
+ @Override
+ public int computeHorizontalScrollExtent(RecyclerView.State state) {
+ return computeScrollExtent(state);
+ }
+
+ @Override
+ public int computeVerticalScrollExtent(RecyclerView.State state) {
+ return computeScrollExtent(state);
+ }
+
+ @Override
+ public int computeHorizontalScrollRange(RecyclerView.State state) {
+ return computeScrollRange(state);
+ }
+
+ @Override
+ public int computeVerticalScrollRange(RecyclerView.State state) {
+ return computeScrollRange(state);
+ }
+
+ private int computeScrollOffset(RecyclerView.State state) {
+ if (getChildCount() == 0) {
+ return 0;
+ }
+ ensureLayoutState();
+ return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper,
+ findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+ findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+ this, mSmoothScrollbarEnabled, mShouldReverseLayout);
+ }
+
+ private int computeScrollExtent(RecyclerView.State state) {
+ if (getChildCount() == 0) {
+ return 0;
+ }
+ ensureLayoutState();
+ return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper,
+ findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+ findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+ this, mSmoothScrollbarEnabled);
+ }
+
+ private int computeScrollRange(RecyclerView.State state) {
+ if (getChildCount() == 0) {
+ return 0;
+ }
+ ensureLayoutState();
+ return ScrollbarHelper.computeScrollRange(state, mOrientationHelper,
+ findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+ findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+ this, mSmoothScrollbarEnabled);
+ }
+
+ /**
+ * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed
+ * based on the number of visible pixels in the visible items. This however assumes that all
+ * list items have similar or equal widths or heights (depending on list orientation).
+ * If you use a list in which items have different dimensions, the scrollbar will change
+ * appearance as the user scrolls through the list. To avoid this issue, you need to disable
+ * this property.
+ *
+ * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based
+ * solely on the number of items in the adapter and the position of the visible items inside
+ * the adapter. This provides a stable scrollbar as the user navigates through a list of items
+ * with varying widths / heights.
+ *
+ * @param enabled Whether or not to enable smooth scrollbar.
+ *
+ * @see #setSmoothScrollbarEnabled(boolean)
+ */
+ public void setSmoothScrollbarEnabled(boolean enabled) {
+ mSmoothScrollbarEnabled = enabled;
+ }
+
+ /**
+ * Returns the current state of the smooth scrollbar feature. It is enabled by default.
+ *
+ * @return True if smooth scrollbar is enabled, false otherwise.
+ *
+ * @see #setSmoothScrollbarEnabled(boolean)
+ */
+ public boolean isSmoothScrollbarEnabled() {
+ return mSmoothScrollbarEnabled;
+ }
+
+ private void updateLayoutState(int layoutDirection, int requiredSpace,
+ boolean canUseExistingSpace, RecyclerView.State state) {
+ // If parent provides a hint, don't measure unlimited.
+ mLayoutState.mInfinite = resolveIsInfinite();
+ mLayoutState.mExtra = getExtraLayoutSpace(state);
+ mLayoutState.mLayoutDirection = layoutDirection;
+ int scrollingOffset;
+ if (layoutDirection == LayoutState.LAYOUT_END) {
+ mLayoutState.mExtra += mOrientationHelper.getEndPadding();
+ // get the first child in the direction we are going
+ final View child = getChildClosestToEnd();
+ // the direction in which we are traversing children
+ mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
+ : LayoutState.ITEM_DIRECTION_TAIL;
+ mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
+ mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
+ // calculate how much we can scroll without adding new children (independent of layout)
+ scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
+ - mOrientationHelper.getEndAfterPadding();
+
+ } else {
+ final View child = getChildClosestToStart();
+ mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding();
+ mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
+ : LayoutState.ITEM_DIRECTION_HEAD;
+ mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
+ mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
+ scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
+ + mOrientationHelper.getStartAfterPadding();
+ }
+ mLayoutState.mAvailable = requiredSpace;
+ if (canUseExistingSpace) {
+ mLayoutState.mAvailable -= scrollingOffset;
+ }
+ mLayoutState.mScrollingOffset = scrollingOffset;
+ }
+
+ boolean resolveIsInfinite() {
+ return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED
+ && mOrientationHelper.getEnd() == 0;
+ }
+
+ void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {
+ final int pos = layoutState.mCurrentPosition;
+ if (pos >= 0 && pos < state.getItemCount()) {
+ layoutPrefetchRegistry.addPosition(pos, layoutState.mScrollingOffset);
+ }
+ }
+
+ @Override
+ public void collectInitialPrefetchPositions(int adapterItemCount,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {
+ final boolean fromEnd;
+ final int anchorPos;
+ if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
+ // use restored state, since it hasn't been resolved yet
+ fromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
+ anchorPos = mPendingSavedState.mAnchorPosition;
+ } else {
+ resolveShouldLayoutReverse();
+ fromEnd = mShouldReverseLayout;
+ if (mPendingScrollPosition == NO_POSITION) {
+ anchorPos = fromEnd ? adapterItemCount - 1 : 0;
+ } else {
+ anchorPos = mPendingScrollPosition;
+ }
+ }
+
+ final int direction = fromEnd
+ ? LayoutState.ITEM_DIRECTION_HEAD
+ : LayoutState.ITEM_DIRECTION_TAIL;
+ int targetPos = anchorPos;
+ for (int i = 0; i < mInitialItemPrefetchCount; i++) {
+ if (targetPos >= 0 && targetPos < adapterItemCount) {
+ layoutPrefetchRegistry.addPosition(targetPos, 0);
+ } else {
+ break; // no more to prefetch
+ }
+ targetPos += direction;
+ }
+ }
+
+ /**
+ * Sets the number of items to prefetch in
+ * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines
+ * how many inner items should be prefetched when this LayoutManager's RecyclerView
+ * is nested inside another RecyclerView.
+ *
+ * <p>Set this value to the number of items this inner LayoutManager will display when it is
+ * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items
+ * so they are ready, avoiding jank as the inner RecyclerView is scrolled into the viewport.</p>
+ *
+ * <p>For example, take a vertically scrolling RecyclerView with horizontally scrolling inner
+ * RecyclerViews. The rows always have 4 items visible in them (or 5 if not aligned). Passing
+ * <code>4</code> to this method for each inner RecyclerView's LinearLayoutManager will enable
+ * RecyclerView's prefetching feature to do create/bind work for 4 views within a row early,
+ * before it is scrolled on screen, instead of just the default 2.</p>
+ *
+ * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView
+ * nested in another RecyclerView.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of
+ * views that will be visible in this view can incur unnecessary bind work, and an increase to
+ * the number of Views created and in active use.</p>
+ *
+ * @param itemCount Number of items to prefetch
+ *
+ * @see #isItemPrefetchEnabled()
+ * @see #getInitialItemPrefetchCount()
+ * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
+ */
+ public void setInitialPrefetchItemCount(int itemCount) {
+ mInitialItemPrefetchCount = itemCount;
+ }
+
+ /**
+ * Gets the number of items to prefetch in
+ * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines
+ * how many inner items should be prefetched when this LayoutManager's RecyclerView
+ * is nested inside another RecyclerView.
+ *
+ * @see #isItemPrefetchEnabled()
+ * @see #setInitialPrefetchItemCount(int)
+ * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
+ *
+ * @return number of items to prefetch.
+ */
+ public int getInitialItemPrefetchCount() {
+ return mInitialItemPrefetchCount;
+ }
+
+ @Override
+ public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {
+ int delta = (mOrientation == HORIZONTAL) ? dx : dy;
+ if (getChildCount() == 0 || delta == 0) {
+ // can't support this scroll, so don't bother prefetching
+ return;
+ }
+
+ final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
+ final int absDy = Math.abs(delta);
+ updateLayoutState(layoutDirection, absDy, true, state);
+ collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
+ }
+
+ int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
+ if (getChildCount() == 0 || dy == 0) {
+ return 0;
+ }
+ mLayoutState.mRecycle = true;
+ ensureLayoutState();
+ final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
+ final int absDy = Math.abs(dy);
+ updateLayoutState(layoutDirection, absDy, true, state);
+ final int consumed = mLayoutState.mScrollingOffset
+ + fill(recycler, mLayoutState, state, false);
+ if (consumed < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Don't have any more elements to scroll");
+ }
+ return 0;
+ }
+ final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
+ mOrientationHelper.offsetChildren(-scrolled);
+ if (DEBUG) {
+ Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
+ }
+ mLayoutState.mLastScrollDelta = scrolled;
+ return scrolled;
+ }
+
+ @Override
+ public void assertNotInLayoutOrScroll(String message) {
+ if (mPendingSavedState == null) {
+ super.assertNotInLayoutOrScroll(message);
+ }
+ }
+
+ /**
+ * Recycles children between given indices.
+ *
+ * @param startIndex inclusive
+ * @param endIndex exclusive
+ */
+ private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
+ if (startIndex == endIndex) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
+ }
+ if (endIndex > startIndex) {
+ for (int i = endIndex - 1; i >= startIndex; i--) {
+ removeAndRecycleViewAt(i, recycler);
+ }
+ } else {
+ for (int i = startIndex; i > endIndex; i--) {
+ removeAndRecycleViewAt(i, recycler);
+ }
+ }
+ }
+
+ /**
+ * Recycles views that went out of bounds after scrolling towards the end of the layout.
+ * <p>
+ * Checks both layout position and visible position to guarantee that the view is not visible.
+ *
+ * @param recycler Recycler instance of {@link com.android.internal.widget.RecyclerView}
+ * @param dt This can be used to add additional padding to the visible area. This is used
+ * to detect children that will go out of bounds after scrolling, without
+ * actually moving them.
+ */
+ private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
+ if (dt < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Called recycle from start with a negative value. This might happen"
+ + " during layout changes but may be sign of a bug");
+ }
+ return;
+ }
+ // ignore padding, ViewGroup may not clip children.
+ final int limit = dt;
+ final int childCount = getChildCount();
+ if (mShouldReverseLayout) {
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (mOrientationHelper.getDecoratedEnd(child) > limit
+ || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
+ // stop here
+ recycleChildren(recycler, childCount - 1, i);
+ return;
+ }
+ }
+ } else {
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (mOrientationHelper.getDecoratedEnd(child) > limit
+ || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
+ // stop here
+ recycleChildren(recycler, 0, i);
+ return;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Recycles views that went out of bounds after scrolling towards the start of the layout.
+ * <p>
+ * Checks both layout position and visible position to guarantee that the view is not visible.
+ *
+ * @param recycler Recycler instance of {@link com.android.internal.widget.RecyclerView}
+ * @param dt This can be used to add additional padding to the visible area. This is used
+ * to detect children that will go out of bounds after scrolling, without
+ * actually moving them.
+ */
+ private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
+ final int childCount = getChildCount();
+ if (dt < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Called recycle from end with a negative value. This might happen"
+ + " during layout changes but may be sign of a bug");
+ }
+ return;
+ }
+ final int limit = mOrientationHelper.getEnd() - dt;
+ if (mShouldReverseLayout) {
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (mOrientationHelper.getDecoratedStart(child) < limit
+ || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
+ // stop here
+ recycleChildren(recycler, 0, i);
+ return;
+ }
+ }
+ } else {
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (mOrientationHelper.getDecoratedStart(child) < limit
+ || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
+ // stop here
+ recycleChildren(recycler, childCount - 1, i);
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to call appropriate recycle method depending on current layout direction
+ *
+ * @param recycler Current recycler that is attached to RecyclerView
+ * @param layoutState Current layout state. Right now, this object does not change but
+ * we may consider moving it out of this view so passing around as a
+ * parameter for now, rather than accessing {@link #mLayoutState}
+ * @see #recycleViewsFromStart(com.android.internal.widget.RecyclerView.Recycler, int)
+ * @see #recycleViewsFromEnd(com.android.internal.widget.RecyclerView.Recycler, int)
+ * @see com.android.internal.widget.LinearLayoutManager.LayoutState#mLayoutDirection
+ */
+ private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
+ if (!layoutState.mRecycle || layoutState.mInfinite) {
+ return;
+ }
+ if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+ recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
+ } else {
+ recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
+ }
+ }
+
+ /**
+ * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
+ * independent from the rest of the {@link com.android.internal.widget.LinearLayoutManager}
+ * and with little change, can be made publicly available as a helper class.
+ *
+ * @param recycler Current recycler that is attached to RecyclerView
+ * @param layoutState Configuration on how we should fill out the available space.
+ * @param state Context passed by the RecyclerView to control scroll steps.
+ * @param stopOnFocusable If true, filling stops in the first focusable new child
+ * @return Number of pixels that it added. Useful for scroll functions.
+ */
+ int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
+ RecyclerView.State state, boolean stopOnFocusable) {
+ // max offset we should set is mFastScroll + available
+ final int start = layoutState.mAvailable;
+ if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
+ // TODO ugly bug fix. should not happen
+ if (layoutState.mAvailable < 0) {
+ layoutState.mScrollingOffset += layoutState.mAvailable;
+ }
+ recycleByLayoutState(recycler, layoutState);
+ }
+ int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
+ LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
+ while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
+ layoutChunkResult.resetInternal();
+ layoutChunk(recycler, state, layoutState, layoutChunkResult);
+ if (layoutChunkResult.mFinished) {
+ break;
+ }
+ layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
+ /**
+ * Consume the available space if:
+ * * layoutChunk did not request to be ignored
+ * * OR we are laying out scrap children
+ * * OR we are not doing pre-layout
+ */
+ if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
+ || !state.isPreLayout()) {
+ layoutState.mAvailable -= layoutChunkResult.mConsumed;
+ // we keep a separate remaining space because mAvailable is important for recycling
+ remainingSpace -= layoutChunkResult.mConsumed;
+ }
+
+ if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
+ layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
+ if (layoutState.mAvailable < 0) {
+ layoutState.mScrollingOffset += layoutState.mAvailable;
+ }
+ recycleByLayoutState(recycler, layoutState);
+ }
+ if (stopOnFocusable && layoutChunkResult.mFocusable) {
+ break;
+ }
+ }
+ if (DEBUG) {
+ validateChildOrder();
+ }
+ return start - layoutState.mAvailable;
+ }
+
+ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
+ LayoutState layoutState, LayoutChunkResult result) {
+ View view = layoutState.next(recycler);
+ if (view == null) {
+ if (DEBUG && layoutState.mScrapList == null) {
+ throw new RuntimeException("received null view when unexpected");
+ }
+ // if we are laying out views in scrap, this may return null which means there is
+ // no more items to layout.
+ result.mFinished = true;
+ return;
+ }
+ LayoutParams params = (LayoutParams) view.getLayoutParams();
+ if (layoutState.mScrapList == null) {
+ if (mShouldReverseLayout == (layoutState.mLayoutDirection
+ == LayoutState.LAYOUT_START)) {
+ addView(view);
+ } else {
+ addView(view, 0);
+ }
+ } else {
+ if (mShouldReverseLayout == (layoutState.mLayoutDirection
+ == LayoutState.LAYOUT_START)) {
+ addDisappearingView(view);
+ } else {
+ addDisappearingView(view, 0);
+ }
+ }
+ measureChildWithMargins(view, 0, 0);
+ result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
+ int left, top, right, bottom;
+ if (mOrientation == VERTICAL) {
+ if (isLayoutRTL()) {
+ right = getWidth() - getPaddingRight();
+ left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
+ } else {
+ left = getPaddingLeft();
+ right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
+ }
+ if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+ bottom = layoutState.mOffset;
+ top = layoutState.mOffset - result.mConsumed;
+ } else {
+ top = layoutState.mOffset;
+ bottom = layoutState.mOffset + result.mConsumed;
+ }
+ } else {
+ top = getPaddingTop();
+ bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
+
+ if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+ right = layoutState.mOffset;
+ left = layoutState.mOffset - result.mConsumed;
+ } else {
+ left = layoutState.mOffset;
+ right = layoutState.mOffset + result.mConsumed;
+ }
+ }
+ // We calculate everything with View's bounding box (which includes decor and margins)
+ // To calculate correct layout position, we subtract margins.
+ layoutDecoratedWithMargins(view, left, top, right, bottom);
+ if (DEBUG) {
+ Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
+ }
+ // Consume the available space if the view is not removed OR changed
+ if (params.isItemRemoved() || params.isItemChanged()) {
+ result.mIgnoreConsumed = true;
+ }
+ result.mFocusable = view.isFocusable();
+ }
+
+ @Override
+ boolean shouldMeasureTwice() {
+ return getHeightMode() != View.MeasureSpec.EXACTLY
+ && getWidthMode() != View.MeasureSpec.EXACTLY
+ && hasFlexibleChildInBothOrientations();
+ }
+
+ /**
+ * Converts a focusDirection to orientation.
+ *
+ * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+ * or 0 for not applicable
+ * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
+ * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
+ */
+ int convertFocusDirectionToLayoutDirection(int focusDirection) {
+ switch (focusDirection) {
+ case View.FOCUS_BACKWARD:
+ if (mOrientation == VERTICAL) {
+ return LayoutState.LAYOUT_START;
+ } else if (isLayoutRTL()) {
+ return LayoutState.LAYOUT_END;
+ } else {
+ return LayoutState.LAYOUT_START;
+ }
+ case View.FOCUS_FORWARD:
+ if (mOrientation == VERTICAL) {
+ return LayoutState.LAYOUT_END;
+ } else if (isLayoutRTL()) {
+ return LayoutState.LAYOUT_START;
+ } else {
+ return LayoutState.LAYOUT_END;
+ }
+ case View.FOCUS_UP:
+ return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
+ : LayoutState.INVALID_LAYOUT;
+ case View.FOCUS_DOWN:
+ return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
+ : LayoutState.INVALID_LAYOUT;
+ case View.FOCUS_LEFT:
+ return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
+ : LayoutState.INVALID_LAYOUT;
+ case View.FOCUS_RIGHT:
+ return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
+ : LayoutState.INVALID_LAYOUT;
+ default:
+ if (DEBUG) {
+ Log.d(TAG, "Unknown focus request:" + focusDirection);
+ }
+ return LayoutState.INVALID_LAYOUT;
+ }
+
+ }
+
+ /**
+ * Convenience method to find the child closes to start. Caller should check it has enough
+ * children.
+ *
+ * @return The child closes to start of the layout from user's perspective.
+ */
+ private View getChildClosestToStart() {
+ return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0);
+ }
+
+ /**
+ * Convenience method to find the child closes to end. Caller should check it has enough
+ * children.
+ *
+ * @return The child closes to end of the layout from user's perspective.
+ */
+ private View getChildClosestToEnd() {
+ return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
+ }
+
+ /**
+ * Convenience method to find the visible child closes to start. Caller should check if it has
+ * enough children.
+ *
+ * @param completelyVisible Whether child should be completely visible or not
+ * @return The first visible child closest to start of the layout from user's perspective.
+ */
+ private View findFirstVisibleChildClosestToStart(boolean completelyVisible,
+ boolean acceptPartiallyVisible) {
+ if (mShouldReverseLayout) {
+ return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
+ acceptPartiallyVisible);
+ } else {
+ return findOneVisibleChild(0, getChildCount(), completelyVisible,
+ acceptPartiallyVisible);
+ }
+ }
+
+ /**
+ * Convenience method to find the visible child closes to end. Caller should check if it has
+ * enough children.
+ *
+ * @param completelyVisible Whether child should be completely visible or not
+ * @return The first visible child closest to end of the layout from user's perspective.
+ */
+ private View findFirstVisibleChildClosestToEnd(boolean completelyVisible,
+ boolean acceptPartiallyVisible) {
+ if (mShouldReverseLayout) {
+ return findOneVisibleChild(0, getChildCount(), completelyVisible,
+ acceptPartiallyVisible);
+ } else {
+ return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
+ acceptPartiallyVisible);
+ }
+ }
+
+
+ /**
+ * Among the children that are suitable to be considered as an anchor child, returns the one
+ * closest to the end of the layout.
+ * <p>
+ * Due to ambiguous adapter updates or children being removed, some children's positions may be
+ * invalid. This method is a best effort to find a position within adapter bounds if possible.
+ * <p>
+ * It also prioritizes children that are within the visible bounds.
+ * @return A View that can be used an an anchor View.
+ */
+ private View findReferenceChildClosestToEnd(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ return mShouldReverseLayout ? findFirstReferenceChild(recycler, state) :
+ findLastReferenceChild(recycler, state);
+ }
+
+ /**
+ * Among the children that are suitable to be considered as an anchor child, returns the one
+ * closest to the start of the layout.
+ * <p>
+ * Due to ambiguous adapter updates or children being removed, some children's positions may be
+ * invalid. This method is a best effort to find a position within adapter bounds if possible.
+ * <p>
+ * It also prioritizes children that are within the visible bounds.
+ *
+ * @return A View that can be used an an anchor View.
+ */
+ private View findReferenceChildClosestToStart(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ return mShouldReverseLayout ? findLastReferenceChild(recycler, state) :
+ findFirstReferenceChild(recycler, state);
+ }
+
+ private View findFirstReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ return findReferenceChild(recycler, state, 0, getChildCount(), state.getItemCount());
+ }
+
+ private View findLastReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ return findReferenceChild(recycler, state, getChildCount() - 1, -1, state.getItemCount());
+ }
+
+ // overridden by GridLayoutManager
+ View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state,
+ int start, int end, int itemCount) {
+ ensureLayoutState();
+ View invalidMatch = null;
+ View outOfBoundsMatch = null;
+ final int boundsStart = mOrientationHelper.getStartAfterPadding();
+ final int boundsEnd = mOrientationHelper.getEndAfterPadding();
+ final int diff = end > start ? 1 : -1;
+ for (int i = start; i != end; i += diff) {
+ final View view = getChildAt(i);
+ final int position = getPosition(view);
+ if (position >= 0 && position < itemCount) {
+ if (((LayoutParams) view.getLayoutParams()).isItemRemoved()) {
+ if (invalidMatch == null) {
+ invalidMatch = view; // removed item, least preferred
+ }
+ } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd
+ || mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
+ if (outOfBoundsMatch == null) {
+ outOfBoundsMatch = view; // item is not visible, less preferred
+ }
+ } else {
+ return view;
+ }
+ }
+ }
+ return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
+ }
+
+ /**
+ * Returns the adapter position of the first visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
+ * <p>
+ * Note that, this value is not affected by layout orientation or item order traversal.
+ * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+ * not in the layout.
+ * <p>
+ * If RecyclerView has item decorators, they will be considered in calculations as well.
+ * <p>
+ * LayoutManager may pre-cache some views that are not necessarily visible. Those views
+ * are ignored in this method.
+ *
+ * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
+ * there aren't any visible items.
+ * @see #findFirstCompletelyVisibleItemPosition()
+ * @see #findLastVisibleItemPosition()
+ */
+ public int findFirstVisibleItemPosition() {
+ final View child = findOneVisibleChild(0, getChildCount(), false, true);
+ return child == null ? NO_POSITION : getPosition(child);
+ }
+
+ /**
+ * Returns the adapter position of the first fully visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
+ * <p>
+ * Note that bounds check is only performed in the current orientation. That means, if
+ * LayoutManager is horizontal, it will only check the view's left and right edges.
+ *
+ * @return The adapter position of the first fully visible item or
+ * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
+ * @see #findFirstVisibleItemPosition()
+ * @see #findLastCompletelyVisibleItemPosition()
+ */
+ public int findFirstCompletelyVisibleItemPosition() {
+ final View child = findOneVisibleChild(0, getChildCount(), true, false);
+ return child == null ? NO_POSITION : getPosition(child);
+ }
+
+ /**
+ * Returns the adapter position of the last visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
+ * <p>
+ * Note that, this value is not affected by layout orientation or item order traversal.
+ * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+ * not in the layout.
+ * <p>
+ * If RecyclerView has item decorators, they will be considered in calculations as well.
+ * <p>
+ * LayoutManager may pre-cache some views that are not necessarily visible. Those views
+ * are ignored in this method.
+ *
+ * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
+ * there aren't any visible items.
+ * @see #findLastCompletelyVisibleItemPosition()
+ * @see #findFirstVisibleItemPosition()
+ */
+ public int findLastVisibleItemPosition() {
+ final View child = findOneVisibleChild(getChildCount() - 1, -1, false, true);
+ return child == null ? NO_POSITION : getPosition(child);
+ }
+
+ /**
+ * Returns the adapter position of the last fully visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
+ * <p>
+ * Note that bounds check is only performed in the current orientation. That means, if
+ * LayoutManager is horizontal, it will only check the view's left and right edges.
+ *
+ * @return The adapter position of the last fully visible view or
+ * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
+ * @see #findLastVisibleItemPosition()
+ * @see #findFirstCompletelyVisibleItemPosition()
+ */
+ public int findLastCompletelyVisibleItemPosition() {
+ final View child = findOneVisibleChild(getChildCount() - 1, -1, true, false);
+ return child == null ? NO_POSITION : getPosition(child);
+ }
+
+ View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible,
+ boolean acceptPartiallyVisible) {
+ ensureLayoutState();
+ final int start = mOrientationHelper.getStartAfterPadding();
+ final int end = mOrientationHelper.getEndAfterPadding();
+ final int next = toIndex > fromIndex ? 1 : -1;
+ View partiallyVisible = null;
+ for (int i = fromIndex; i != toIndex; i += next) {
+ final View child = getChildAt(i);
+ final int childStart = mOrientationHelper.getDecoratedStart(child);
+ final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+ if (childStart < end && childEnd > start) {
+ if (completelyVisible) {
+ if (childStart >= start && childEnd <= end) {
+ return child;
+ } else if (acceptPartiallyVisible && partiallyVisible == null) {
+ partiallyVisible = child;
+ }
+ } else {
+ return child;
+ }
+ }
+ }
+ return partiallyVisible;
+ }
+
+ @Override
+ public View onFocusSearchFailed(View focused, int focusDirection,
+ RecyclerView.Recycler recycler, RecyclerView.State state) {
+ resolveShouldLayoutReverse();
+ if (getChildCount() == 0) {
+ return null;
+ }
+
+ final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
+ if (layoutDir == LayoutState.INVALID_LAYOUT) {
+ return null;
+ }
+ ensureLayoutState();
+ final View referenceChild;
+ if (layoutDir == LayoutState.LAYOUT_START) {
+ referenceChild = findReferenceChildClosestToStart(recycler, state);
+ } else {
+ referenceChild = findReferenceChildClosestToEnd(recycler, state);
+ }
+ if (referenceChild == null) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "Cannot find a child with a valid position to be used for focus search.");
+ }
+ return null;
+ }
+ ensureLayoutState();
+ final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace());
+ updateLayoutState(layoutDir, maxScroll, false, state);
+ mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
+ mLayoutState.mRecycle = false;
+ fill(recycler, mLayoutState, state, true);
+ final View nextFocus;
+ if (layoutDir == LayoutState.LAYOUT_START) {
+ nextFocus = getChildClosestToStart();
+ } else {
+ nextFocus = getChildClosestToEnd();
+ }
+ if (nextFocus == referenceChild || !nextFocus.isFocusable()) {
+ return null;
+ }
+ return nextFocus;
+ }
+
+ /**
+ * Used for debugging.
+ * Logs the internal representation of children to default logger.
+ */
+ private void logChildren() {
+ Log.d(TAG, "internal representation of views on the screen");
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ Log.d(TAG, "item " + getPosition(child) + ", coord:"
+ + mOrientationHelper.getDecoratedStart(child));
+ }
+ Log.d(TAG, "==============");
+ }
+
+ /**
+ * Used for debugging.
+ * Validates that child views are laid out in correct order. This is important because rest of
+ * the algorithm relies on this constraint.
+ *
+ * In default layout, child 0 should be closest to screen position 0 and last child should be
+ * closest to position WIDTH or HEIGHT.
+ * In reverse layout, last child should be closes to screen position 0 and first child should
+ * be closest to position WIDTH or HEIGHT
+ */
+ void validateChildOrder() {
+ Log.d(TAG, "validating child count " + getChildCount());
+ if (getChildCount() < 1) {
+ return;
+ }
+ int lastPos = getPosition(getChildAt(0));
+ int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0));
+ if (mShouldReverseLayout) {
+ for (int i = 1; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ int pos = getPosition(child);
+ int screenLoc = mOrientationHelper.getDecoratedStart(child);
+ if (pos < lastPos) {
+ logChildren();
+ throw new RuntimeException("detected invalid position. loc invalid? "
+ + (screenLoc < lastScreenLoc));
+ }
+ if (screenLoc > lastScreenLoc) {
+ logChildren();
+ throw new RuntimeException("detected invalid location");
+ }
+ }
+ } else {
+ for (int i = 1; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ int pos = getPosition(child);
+ int screenLoc = mOrientationHelper.getDecoratedStart(child);
+ if (pos < lastPos) {
+ logChildren();
+ throw new RuntimeException("detected invalid position. loc invalid? "
+ + (screenLoc < lastScreenLoc));
+ }
+ if (screenLoc < lastScreenLoc) {
+ logChildren();
+ throw new RuntimeException("detected invalid location");
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean supportsPredictiveItemAnimations() {
+ return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
+ }
+
+ /**
+ * @hide This method should be called by ItemTouchHelper only.
+ */
+ @Override
+ public void prepareForDrop(View view, View target, int x, int y) {
+ assertNotInLayoutOrScroll("Cannot drop a view during a scroll or layout calculation");
+ ensureLayoutState();
+ resolveShouldLayoutReverse();
+ final int myPos = getPosition(view);
+ final int targetPos = getPosition(target);
+ final int dropDirection = myPos < targetPos ? LayoutState.ITEM_DIRECTION_TAIL
+ : LayoutState.ITEM_DIRECTION_HEAD;
+ if (mShouldReverseLayout) {
+ if (dropDirection == LayoutState.ITEM_DIRECTION_TAIL) {
+ scrollToPositionWithOffset(targetPos,
+ mOrientationHelper.getEndAfterPadding()
+ - (mOrientationHelper.getDecoratedStart(target)
+ + mOrientationHelper.getDecoratedMeasurement(view)));
+ } else {
+ scrollToPositionWithOffset(targetPos,
+ mOrientationHelper.getEndAfterPadding()
+ - mOrientationHelper.getDecoratedEnd(target));
+ }
+ } else {
+ if (dropDirection == LayoutState.ITEM_DIRECTION_HEAD) {
+ scrollToPositionWithOffset(targetPos, mOrientationHelper.getDecoratedStart(target));
+ } else {
+ scrollToPositionWithOffset(targetPos,
+ mOrientationHelper.getDecoratedEnd(target)
+ - mOrientationHelper.getDecoratedMeasurement(view));
+ }
+ }
+ }
+
+ /**
+ * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
+ * space.
+ */
+ static class LayoutState {
+
+ static final String TAG = "LLM#LayoutState";
+
+ static final int LAYOUT_START = -1;
+
+ static final int LAYOUT_END = 1;
+
+ static final int INVALID_LAYOUT = Integer.MIN_VALUE;
+
+ static final int ITEM_DIRECTION_HEAD = -1;
+
+ static final int ITEM_DIRECTION_TAIL = 1;
+
+ static final int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE;
+
+ /**
+ * We may not want to recycle children in some cases (e.g. layout)
+ */
+ boolean mRecycle = true;
+
+ /**
+ * Pixel offset where layout should start
+ */
+ int mOffset;
+
+ /**
+ * Number of pixels that we should fill, in the layout direction.
+ */
+ int mAvailable;
+
+ /**
+ * Current position on the adapter to get the next item.
+ */
+ int mCurrentPosition;
+
+ /**
+ * Defines the direction in which the data adapter is traversed.
+ * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
+ */
+ int mItemDirection;
+
+ /**
+ * Defines the direction in which the layout is filled.
+ * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
+ */
+ int mLayoutDirection;
+
+ /**
+ * Used when LayoutState is constructed in a scrolling state.
+ * It should be set the amount of scrolling we can make without creating a new view.
+ * Settings this is required for efficient view recycling.
+ */
+ int mScrollingOffset;
+
+ /**
+ * Used if you want to pre-layout items that are not yet visible.
+ * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
+ * {@link #mExtra} is not considered to avoid recycling visible children.
+ */
+ int mExtra = 0;
+
+ /**
+ * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value
+ * is set to true, we skip removed views since they should not be laid out in post layout
+ * step.
+ */
+ boolean mIsPreLayout = false;
+
+ /**
+ * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)}
+ * amount.
+ */
+ int mLastScrollDelta;
+
+ /**
+ * When LLM needs to layout particular views, it sets this list in which case, LayoutState
+ * will only return views from this list and return null if it cannot find an item.
+ */
+ List<RecyclerView.ViewHolder> mScrapList = null;
+
+ /**
+ * Used when there is no limit in how many views can be laid out.
+ */
+ boolean mInfinite;
+
+ /**
+ * @return true if there are more items in the data adapter
+ */
+ boolean hasMore(RecyclerView.State state) {
+ return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
+ }
+
+ /**
+ * Gets the view for the next element that we should layout.
+ * Also updates current item index to the next item, based on {@link #mItemDirection}
+ *
+ * @return The next element that we should layout.
+ */
+ View next(RecyclerView.Recycler recycler) {
+ if (mScrapList != null) {
+ return nextViewFromScrapList();
+ }
+ final View view = recycler.getViewForPosition(mCurrentPosition);
+ mCurrentPosition += mItemDirection;
+ return view;
+ }
+
+ /**
+ * Returns the next item from the scrap list.
+ * <p>
+ * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection
+ *
+ * @return View if an item in the current position or direction exists if not null.
+ */
+ private View nextViewFromScrapList() {
+ final int size = mScrapList.size();
+ for (int i = 0; i < size; i++) {
+ final View view = mScrapList.get(i).itemView;
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (lp.isItemRemoved()) {
+ continue;
+ }
+ if (mCurrentPosition == lp.getViewLayoutPosition()) {
+ assignPositionFromScrapList(view);
+ return view;
+ }
+ }
+ return null;
+ }
+
+ public void assignPositionFromScrapList() {
+ assignPositionFromScrapList(null);
+ }
+
+ public void assignPositionFromScrapList(View ignore) {
+ final View closest = nextViewInLimitedList(ignore);
+ if (closest == null) {
+ mCurrentPosition = NO_POSITION;
+ } else {
+ mCurrentPosition = ((LayoutParams) closest.getLayoutParams())
+ .getViewLayoutPosition();
+ }
+ }
+
+ public View nextViewInLimitedList(View ignore) {
+ int size = mScrapList.size();
+ View closest = null;
+ int closestDistance = Integer.MAX_VALUE;
+ if (DEBUG && mIsPreLayout) {
+ throw new IllegalStateException("Scrap list cannot be used in pre layout");
+ }
+ for (int i = 0; i < size; i++) {
+ View view = mScrapList.get(i).itemView;
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (view == ignore || lp.isItemRemoved()) {
+ continue;
+ }
+ final int distance = (lp.getViewLayoutPosition() - mCurrentPosition)
+ * mItemDirection;
+ if (distance < 0) {
+ continue; // item is not in current direction
+ }
+ if (distance < closestDistance) {
+ closest = view;
+ closestDistance = distance;
+ if (distance == 0) {
+ break;
+ }
+ }
+ }
+ return closest;
+ }
+
+ void log() {
+ Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:"
+ + mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static class SavedState implements Parcelable {
+
+ int mAnchorPosition;
+
+ int mAnchorOffset;
+
+ boolean mAnchorLayoutFromEnd;
+
+ public SavedState() {
+
+ }
+
+ SavedState(Parcel in) {
+ mAnchorPosition = in.readInt();
+ mAnchorOffset = in.readInt();
+ mAnchorLayoutFromEnd = in.readInt() == 1;
+ }
+
+ public SavedState(SavedState other) {
+ mAnchorPosition = other.mAnchorPosition;
+ mAnchorOffset = other.mAnchorOffset;
+ mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
+ }
+
+ boolean hasValidAnchor() {
+ return mAnchorPosition >= 0;
+ }
+
+ void invalidateAnchor() {
+ mAnchorPosition = NO_POSITION;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mAnchorPosition);
+ dest.writeInt(mAnchorOffset);
+ dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ /**
+ * Simple data class to keep Anchor information
+ */
+ class AnchorInfo {
+ int mPosition;
+ int mCoordinate;
+ boolean mLayoutFromEnd;
+ boolean mValid;
+
+ AnchorInfo() {
+ reset();
+ }
+
+ void reset() {
+ mPosition = NO_POSITION;
+ mCoordinate = INVALID_OFFSET;
+ mLayoutFromEnd = false;
+ mValid = false;
+ }
+
+ /**
+ * assigns anchor coordinate from the RecyclerView's padding depending on current
+ * layoutFromEnd value
+ */
+ void assignCoordinateFromPadding() {
+ mCoordinate = mLayoutFromEnd
+ ? mOrientationHelper.getEndAfterPadding()
+ : mOrientationHelper.getStartAfterPadding();
+ }
+
+ @Override
+ public String toString() {
+ return "AnchorInfo{"
+ + "mPosition=" + mPosition
+ + ", mCoordinate=" + mCoordinate
+ + ", mLayoutFromEnd=" + mLayoutFromEnd
+ + ", mValid=" + mValid
+ + '}';
+ }
+
+ boolean isViewValidAsAnchor(View child, RecyclerView.State state) {
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ return !lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0
+ && lp.getViewLayoutPosition() < state.getItemCount();
+ }
+
+ public void assignFromViewAndKeepVisibleRect(View child) {
+ final int spaceChange = mOrientationHelper.getTotalSpaceChange();
+ if (spaceChange >= 0) {
+ assignFromView(child);
+ return;
+ }
+ mPosition = getPosition(child);
+ if (mLayoutFromEnd) {
+ final int prevLayoutEnd = mOrientationHelper.getEndAfterPadding() - spaceChange;
+ final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+ final int previousEndMargin = prevLayoutEnd - childEnd;
+ mCoordinate = mOrientationHelper.getEndAfterPadding() - previousEndMargin;
+ // ensure we did not push child's top out of bounds because of this
+ if (previousEndMargin > 0) { // we have room to shift bottom if necessary
+ final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
+ final int estimatedChildStart = mCoordinate - childSize;
+ final int layoutStart = mOrientationHelper.getStartAfterPadding();
+ final int previousStartMargin = mOrientationHelper.getDecoratedStart(child)
+ - layoutStart;
+ final int startReference = layoutStart + Math.min(previousStartMargin, 0);
+ final int startMargin = estimatedChildStart - startReference;
+ if (startMargin < 0) {
+ // offset to make top visible but not too much
+ mCoordinate += Math.min(previousEndMargin, -startMargin);
+ }
+ }
+ } else {
+ final int childStart = mOrientationHelper.getDecoratedStart(child);
+ final int startMargin = childStart - mOrientationHelper.getStartAfterPadding();
+ mCoordinate = childStart;
+ if (startMargin > 0) { // we have room to fix end as well
+ final int estimatedEnd = childStart
+ + mOrientationHelper.getDecoratedMeasurement(child);
+ final int previousLayoutEnd = mOrientationHelper.getEndAfterPadding()
+ - spaceChange;
+ final int previousEndMargin = previousLayoutEnd
+ - mOrientationHelper.getDecoratedEnd(child);
+ final int endReference = mOrientationHelper.getEndAfterPadding()
+ - Math.min(0, previousEndMargin);
+ final int endMargin = endReference - estimatedEnd;
+ if (endMargin < 0) {
+ mCoordinate -= Math.min(startMargin, -endMargin);
+ }
+ }
+ }
+ }
+
+ public void assignFromView(View child) {
+ if (mLayoutFromEnd) {
+ mCoordinate = mOrientationHelper.getDecoratedEnd(child)
+ + mOrientationHelper.getTotalSpaceChange();
+ } else {
+ mCoordinate = mOrientationHelper.getDecoratedStart(child);
+ }
+
+ mPosition = getPosition(child);
+ }
+ }
+
+ protected static class LayoutChunkResult {
+ public int mConsumed;
+ public boolean mFinished;
+ public boolean mIgnoreConsumed;
+ public boolean mFocusable;
+
+ void resetInternal() {
+ mConsumed = 0;
+ mFinished = false;
+ mIgnoreConsumed = false;
+ mFocusable = false;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/LinearSmoothScroller.java b/core/java/com/android/internal/widget/LinearSmoothScroller.java
new file mode 100644
index 0000000..d024f21
--- /dev/null
+++ b/core/java/com/android/internal/widget/LinearSmoothScroller.java
@@ -0,0 +1,361 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * {@link RecyclerView.SmoothScroller} implementation which uses a {@link LinearInterpolator} until
+ * the target position becomes a child of the RecyclerView and then uses a
+ * {@link DecelerateInterpolator} to slowly approach to target position.
+ * <p>
+ * If the {@link RecyclerView.LayoutManager} you are using does not implement the
+ * {@link RecyclerView.SmoothScroller.ScrollVectorProvider} interface, then you must override the
+ * {@link #computeScrollVectorForPosition(int)} method. All the LayoutManagers bundled with
+ * the support library implement this interface.
+ */
+public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
+
+ private static final String TAG = "LinearSmoothScroller";
+
+ private static final boolean DEBUG = false;
+
+ private static final float MILLISECONDS_PER_INCH = 25f;
+
+ private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
+
+ /**
+ * Align child view's left or top with parent view's left or top
+ *
+ * @see #calculateDtToFit(int, int, int, int, int)
+ * @see #calculateDxToMakeVisible(android.view.View, int)
+ * @see #calculateDyToMakeVisible(android.view.View, int)
+ */
+ public static final int SNAP_TO_START = -1;
+
+ /**
+ * Align child view's right or bottom with parent view's right or bottom
+ *
+ * @see #calculateDtToFit(int, int, int, int, int)
+ * @see #calculateDxToMakeVisible(android.view.View, int)
+ * @see #calculateDyToMakeVisible(android.view.View, int)
+ */
+ public static final int SNAP_TO_END = 1;
+
+ /**
+ * <p>Decides if the child should be snapped from start or end, depending on where it
+ * currently is in relation to its parent.</p>
+ * <p>For instance, if the view is virtually on the left of RecyclerView, using
+ * {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}</p>
+ *
+ * @see #calculateDtToFit(int, int, int, int, int)
+ * @see #calculateDxToMakeVisible(android.view.View, int)
+ * @see #calculateDyToMakeVisible(android.view.View, int)
+ */
+ public static final int SNAP_TO_ANY = 0;
+
+ // Trigger a scroll to a further distance than TARGET_SEEK_SCROLL_DISTANCE_PX so that if target
+ // view is not laid out until interim target position is reached, we can detect the case before
+ // scrolling slows down and reschedule another interim target scroll
+ private static final float TARGET_SEEK_EXTRA_SCROLL_RATIO = 1.2f;
+
+ protected final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
+
+ protected final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
+
+ protected PointF mTargetVector;
+
+ private final float MILLISECONDS_PER_PX;
+
+ // Temporary variables to keep track of the interim scroll target. These values do not
+ // point to a real item position, rather point to an estimated location pixels.
+ protected int mInterimTargetDx = 0, mInterimTargetDy = 0;
+
+ public LinearSmoothScroller(Context context) {
+ MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onStart() {
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+ final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
+ final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
+ final int distance = (int) Math.sqrt(dx * dx + dy * dy);
+ final int time = calculateTimeForDeceleration(distance);
+ if (time > 0) {
+ action.update(-dx, -dy, time, mDecelerateInterpolator);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {
+ if (getChildCount() == 0) {
+ stop();
+ return;
+ }
+ //noinspection PointlessBooleanExpression
+ if (DEBUG && mTargetVector != null
+ && ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
+ throw new IllegalStateException("Scroll happened in the opposite direction"
+ + " of the target. Some calculations are wrong");
+ }
+ mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
+ mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);
+
+ if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
+ updateActionForInterimTarget(action);
+ } // everything is valid, keep going
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onStop() {
+ mInterimTargetDx = mInterimTargetDy = 0;
+ mTargetVector = null;
+ }
+
+ /**
+ * Calculates the scroll speed.
+ *
+ * @param displayMetrics DisplayMetrics to be used for real dimension calculations
+ * @return The time (in ms) it should take for each pixel. For instance, if returned value is
+ * 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
+ */
+ protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+ return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+ }
+
+ /**
+ * <p>Calculates the time for deceleration so that transition from LinearInterpolator to
+ * DecelerateInterpolator looks smooth.</p>
+ *
+ * @param dx Distance to scroll
+ * @return Time for DecelerateInterpolator to smoothly traverse the distance when transitioning
+ * from LinearInterpolation
+ */
+ protected int calculateTimeForDeceleration(int dx) {
+ // we want to cover same area with the linear interpolator for the first 10% of the
+ // interpolation. After that, deceleration will take control.
+ // area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x
+ // which gives 0.100028 when x = .3356
+ // this is why we divide linear scrolling time with .3356
+ return (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
+ }
+
+ /**
+ * Calculates the time it should take to scroll the given distance (in pixels)
+ *
+ * @param dx Distance in pixels that we want to scroll
+ * @return Time in milliseconds
+ * @see #calculateSpeedPerPixel(android.util.DisplayMetrics)
+ */
+ protected int calculateTimeForScrolling(int dx) {
+ // In a case where dx is very small, rounding may return 0 although dx > 0.
+ // To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
+ // time.
+ return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
+ }
+
+ /**
+ * When scrolling towards a child view, this method defines whether we should align the left
+ * or the right edge of the child with the parent RecyclerView.
+ *
+ * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
+ * @see #SNAP_TO_START
+ * @see #SNAP_TO_END
+ * @see #SNAP_TO_ANY
+ */
+ protected int getHorizontalSnapPreference() {
+ return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
+ mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
+ }
+
+ /**
+ * When scrolling towards a child view, this method defines whether we should align the top
+ * or the bottom edge of the child with the parent RecyclerView.
+ *
+ * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
+ * @see #SNAP_TO_START
+ * @see #SNAP_TO_END
+ * @see #SNAP_TO_ANY
+ */
+ protected int getVerticalSnapPreference() {
+ return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
+ mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
+ }
+
+ /**
+ * When the target scroll position is not a child of the RecyclerView, this method calculates
+ * a direction vector towards that child and triggers a smooth scroll.
+ *
+ * @see #computeScrollVectorForPosition(int)
+ */
+ protected void updateActionForInterimTarget(Action action) {
+ // find an interim target position
+ PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
+ if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) {
+ final int target = getTargetPosition();
+ action.jumpTo(target);
+ stop();
+ return;
+ }
+ normalize(scrollVector);
+ mTargetVector = scrollVector;
+
+ mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
+ mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
+ final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
+ // To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
+ // interim target. Since we track the distance travelled in onSeekTargetStep callback, it
+ // won't actually scroll more than what we need.
+ action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO),
+ (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO),
+ (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
+ }
+
+ private int clampApplyScroll(int tmpDt, int dt) {
+ final int before = tmpDt;
+ tmpDt -= dt;
+ if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset
+ return 0;
+ }
+ return tmpDt;
+ }
+
+ /**
+ * Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and
+ * {@link #calculateDyToMakeVisible(android.view.View, int)}
+ */
+ public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
+ snapPreference) {
+ switch (snapPreference) {
+ case SNAP_TO_START:
+ return boxStart - viewStart;
+ case SNAP_TO_END:
+ return boxEnd - viewEnd;
+ case SNAP_TO_ANY:
+ final int dtStart = boxStart - viewStart;
+ if (dtStart > 0) {
+ return dtStart;
+ }
+ final int dtEnd = boxEnd - viewEnd;
+ if (dtEnd < 0) {
+ return dtEnd;
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("snap preference should be one of the"
+ + " constants defined in SmoothScroller, starting with SNAP_");
+ }
+ return 0;
+ }
+
+ /**
+ * Calculates the vertical scroll amount necessary to make the given view fully visible
+ * inside the RecyclerView.
+ *
+ * @param view The view which we want to make fully visible
+ * @param snapPreference The edge which the view should snap to when entering the visible
+ * area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
+ * {@link #SNAP_TO_ANY}.
+ * @return The vertical scroll amount necessary to make the view visible with the given
+ * snap preference.
+ */
+ public int calculateDyToMakeVisible(View view, int snapPreference) {
+ final RecyclerView.LayoutManager layoutManager = getLayoutManager();
+ if (layoutManager == null || !layoutManager.canScrollVertically()) {
+ return 0;
+ }
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
+ final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
+ final int start = layoutManager.getPaddingTop();
+ final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
+ return calculateDtToFit(top, bottom, start, end, snapPreference);
+ }
+
+ /**
+ * Calculates the horizontal scroll amount necessary to make the given view fully visible
+ * inside the RecyclerView.
+ *
+ * @param view The view which we want to make fully visible
+ * @param snapPreference The edge which the view should snap to when entering the visible
+ * area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
+ * {@link #SNAP_TO_END}
+ * @return The vertical scroll amount necessary to make the view visible with the given
+ * snap preference.
+ */
+ public int calculateDxToMakeVisible(View view, int snapPreference) {
+ final RecyclerView.LayoutManager layoutManager = getLayoutManager();
+ if (layoutManager == null || !layoutManager.canScrollHorizontally()) {
+ return 0;
+ }
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
+ final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
+ final int start = layoutManager.getPaddingLeft();
+ final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
+ return calculateDtToFit(left, right, start, end, snapPreference);
+ }
+
+ /**
+ * Compute the scroll vector for a given target position.
+ * <p>
+ * This method can return null if the layout manager cannot calculate a scroll vector
+ * for the given position (e.g. it has no current scroll position).
+ *
+ * @param targetPosition the position to which the scroller is scrolling
+ *
+ * @return the scroll vector for a given target position
+ */
+ @Nullable
+ public PointF computeScrollVectorForPosition(int targetPosition) {
+ RecyclerView.LayoutManager layoutManager = getLayoutManager();
+ if (layoutManager instanceof ScrollVectorProvider) {
+ return ((ScrollVectorProvider) layoutManager)
+ .computeScrollVectorForPosition(targetPosition);
+ }
+ Log.w(TAG, "You should override computeScrollVectorForPosition when the LayoutManager"
+ + " does not implement " + ScrollVectorProvider.class.getCanonicalName());
+ return null;
+ }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index a29882b4..ece5540 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1314,7 +1314,7 @@
}
private boolean isDoNotAskCredentialsOnBootSet() {
- return mDevicePolicyManager.getDoNotAskCredentialsOnBoot();
+ return getDevicePolicyManager().getDoNotAskCredentialsOnBoot();
}
private boolean shouldEncryptWithCredentials(boolean defaultValue) {
diff --git a/core/java/com/android/internal/widget/NestedScrollingChild.java b/core/java/com/android/internal/widget/NestedScrollingChild.java
new file mode 100644
index 0000000..20285b5
--- /dev/null
+++ b/core/java/com/android/internal/widget/NestedScrollingChild.java
@@ -0,0 +1,226 @@
+/*
+ * 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.internal.widget;
+
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+/**
+ * This interface should be implemented by {@link android.view.View View} subclasses that wish
+ * to support dispatching nested scrolling operations to a cooperating parent
+ * {@link android.view.ViewGroup ViewGroup}.
+ *
+ * <p>Classes implementing this interface should create a final instance of a
+ * {@link NestedScrollingChildHelper} as a field and delegate any View methods to the
+ * <code>NestedScrollingChildHelper</code> methods of the same signature.</p>
+ *
+ * <p>Views invoking nested scrolling functionality should always do so from the relevant
+ * {@link ViewCompat}, {@link ViewGroupCompat} or {@link ViewParentCompat} compatibility
+ * shim static methods. This ensures interoperability with nested scrolling views on Android
+ * 5.0 Lollipop and newer.</p>
+ */
+public interface NestedScrollingChild {
+ /**
+ * Enable or disable nested scrolling for this view.
+ *
+ * <p>If this property is set to true the view will be permitted to initiate nested
+ * scrolling operations with a compatible parent view in the current hierarchy. If this
+ * view does not implement nested scrolling this will have no effect. Disabling nested scrolling
+ * while a nested scroll is in progress has the effect of {@link #stopNestedScroll() stopping}
+ * the nested scroll.</p>
+ *
+ * @param enabled true to enable nested scrolling, false to disable
+ *
+ * @see #isNestedScrollingEnabled()
+ */
+ void setNestedScrollingEnabled(boolean enabled);
+
+ /**
+ * Returns true if nested scrolling is enabled for this view.
+ *
+ * <p>If nested scrolling is enabled and this View class implementation supports it,
+ * this view will act as a nested scrolling child view when applicable, forwarding data
+ * about the scroll operation in progress to a compatible and cooperating nested scrolling
+ * parent.</p>
+ *
+ * @return true if nested scrolling is enabled
+ *
+ * @see #setNestedScrollingEnabled(boolean)
+ */
+ boolean isNestedScrollingEnabled();
+
+ /**
+ * Begin a nestable scroll operation along the given axes.
+ *
+ * <p>A view starting a nested scroll promises to abide by the following contract:</p>
+ *
+ * <p>The view will call startNestedScroll upon initiating a scroll operation. In the case
+ * of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}.
+ * In the case of touch scrolling the nested scroll will be terminated automatically in
+ * the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}.
+ * In the event of programmatic scrolling the caller must explicitly call
+ * {@link #stopNestedScroll()} to indicate the end of the nested scroll.</p>
+ *
+ * <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
+ * If it returns false the caller may ignore the rest of this contract until the next scroll.
+ * Calling startNestedScroll while a nested scroll is already in progress will return true.</p>
+ *
+ * <p>At each incremental step of the scroll the caller should invoke
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll}
+ * once it has calculated the requested scrolling delta. If it returns true the nested scrolling
+ * parent at least partially consumed the scroll and the caller should adjust the amount it
+ * scrolls by.</p>
+ *
+ * <p>After applying the remainder of the scroll delta the caller should invoke
+ * {@link #dispatchNestedScroll(int, int, int, int, int[]) dispatchNestedScroll}, passing
+ * both the delta consumed and the delta unconsumed. A nested scrolling parent may treat
+ * these values differently. See
+ * {@link NestedScrollingParent#onNestedScroll(View, int, int, int, int)}.
+ * </p>
+ *
+ * @param axes Flags consisting of a combination of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}
+ * and/or {@link ViewCompat#SCROLL_AXIS_VERTICAL}.
+ * @return true if a cooperative parent was found and nested scrolling has been enabled for
+ * the current gesture.
+ *
+ * @see #stopNestedScroll()
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ boolean startNestedScroll(int axes);
+
+ /**
+ * Stop a nested scroll in progress.
+ *
+ * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
+ *
+ * @see #startNestedScroll(int)
+ */
+ void stopNestedScroll();
+
+ /**
+ * Returns true if this view has a nested scrolling parent.
+ *
+ * <p>The presence of a nested scrolling parent indicates that this view has initiated
+ * a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p>
+ *
+ * @return whether this view has a nested scrolling parent
+ */
+ boolean hasNestedScrollingParent();
+
+ /**
+ * Dispatch one step of a nested scroll in progress.
+ *
+ * <p>Implementations of views that support nested scrolling should call this to report
+ * info about a scroll in progress to the current nested scrolling parent. If a nested scroll
+ * is not currently in progress or nested scrolling is not
+ * {@link #isNestedScrollingEnabled() enabled} for this view this method does nothing.</p>
+ *
+ * <p>Compatible View implementations should also call
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll} before
+ * consuming a component of the scroll event themselves.</p>
+ *
+ * @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
+ * @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
+ * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the event was dispatched, false if it could not be dispatched.
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ */
+ boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
+
+ /**
+ * Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
+ *
+ * <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
+ * <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested
+ * scrolling operation to consume some or all of the scroll operation before the child view
+ * consumes it.</p>
+ *
+ * @param dx Horizontal scroll distance in pixels
+ * @param dy Vertical scroll distance in pixels
+ * @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
+ * and consumed[1] the consumed dy.
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the parent consumed some or all of the scroll delta
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
+
+ /**
+ * Dispatch a fling to a nested scrolling parent.
+ *
+ * <p>This method should be used to indicate that a nested scrolling child has detected
+ * suitable conditions for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+ *
+ * @param velocityX Horizontal fling velocity in pixels per second
+ * @param velocityY Vertical fling velocity in pixels per second
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if the nested scrolling parent consumed or otherwise reacted to the fling
+ */
+ boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
+
+ /**
+ * Dispatch a fling to a nested scrolling parent before it is processed by this view.
+ *
+ * <p>Nested pre-fling events are to nested fling events what touch intercept is to touch
+ * and what nested pre-scroll is to nested scroll. <code>dispatchNestedPreFling</code>
+ * offsets an opportunity for the parent view in a nested fling to fully consume the fling
+ * before the child view consumes it. If this method returns <code>true</code>, a nested
+ * parent view consumed the fling and this view should not scroll as a result.</p>
+ *
+ * <p>For a better user experience, only one view in a nested scrolling chain should consume
+ * the fling at a time. If a parent view consumed the fling this method will return false.
+ * Custom view implementations should account for this in two ways:</p>
+ *
+ * <ul>
+ * <li>If a custom view is paged and needs to settle to a fixed page-point, do not
+ * call <code>dispatchNestedPreFling</code>; consume the fling and settle to a valid
+ * position regardless.</li>
+ * <li>If a nested parent does consume the fling, this view should not scroll at all,
+ * even to settle back to a valid idle position.</li>
+ * </ul>
+ *
+ * <p>Views should also not offer fling velocities to nested parent views along an axis
+ * where scrolling is not currently supported; a {@link android.widget.ScrollView ScrollView}
+ * should not offer a horizontal fling velocity to its parents since scrolling along that
+ * axis is not permitted and carrying velocity along that motion does not make sense.</p>
+ *
+ * @param velocityX Horizontal fling velocity in pixels per second
+ * @param velocityY Vertical fling velocity in pixels per second
+ * @return true if a nested scrolling parent consumed the fling
+ */
+ boolean dispatchNestedPreFling(float velocityX, float velocityY);
+}
diff --git a/core/java/com/android/internal/widget/OpReorderer.java b/core/java/com/android/internal/widget/OpReorderer.java
new file mode 100644
index 0000000..babb087
--- /dev/null
+++ b/core/java/com/android/internal/widget/OpReorderer.java
@@ -0,0 +1,239 @@
+/*
+ * 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.internal.widget;
+
+import static com.android.internal.widget.AdapterHelper.UpdateOp.ADD;
+import static com.android.internal.widget.AdapterHelper.UpdateOp.MOVE;
+import static com.android.internal.widget.AdapterHelper.UpdateOp.REMOVE;
+import static com.android.internal.widget.AdapterHelper.UpdateOp.UPDATE;
+
+import com.android.internal.widget.AdapterHelper.UpdateOp;
+
+import java.util.List;
+
+class OpReorderer {
+
+ final Callback mCallback;
+
+ OpReorderer(Callback callback) {
+ mCallback = callback;
+ }
+
+ void reorderOps(List<UpdateOp> ops) {
+ // since move operations breaks continuity, their effects on ADD/RM are hard to handle.
+ // we push them to the end of the list so that they can be handled easily.
+ int badMove;
+ while ((badMove = getLastMoveOutOfOrder(ops)) != -1) {
+ swapMoveOp(ops, badMove, badMove + 1);
+ }
+ }
+
+ private void swapMoveOp(List<UpdateOp> list, int badMove, int next) {
+ final UpdateOp moveOp = list.get(badMove);
+ final UpdateOp nextOp = list.get(next);
+ switch (nextOp.cmd) {
+ case REMOVE:
+ swapMoveRemove(list, badMove, moveOp, next, nextOp);
+ break;
+ case ADD:
+ swapMoveAdd(list, badMove, moveOp, next, nextOp);
+ break;
+ case UPDATE:
+ swapMoveUpdate(list, badMove, moveOp, next, nextOp);
+ break;
+ }
+ }
+
+ void swapMoveRemove(List<UpdateOp> list, int movePos, UpdateOp moveOp,
+ int removePos, UpdateOp removeOp) {
+ UpdateOp extraRm = null;
+ // check if move is nulled out by remove
+ boolean revertedMove = false;
+ final boolean moveIsBackwards;
+
+ if (moveOp.positionStart < moveOp.itemCount) {
+ moveIsBackwards = false;
+ if (removeOp.positionStart == moveOp.positionStart
+ && removeOp.itemCount == moveOp.itemCount - moveOp.positionStart) {
+ revertedMove = true;
+ }
+ } else {
+ moveIsBackwards = true;
+ if (removeOp.positionStart == moveOp.itemCount + 1
+ && removeOp.itemCount == moveOp.positionStart - moveOp.itemCount) {
+ revertedMove = true;
+ }
+ }
+
+ // going in reverse, first revert the effect of add
+ if (moveOp.itemCount < removeOp.positionStart) {
+ removeOp.positionStart--;
+ } else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) {
+ // move is removed.
+ removeOp.itemCount--;
+ moveOp.cmd = REMOVE;
+ moveOp.itemCount = 1;
+ if (removeOp.itemCount == 0) {
+ list.remove(removePos);
+ mCallback.recycleUpdateOp(removeOp);
+ }
+ // no need to swap, it is already a remove
+ return;
+ }
+
+ // now affect of add is consumed. now apply effect of first remove
+ if (moveOp.positionStart <= removeOp.positionStart) {
+ removeOp.positionStart++;
+ } else if (moveOp.positionStart < removeOp.positionStart + removeOp.itemCount) {
+ final int remaining = removeOp.positionStart + removeOp.itemCount
+ - moveOp.positionStart;
+ extraRm = mCallback.obtainUpdateOp(REMOVE, moveOp.positionStart + 1, remaining, null);
+ removeOp.itemCount = moveOp.positionStart - removeOp.positionStart;
+ }
+
+ // if effects of move is reverted by remove, we are done.
+ if (revertedMove) {
+ list.set(movePos, removeOp);
+ list.remove(removePos);
+ mCallback.recycleUpdateOp(moveOp);
+ return;
+ }
+
+ // now find out the new locations for move actions
+ if (moveIsBackwards) {
+ if (extraRm != null) {
+ if (moveOp.positionStart > extraRm.positionStart) {
+ moveOp.positionStart -= extraRm.itemCount;
+ }
+ if (moveOp.itemCount > extraRm.positionStart) {
+ moveOp.itemCount -= extraRm.itemCount;
+ }
+ }
+ if (moveOp.positionStart > removeOp.positionStart) {
+ moveOp.positionStart -= removeOp.itemCount;
+ }
+ if (moveOp.itemCount > removeOp.positionStart) {
+ moveOp.itemCount -= removeOp.itemCount;
+ }
+ } else {
+ if (extraRm != null) {
+ if (moveOp.positionStart >= extraRm.positionStart) {
+ moveOp.positionStart -= extraRm.itemCount;
+ }
+ if (moveOp.itemCount >= extraRm.positionStart) {
+ moveOp.itemCount -= extraRm.itemCount;
+ }
+ }
+ if (moveOp.positionStart >= removeOp.positionStart) {
+ moveOp.positionStart -= removeOp.itemCount;
+ }
+ if (moveOp.itemCount >= removeOp.positionStart) {
+ moveOp.itemCount -= removeOp.itemCount;
+ }
+ }
+
+ list.set(movePos, removeOp);
+ if (moveOp.positionStart != moveOp.itemCount) {
+ list.set(removePos, moveOp);
+ } else {
+ list.remove(removePos);
+ }
+ if (extraRm != null) {
+ list.add(movePos, extraRm);
+ }
+ }
+
+ private void swapMoveAdd(List<UpdateOp> list, int move, UpdateOp moveOp, int add,
+ UpdateOp addOp) {
+ int offset = 0;
+ // going in reverse, first revert the effect of add
+ if (moveOp.itemCount < addOp.positionStart) {
+ offset--;
+ }
+ if (moveOp.positionStart < addOp.positionStart) {
+ offset++;
+ }
+ if (addOp.positionStart <= moveOp.positionStart) {
+ moveOp.positionStart += addOp.itemCount;
+ }
+ if (addOp.positionStart <= moveOp.itemCount) {
+ moveOp.itemCount += addOp.itemCount;
+ }
+ addOp.positionStart += offset;
+ list.set(move, addOp);
+ list.set(add, moveOp);
+ }
+
+ void swapMoveUpdate(List<UpdateOp> list, int move, UpdateOp moveOp, int update,
+ UpdateOp updateOp) {
+ UpdateOp extraUp1 = null;
+ UpdateOp extraUp2 = null;
+ // going in reverse, first revert the effect of add
+ if (moveOp.itemCount < updateOp.positionStart) {
+ updateOp.positionStart--;
+ } else if (moveOp.itemCount < updateOp.positionStart + updateOp.itemCount) {
+ // moved item is updated. add an update for it
+ updateOp.itemCount--;
+ extraUp1 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart, 1, updateOp.payload);
+ }
+ // now affect of add is consumed. now apply effect of first remove
+ if (moveOp.positionStart <= updateOp.positionStart) {
+ updateOp.positionStart++;
+ } else if (moveOp.positionStart < updateOp.positionStart + updateOp.itemCount) {
+ final int remaining = updateOp.positionStart + updateOp.itemCount
+ - moveOp.positionStart;
+ extraUp2 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart + 1, remaining,
+ updateOp.payload);
+ updateOp.itemCount -= remaining;
+ }
+ list.set(update, moveOp);
+ if (updateOp.itemCount > 0) {
+ list.set(move, updateOp);
+ } else {
+ list.remove(move);
+ mCallback.recycleUpdateOp(updateOp);
+ }
+ if (extraUp1 != null) {
+ list.add(move, extraUp1);
+ }
+ if (extraUp2 != null) {
+ list.add(move, extraUp2);
+ }
+ }
+
+ private int getLastMoveOutOfOrder(List<UpdateOp> list) {
+ boolean foundNonMove = false;
+ for (int i = list.size() - 1; i >= 0; i--) {
+ final UpdateOp op1 = list.get(i);
+ if (op1.cmd == MOVE) {
+ if (foundNonMove) {
+ return i;
+ }
+ } else {
+ foundNonMove = true;
+ }
+ }
+ return -1;
+ }
+
+ interface Callback {
+
+ UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount, Object payload);
+
+ void recycleUpdateOp(UpdateOp op);
+ }
+}
diff --git a/core/java/com/android/internal/widget/OrientationHelper.java b/core/java/com/android/internal/widget/OrientationHelper.java
new file mode 100644
index 0000000..1b02c88
--- /dev/null
+++ b/core/java/com/android/internal/widget/OrientationHelper.java
@@ -0,0 +1,439 @@
+/*
+ * 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.internal.widget;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * Helper class for LayoutManagers to abstract measurements depending on the View's orientation.
+ * <p>
+ * It is developed to easily support vertical and horizontal orientations in a LayoutManager but
+ * can also be used to abstract calls around view bounds and child measurements with margins and
+ * decorations.
+ *
+ * @see #createHorizontalHelper(RecyclerView.LayoutManager)
+ * @see #createVerticalHelper(RecyclerView.LayoutManager)
+ */
+public abstract class OrientationHelper {
+
+ private static final int INVALID_SIZE = Integer.MIN_VALUE;
+
+ protected final RecyclerView.LayoutManager mLayoutManager;
+
+ public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+
+ public static final int VERTICAL = LinearLayout.VERTICAL;
+
+ private int mLastTotalSpace = INVALID_SIZE;
+
+ final Rect mTmpRect = new Rect();
+
+ private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
+ mLayoutManager = layoutManager;
+ }
+
+ /**
+ * Call this method after onLayout method is complete if state is NOT pre-layout.
+ * This method records information like layout bounds that might be useful in the next layout
+ * calculations.
+ */
+ public void onLayoutComplete() {
+ mLastTotalSpace = getTotalSpace();
+ }
+
+ /**
+ * Returns the layout space change between the previous layout pass and current layout pass.
+ * <p>
+ * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's
+ * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler,
+ * RecyclerView.State)} method.
+ *
+ * @return The difference between the current total space and previous layout's total space.
+ * @see #onLayoutComplete()
+ */
+ public int getTotalSpaceChange() {
+ return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace;
+ }
+
+ /**
+ * Returns the start of the view including its decoration and margin.
+ * <p>
+ * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left
+ * decoration and 3px left margin, returned value will be 15px.
+ *
+ * @param view The view element to check
+ * @return The first pixel of the element
+ * @see #getDecoratedEnd(android.view.View)
+ */
+ public abstract int getDecoratedStart(View view);
+
+ /**
+ * Returns the end of the view including its decoration and margin.
+ * <p>
+ * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right
+ * decoration and 3px right margin, returned value will be 205.
+ *
+ * @param view The view element to check
+ * @return The last pixel of the element
+ * @see #getDecoratedStart(android.view.View)
+ */
+ public abstract int getDecoratedEnd(View view);
+
+ /**
+ * Returns the end of the View after its matrix transformations are applied to its layout
+ * position.
+ * <p>
+ * This method is useful when trying to detect the visible edge of a View.
+ * <p>
+ * It includes the decorations but does not include the margins.
+ *
+ * @param view The view whose transformed end will be returned
+ * @return The end of the View after its decor insets and transformation matrix is applied to
+ * its position
+ *
+ * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
+ */
+ public abstract int getTransformedEndWithDecoration(View view);
+
+ /**
+ * Returns the start of the View after its matrix transformations are applied to its layout
+ * position.
+ * <p>
+ * This method is useful when trying to detect the visible edge of a View.
+ * <p>
+ * It includes the decorations but does not include the margins.
+ *
+ * @param view The view whose transformed start will be returned
+ * @return The start of the View after its decor insets and transformation matrix is applied to
+ * its position
+ *
+ * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
+ */
+ public abstract int getTransformedStartWithDecoration(View view);
+
+ /**
+ * Returns the space occupied by this View in the current orientation including decorations and
+ * margins.
+ *
+ * @param view The view element to check
+ * @return Total space occupied by this view
+ * @see #getDecoratedMeasurementInOther(View)
+ */
+ public abstract int getDecoratedMeasurement(View view);
+
+ /**
+ * Returns the space occupied by this View in the perpendicular orientation including
+ * decorations and margins.
+ *
+ * @param view The view element to check
+ * @return Total space occupied by this view in the perpendicular orientation to current one
+ * @see #getDecoratedMeasurement(View)
+ */
+ public abstract int getDecoratedMeasurementInOther(View view);
+
+ /**
+ * Returns the start position of the layout after the start padding is added.
+ *
+ * @return The very first pixel we can draw.
+ */
+ public abstract int getStartAfterPadding();
+
+ /**
+ * Returns the end position of the layout after the end padding is removed.
+ *
+ * @return The end boundary for this layout.
+ */
+ public abstract int getEndAfterPadding();
+
+ /**
+ * Returns the end position of the layout without taking padding into account.
+ *
+ * @return The end boundary for this layout without considering padding.
+ */
+ public abstract int getEnd();
+
+ /**
+ * Offsets all children's positions by the given amount.
+ *
+ * @param amount Value to add to each child's layout parameters
+ */
+ public abstract void offsetChildren(int amount);
+
+ /**
+ * Returns the total space to layout. This number is the difference between
+ * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
+ *
+ * @return Total space to layout children
+ */
+ public abstract int getTotalSpace();
+
+ /**
+ * Offsets the child in this orientation.
+ *
+ * @param view View to offset
+ * @param offset offset amount
+ */
+ public abstract void offsetChild(View view, int offset);
+
+ /**
+ * Returns the padding at the end of the layout. For horizontal helper, this is the right
+ * padding and for vertical helper, this is the bottom padding. This method does not check
+ * whether the layout is RTL or not.
+ *
+ * @return The padding at the end of the layout.
+ */
+ public abstract int getEndPadding();
+
+ /**
+ * Returns the MeasureSpec mode for the current orientation from the LayoutManager.
+ *
+ * @return The current measure spec mode.
+ *
+ * @see View.MeasureSpec
+ * @see RecyclerView.LayoutManager#getWidthMode()
+ * @see RecyclerView.LayoutManager#getHeightMode()
+ */
+ public abstract int getMode();
+
+ /**
+ * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager.
+ *
+ * @return The current measure spec mode.
+ *
+ * @see View.MeasureSpec
+ * @see RecyclerView.LayoutManager#getWidthMode()
+ * @see RecyclerView.LayoutManager#getHeightMode()
+ */
+ public abstract int getModeInOther();
+
+ /**
+ * Creates an OrientationHelper for the given LayoutManager and orientation.
+ *
+ * @param layoutManager LayoutManager to attach to
+ * @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
+ * @return A new OrientationHelper
+ */
+ public static OrientationHelper createOrientationHelper(
+ RecyclerView.LayoutManager layoutManager, int orientation) {
+ switch (orientation) {
+ case HORIZONTAL:
+ return createHorizontalHelper(layoutManager);
+ case VERTICAL:
+ return createVerticalHelper(layoutManager);
+ }
+ throw new IllegalArgumentException("invalid orientation");
+ }
+
+ /**
+ * Creates a horizontal OrientationHelper for the given LayoutManager.
+ *
+ * @param layoutManager The LayoutManager to attach to.
+ * @return A new OrientationHelper
+ */
+ public static OrientationHelper createHorizontalHelper(
+ RecyclerView.LayoutManager layoutManager) {
+ return new OrientationHelper(layoutManager) {
+ @Override
+ public int getEndAfterPadding() {
+ return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public int getEnd() {
+ return mLayoutManager.getWidth();
+ }
+
+ @Override
+ public void offsetChildren(int amount) {
+ mLayoutManager.offsetChildrenHorizontal(amount);
+ }
+
+ @Override
+ public int getStartAfterPadding() {
+ return mLayoutManager.getPaddingLeft();
+ }
+
+ @Override
+ public int getDecoratedMeasurement(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ + params.rightMargin;
+ }
+
+ @Override
+ public int getDecoratedMeasurementInOther(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ + params.bottomMargin;
+ }
+
+ @Override
+ public int getDecoratedEnd(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
+ }
+
+ @Override
+ public int getDecoratedStart(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
+ }
+
+ @Override
+ public int getTransformedEndWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.right;
+ }
+
+ @Override
+ public int getTransformedStartWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.left;
+ }
+
+ @Override
+ public int getTotalSpace() {
+ return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
+ - mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public void offsetChild(View view, int offset) {
+ view.offsetLeftAndRight(offset);
+ }
+
+ @Override
+ public int getEndPadding() {
+ return mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public int getMode() {
+ return mLayoutManager.getWidthMode();
+ }
+
+ @Override
+ public int getModeInOther() {
+ return mLayoutManager.getHeightMode();
+ }
+ };
+ }
+
+ /**
+ * Creates a vertical OrientationHelper for the given LayoutManager.
+ *
+ * @param layoutManager The LayoutManager to attach to.
+ * @return A new OrientationHelper
+ */
+ public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
+ return new OrientationHelper(layoutManager) {
+ @Override
+ public int getEndAfterPadding() {
+ return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public int getEnd() {
+ return mLayoutManager.getHeight();
+ }
+
+ @Override
+ public void offsetChildren(int amount) {
+ mLayoutManager.offsetChildrenVertical(amount);
+ }
+
+ @Override
+ public int getStartAfterPadding() {
+ return mLayoutManager.getPaddingTop();
+ }
+
+ @Override
+ public int getDecoratedMeasurement(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ + params.bottomMargin;
+ }
+
+ @Override
+ public int getDecoratedMeasurementInOther(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ + params.rightMargin;
+ }
+
+ @Override
+ public int getDecoratedEnd(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
+ }
+
+ @Override
+ public int getDecoratedStart(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedTop(view) - params.topMargin;
+ }
+
+ @Override
+ public int getTransformedEndWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.bottom;
+ }
+
+ @Override
+ public int getTransformedStartWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.top;
+ }
+
+ @Override
+ public int getTotalSpace() {
+ return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
+ - mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public void offsetChild(View view, int offset) {
+ view.offsetTopAndBottom(offset);
+ }
+
+ @Override
+ public int getEndPadding() {
+ return mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public int getMode() {
+ return mLayoutManager.getHeightMode();
+ }
+
+ @Override
+ public int getModeInOther() {
+ return mLayoutManager.getWidthMode();
+ }
+ };
+ }
+}
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
new file mode 100644
index 0000000..0cf3164
--- /dev/null
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -0,0 +1,12255 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.CallSuper;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.Observable;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.AbsSavedState;
+import android.view.Display;
+import android.view.FocusFinder;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
+import android.widget.EdgeEffect;
+import android.widget.OverScroller;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A flexible view for providing a limited window into a large data set.
+ *
+ * <h3>Glossary of terms:</h3>
+ *
+ * <ul>
+ * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views
+ * that represent items in a data set.</li>
+ * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li>
+ * <li><em>Index:</em> The index of an attached child view as used in a call to
+ * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li>
+ * <li><em>Binding:</em> The process of preparing a child view to display data corresponding
+ * to a <em>position</em> within the adapter.</li>
+ * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter
+ * position may be placed in a cache for later reuse to display the same type of data again
+ * later. This can drastically improve performance by skipping initial layout inflation
+ * or construction.</li>
+ * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached
+ * state during layout. Scrap views may be reused without becoming fully detached
+ * from the parent RecyclerView, either unmodified if no rebinding is required or modified
+ * by the adapter if the view was considered <em>dirty</em>.</li>
+ * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before
+ * being displayed.</li>
+ * </ul>
+ *
+ * <h4>Positions in RecyclerView:</h4>
+ * <p>
+ * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and
+ * {@link LayoutManager} to be able to detect data set changes in batches during a layout
+ * calculation. This saves LayoutManager from tracking adapter changes to calculate animations.
+ * It also helps with performance because all view bindings happen at the same time and unnecessary
+ * bindings are avoided.
+ * <p>
+ * For this reason, there are two types of <code>position</code> related methods in RecyclerView:
+ * <ul>
+ * <li>layout position: Position of an item in the latest layout calculation. This is the
+ * position from the LayoutManager's perspective.</li>
+ * <li>adapter position: Position of an item in the adapter. This is the position from
+ * the Adapter's perspective.</li>
+ * </ul>
+ * <p>
+ * These two positions are the same except the time between dispatching <code>adapter.notify*
+ * </code> events and calculating the updated layout.
+ * <p>
+ * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest
+ * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()},
+ * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the
+ * last layout calculation. You can rely on these positions to be consistent with what user is
+ * currently seeing on the screen. For example, if you have a list of items on the screen and user
+ * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user
+ * is seeing.
+ * <p>
+ * The other set of position related methods are in the form of
+ * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()},
+ * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to
+ * work with up-to-date adapter positions even if they may not have been reflected to layout yet.
+ * For example, if you want to access the item in the adapter on a ViewHolder click, you should use
+ * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate
+ * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has
+ * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or
+ * <code>null</code> results from these methods.
+ * <p>
+ * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when
+ * writing an {@link Adapter}, you probably want to use adapter positions.
+ *
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_layoutManager
+ */
+public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
+
+ static final String TAG = "RecyclerView";
+
+ static final boolean DEBUG = false;
+
+ private static final int[] NESTED_SCROLLING_ATTRS = { android.R.attr.nestedScrollingEnabled };
+
+ private static final int[] CLIP_TO_PADDING_ATTR = {android.R.attr.clipToPadding};
+
+ /**
+ * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if
+ * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by
+ * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler
+ * recursively traverses itemView and invalidates display list for each ViewGroup that matches
+ * this criteria.
+ */
+ static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18
+ || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20;
+ /**
+ * On M+, an unspecified measure spec may include a hint which we can use. On older platforms,
+ * this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to
+ * 0 when mode is unspecified.
+ */
+ static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23;
+
+ static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16;
+
+ /**
+ * On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to
+ * RenderThread but before the next frame begins. We schedule prefetch work in this window.
+ */
+ private static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21;
+
+ /**
+ * FocusFinder#findNextFocus is broken on ICS MR1 and older for View.FOCUS_BACKWARD direction.
+ * We convert it to an absolute direction such as FOCUS_DOWN or FOCUS_LEFT.
+ */
+ private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15;
+
+ /**
+ * on API 15-, a focused child can still be considered a focused child of RV even after
+ * it's being removed or its focusable flag is set to false. This is because when this focused
+ * child is detached, the reference to this child is not removed in clearFocus. API 16 and above
+ * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus
+ * to request focus on a new child, which will clear the focus on the old (detached) child as a
+ * side-effect.
+ */
+ private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15;
+
+ static final boolean DISPATCH_TEMP_DETACH = false;
+ public static final int HORIZONTAL = 0;
+ public static final int VERTICAL = 1;
+
+ public static final int NO_POSITION = -1;
+ public static final long NO_ID = -1;
+ public static final int INVALID_TYPE = -1;
+
+ /**
+ * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
+ * that the RecyclerView should use the standard touch slop for smooth,
+ * continuous scrolling.
+ */
+ public static final int TOUCH_SLOP_DEFAULT = 0;
+
+ /**
+ * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
+ * that the RecyclerView should use the standard touch slop for scrolling
+ * widgets that snap to a page or other coarse-grained barrier.
+ */
+ public static final int TOUCH_SLOP_PAGING = 1;
+
+ static final int MAX_SCROLL_DURATION = 2000;
+
+ /**
+ * RecyclerView is calculating a scroll.
+ * If there are too many of these in Systrace, some Views inside RecyclerView might be causing
+ * it. Try to avoid using EditText, focusable views or handle them with care.
+ */
+ static final String TRACE_SCROLL_TAG = "RV Scroll";
+
+ /**
+ * OnLayout has been called by the View system.
+ * If this shows up too many times in Systrace, make sure the children of RecyclerView do not
+ * update themselves directly. This will cause a full re-layout but when it happens via the
+ * Adapter notifyItemChanged, RecyclerView can avoid full layout calculation.
+ */
+ private static final String TRACE_ON_LAYOUT_TAG = "RV OnLayout";
+
+ /**
+ * NotifyDataSetChanged or equal has been called.
+ * If this is taking a long time, try sending granular notify adapter changes instead of just
+ * calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter
+ * might help.
+ */
+ private static final String TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG = "RV FullInvalidate";
+
+ /**
+ * RecyclerView is doing a layout for partial adapter updates (we know what has changed)
+ * If this is taking a long time, you may have dispatched too many Adapter updates causing too
+ * many Views being rebind. Make sure all are necessary and also prefer using notify*Range
+ * methods.
+ */
+ private static final String TRACE_HANDLE_ADAPTER_UPDATES_TAG = "RV PartialInvalidate";
+
+ /**
+ * RecyclerView is rebinding a View.
+ * If this is taking a lot of time, consider optimizing your layout or make sure you are not
+ * doing extra operations in onBindViewHolder call.
+ */
+ static final String TRACE_BIND_VIEW_TAG = "RV OnBindView";
+
+ /**
+ * RecyclerView is attempting to pre-populate off screen views.
+ */
+ static final String TRACE_PREFETCH_TAG = "RV Prefetch";
+
+ /**
+ * RecyclerView is attempting to pre-populate off screen itemviews within an off screen
+ * RecyclerView.
+ */
+ static final String TRACE_NESTED_PREFETCH_TAG = "RV Nested Prefetch";
+
+ /**
+ * RecyclerView is creating a new View.
+ * If too many of these present in Systrace:
+ * - There might be a problem in Recycling (e.g. custom Animations that set transient state and
+ * prevent recycling or ItemAnimator not implementing the contract properly. ({@link
+ * > Adapter#onFailedToRecycleView(ViewHolder)})
+ *
+ * - There might be too many item view types.
+ * > Try merging them
+ *
+ * - There might be too many itemChange animations and not enough space in RecyclerPool.
+ * >Try increasing your pool size and item cache size.
+ */
+ static final String TRACE_CREATE_VIEW_TAG = "RV CreateView";
+ private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE =
+ new Class[]{Context.class, AttributeSet.class, int.class, int.class};
+
+ private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
+
+ final Recycler mRecycler = new Recycler();
+
+ private SavedState mPendingSavedState;
+
+ /**
+ * Handles adapter updates
+ */
+ AdapterHelper mAdapterHelper;
+
+ /**
+ * Handles abstraction between LayoutManager children and RecyclerView children
+ */
+ ChildHelper mChildHelper;
+
+ /**
+ * Keeps data about views to be used for animations
+ */
+ final ViewInfoStore mViewInfoStore = new ViewInfoStore();
+
+ /**
+ * Prior to L, there is no way to query this variable which is why we override the setter and
+ * track it here.
+ */
+ boolean mClipToPadding;
+
+ /**
+ * Note: this Runnable is only ever posted if:
+ * 1) We've been through first layout
+ * 2) We know we have a fixed size (mHasFixedSize)
+ * 3) We're attached
+ */
+ final Runnable mUpdateChildViewsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!mFirstLayoutComplete || isLayoutRequested()) {
+ // a layout request will happen, we should not do layout here.
+ return;
+ }
+ if (!mIsAttached) {
+ requestLayout();
+ // if we are not attached yet, mark us as requiring layout and skip
+ return;
+ }
+ if (mLayoutFrozen) {
+ mLayoutRequestEaten = true;
+ return; //we'll process updates when ice age ends.
+ }
+ consumePendingUpdateOperations();
+ }
+ };
+
+ final Rect mTempRect = new Rect();
+ private final Rect mTempRect2 = new Rect();
+ final RectF mTempRectF = new RectF();
+ Adapter mAdapter;
+ @VisibleForTesting LayoutManager mLayout;
+ RecyclerListener mRecyclerListener;
+ final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>();
+ private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
+ new ArrayList<>();
+ private OnItemTouchListener mActiveOnItemTouchListener;
+ boolean mIsAttached;
+ boolean mHasFixedSize;
+ @VisibleForTesting boolean mFirstLayoutComplete;
+
+ // Counting lock to control whether we should ignore requestLayout calls from children or not.
+ private int mEatRequestLayout = 0;
+
+ boolean mLayoutRequestEaten;
+ boolean mLayoutFrozen;
+ private boolean mIgnoreMotionEventTillDown;
+
+ // binary OR of change events that were eaten during a layout or scroll.
+ private int mEatenAccessibilityChangeFlags;
+ boolean mAdapterUpdateDuringMeasure;
+
+ private final AccessibilityManager mAccessibilityManager;
+ private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
+
+ /**
+ * Set to true when an adapter data set changed notification is received.
+ * In that case, we cannot run any animations since we don't know what happened until layout.
+ *
+ * Attached items are invalid until next layout, at which point layout will animate/replace
+ * items as necessary, building up content from the (effectively) new adapter from scratch.
+ *
+ * Cached items must be discarded when setting this to true, so that the cache may be freely
+ * used by prefetching until the next layout occurs.
+ *
+ * @see #setDataSetChangedAfterLayout()
+ */
+ boolean mDataSetHasChangedAfterLayout = false;
+
+ /**
+ * This variable is incremented during a dispatchLayout and/or scroll.
+ * Some methods should not be called during these periods (e.g. adapter data change).
+ * Doing so will create hard to find bugs so we better check it and throw an exception.
+ *
+ * @see #assertInLayoutOrScroll(String)
+ * @see #assertNotInLayoutOrScroll(String)
+ */
+ private int mLayoutOrScrollCounter = 0;
+
+ /**
+ * Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception
+ * (for API compatibility).
+ * <p>
+ * It is a bad practice for a developer to update the data in a scroll callback since it is
+ * potentially called during a layout.
+ */
+ private int mDispatchScrollCounter = 0;
+
+ private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
+
+ ItemAnimator mItemAnimator = new DefaultItemAnimator();
+
+ private static final int INVALID_POINTER = -1;
+
+ /**
+ * The RecyclerView is not currently scrolling.
+ * @see #getScrollState()
+ */
+ public static final int SCROLL_STATE_IDLE = 0;
+
+ /**
+ * The RecyclerView is currently being dragged by outside input such as user touch input.
+ * @see #getScrollState()
+ */
+ public static final int SCROLL_STATE_DRAGGING = 1;
+
+ /**
+ * The RecyclerView is currently animating to a final position while not under
+ * outside control.
+ * @see #getScrollState()
+ */
+ public static final int SCROLL_STATE_SETTLING = 2;
+
+ static final long FOREVER_NS = Long.MAX_VALUE;
+
+ // Touch/scrolling handling
+
+ private int mScrollState = SCROLL_STATE_IDLE;
+ private int mScrollPointerId = INVALID_POINTER;
+ private VelocityTracker mVelocityTracker;
+ private int mInitialTouchX;
+ private int mInitialTouchY;
+ private int mLastTouchX;
+ private int mLastTouchY;
+ private int mTouchSlop;
+ private OnFlingListener mOnFlingListener;
+ private final int mMinFlingVelocity;
+ private final int mMaxFlingVelocity;
+ // This value is used when handling generic motion events.
+ private float mScrollFactor = Float.MIN_VALUE;
+ private boolean mPreserveFocusAfterLayout = true;
+
+ final ViewFlinger mViewFlinger = new ViewFlinger();
+
+ GapWorker mGapWorker;
+ GapWorker.LayoutPrefetchRegistryImpl mPrefetchRegistry =
+ ALLOW_THREAD_GAP_WORK ? new GapWorker.LayoutPrefetchRegistryImpl() : null;
+
+ final State mState = new State();
+
+ private OnScrollListener mScrollListener;
+ private List<OnScrollListener> mScrollListeners;
+
+ // For use in item animations
+ boolean mItemsAddedOrRemoved = false;
+ boolean mItemsChanged = false;
+ private ItemAnimator.ItemAnimatorListener mItemAnimatorListener =
+ new ItemAnimatorRestoreListener();
+ boolean mPostedAnimatorRunner = false;
+ RecyclerViewAccessibilityDelegate mAccessibilityDelegate;
+ private ChildDrawingOrderCallback mChildDrawingOrderCallback;
+
+ // simple array to keep min and max child position during a layout calculation
+ // preserved not to create a new one in each layout pass
+ private final int[] mMinMaxLayoutPositions = new int[2];
+
+ private final int[] mScrollOffset = new int[2];
+ private final int[] mScrollConsumed = new int[2];
+ private final int[] mNestedOffsets = new int[2];
+
+ /**
+ * These are views that had their a11y importance changed during a layout. We defer these events
+ * until the end of the layout because a11y service may make sync calls back to the RV while
+ * the View's state is undefined.
+ */
+ @VisibleForTesting
+ final List<ViewHolder> mPendingAccessibilityImportanceChange = new ArrayList();
+
+ private Runnable mItemAnimatorRunner = new Runnable() {
+ @Override
+ public void run() {
+ if (mItemAnimator != null) {
+ mItemAnimator.runPendingAnimations();
+ }
+ mPostedAnimatorRunner = false;
+ }
+ };
+
+ static final Interpolator sQuinticInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
+ }
+ };
+
+ /**
+ * The callback to convert view info diffs into animations.
+ */
+ private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
+ new ViewInfoStore.ProcessCallback() {
+ @Override
+ public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
+ @Nullable ItemHolderInfo postInfo) {
+ mRecycler.unscrapView(viewHolder);
+ animateDisappearance(viewHolder, info, postInfo);
+ }
+ @Override
+ public void processAppeared(ViewHolder viewHolder,
+ ItemHolderInfo preInfo, ItemHolderInfo info) {
+ animateAppearance(viewHolder, preInfo, info);
+ }
+
+ @Override
+ public void processPersistent(ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
+ viewHolder.setIsRecyclable(false);
+ if (mDataSetHasChangedAfterLayout) {
+ // since it was rebound, use change instead as we'll be mapping them from
+ // stable ids. If stable ids were false, we would not be running any
+ // animations
+ if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, postInfo)) {
+ postAnimationRunner();
+ }
+ } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
+ postAnimationRunner();
+ }
+ }
+ @Override
+ public void unused(ViewHolder viewHolder) {
+ mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
+ }
+ };
+
+ public RecyclerView(Context context) {
+ this(context, null);
+ }
+
+ public RecyclerView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
+ mClipToPadding = a.getBoolean(0, true);
+ a.recycle();
+ } else {
+ mClipToPadding = true;
+ }
+ setScrollContainer(true);
+ setFocusableInTouchMode(true);
+
+ final ViewConfiguration vc = ViewConfiguration.get(context);
+ mTouchSlop = vc.getScaledTouchSlop();
+ mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
+ mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
+ setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
+
+ mItemAnimator.setListener(mItemAnimatorListener);
+ initAdapterManager();
+ initChildrenHelper();
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ mAccessibilityManager = (AccessibilityManager) getContext()
+ .getSystemService(Context.ACCESSIBILITY_SERVICE);
+ setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
+ // Create the layoutManager if specified.
+
+ boolean nestedScrollingEnabled = true;
+
+ if (attrs != null) {
+ int defStyleRes = 0;
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
+ defStyle, defStyleRes);
+ String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);
+ int descendantFocusability = a.getInt(
+ R.styleable.RecyclerView_descendantFocusability, -1);
+ if (descendantFocusability == -1) {
+ setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ }
+ a.recycle();
+ createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
+
+ if (Build.VERSION.SDK_INT >= 21) {
+ a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,
+ defStyle, defStyleRes);
+ nestedScrollingEnabled = a.getBoolean(0, true);
+ a.recycle();
+ }
+ } else {
+ setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ }
+
+ // Re-set whether nested scrolling is enabled so that it is set on all API levels
+ setNestedScrollingEnabled(nestedScrollingEnabled);
+ }
+
+ /**
+ * Returns the accessibility delegate compatibility implementation used by the RecyclerView.
+ * @return An instance of AccessibilityDelegateCompat used by RecyclerView
+ */
+ public RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() {
+ return mAccessibilityDelegate;
+ }
+
+ /**
+ * Sets the accessibility delegate compatibility implementation used by RecyclerView.
+ * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView.
+ */
+ public void setAccessibilityDelegateCompat(
+ RecyclerViewAccessibilityDelegate accessibilityDelegate) {
+ mAccessibilityDelegate = accessibilityDelegate;
+ setAccessibilityDelegate(mAccessibilityDelegate);
+ }
+
+ /**
+ * Instantiate and set a LayoutManager, if specified in the attributes.
+ */
+ private void createLayoutManager(Context context, String className, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ if (className != null) {
+ className = className.trim();
+ if (className.length() != 0) { // Can't use isEmpty since it was added in API 9.
+ className = getFullClassName(context, className);
+ try {
+ ClassLoader classLoader;
+ if (isInEditMode()) {
+ // Stupid layoutlib cannot handle simple class loaders.
+ classLoader = this.getClass().getClassLoader();
+ } else {
+ classLoader = context.getClassLoader();
+ }
+ Class<? extends LayoutManager> layoutManagerClass =
+ classLoader.loadClass(className).asSubclass(LayoutManager.class);
+ Constructor<? extends LayoutManager> constructor;
+ Object[] constructorArgs = null;
+ try {
+ constructor = layoutManagerClass
+ .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
+ constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
+ } catch (NoSuchMethodException e) {
+ try {
+ constructor = layoutManagerClass.getConstructor();
+ } catch (NoSuchMethodException e1) {
+ e1.initCause(e);
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Error creating LayoutManager " + className, e1);
+ }
+ }
+ constructor.setAccessible(true);
+ setLayoutManager(constructor.newInstance(constructorArgs));
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Unable to find LayoutManager " + className, e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Could not instantiate the LayoutManager: " + className, e);
+ } catch (InstantiationException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Could not instantiate the LayoutManager: " + className, e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Cannot access non-public constructor " + className, e);
+ } catch (ClassCastException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Class is not a LayoutManager " + className, e);
+ }
+ }
+ }
+ }
+
+ private String getFullClassName(Context context, String className) {
+ if (className.charAt(0) == '.') {
+ return context.getPackageName() + className;
+ }
+ if (className.contains(".")) {
+ return className;
+ }
+ return RecyclerView.class.getPackage().getName() + '.' + className;
+ }
+
+ private void initChildrenHelper() {
+ mChildHelper = new ChildHelper(new ChildHelper.Callback() {
+ @Override
+ public int getChildCount() {
+ return RecyclerView.this.getChildCount();
+ }
+
+ @Override
+ public void addView(View child, int index) {
+ RecyclerView.this.addView(child, index);
+ dispatchChildAttached(child);
+ }
+
+ @Override
+ public int indexOfChild(View view) {
+ return RecyclerView.this.indexOfChild(view);
+ }
+
+ @Override
+ public void removeViewAt(int index) {
+ final View child = RecyclerView.this.getChildAt(index);
+ if (child != null) {
+ dispatchChildDetached(child);
+ }
+ RecyclerView.this.removeViewAt(index);
+ }
+
+ @Override
+ public View getChildAt(int offset) {
+ return RecyclerView.this.getChildAt(offset);
+ }
+
+ @Override
+ public void removeAllViews() {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ dispatchChildDetached(getChildAt(i));
+ }
+ RecyclerView.this.removeAllViews();
+ }
+
+ @Override
+ public ViewHolder getChildViewHolder(View view) {
+ return getChildViewHolderInt(view);
+ }
+
+ @Override
+ public void attachViewToParent(View child, int index,
+ ViewGroup.LayoutParams layoutParams) {
+ final ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ if (!vh.isTmpDetached() && !vh.shouldIgnore()) {
+ throw new IllegalArgumentException("Called attach on a child which is not"
+ + " detached: " + vh);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "reAttach " + vh);
+ }
+ vh.clearTmpDetachFlag();
+ }
+ RecyclerView.this.attachViewToParent(child, index, layoutParams);
+ }
+
+ @Override
+ public void detachViewFromParent(int offset) {
+ final View view = getChildAt(offset);
+ if (view != null) {
+ final ViewHolder vh = getChildViewHolderInt(view);
+ if (vh != null) {
+ if (vh.isTmpDetached() && !vh.shouldIgnore()) {
+ throw new IllegalArgumentException("called detach on an already"
+ + " detached child " + vh);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "tmpDetach " + vh);
+ }
+ vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
+ }
+ }
+ RecyclerView.this.detachViewFromParent(offset);
+ }
+
+ @Override
+ public void onEnteredHiddenState(View child) {
+ final ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ vh.onEnteredHiddenState(RecyclerView.this);
+ }
+ }
+
+ @Override
+ public void onLeftHiddenState(View child) {
+ final ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ vh.onLeftHiddenState(RecyclerView.this);
+ }
+ }
+ });
+ }
+
+ void initAdapterManager() {
+ mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
+ @Override
+ public ViewHolder findViewHolder(int position) {
+ final ViewHolder vh = findViewHolderForPosition(position, true);
+ if (vh == null) {
+ return null;
+ }
+ // ensure it is not hidden because for adapter helper, the only thing matter is that
+ // LM thinks view is a child.
+ if (mChildHelper.isHidden(vh.itemView)) {
+ if (DEBUG) {
+ Log.d(TAG, "assuming view holder cannot be find because it is hidden");
+ }
+ return null;
+ }
+ return vh;
+ }
+
+ @Override
+ public void offsetPositionsForRemovingInvisible(int start, int count) {
+ offsetPositionRecordsForRemove(start, count, true);
+ mItemsAddedOrRemoved = true;
+ mState.mDeletedInvisibleItemCountSincePreviousLayout += count;
+ }
+
+ @Override
+ public void offsetPositionsForRemovingLaidOutOrNewView(
+ int positionStart, int itemCount) {
+ offsetPositionRecordsForRemove(positionStart, itemCount, false);
+ mItemsAddedOrRemoved = true;
+ }
+
+ @Override
+ public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
+ viewRangeUpdate(positionStart, itemCount, payload);
+ mItemsChanged = true;
+ }
+
+ @Override
+ public void onDispatchFirstPass(AdapterHelper.UpdateOp op) {
+ dispatchUpdate(op);
+ }
+
+ void dispatchUpdate(AdapterHelper.UpdateOp op) {
+ switch (op.cmd) {
+ case AdapterHelper.UpdateOp.ADD:
+ mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
+ break;
+ case AdapterHelper.UpdateOp.REMOVE:
+ mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
+ break;
+ case AdapterHelper.UpdateOp.UPDATE:
+ mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
+ op.payload);
+ break;
+ case AdapterHelper.UpdateOp.MOVE:
+ mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
+ break;
+ }
+ }
+
+ @Override
+ public void onDispatchSecondPass(AdapterHelper.UpdateOp op) {
+ dispatchUpdate(op);
+ }
+
+ @Override
+ public void offsetPositionsForAdd(int positionStart, int itemCount) {
+ offsetPositionRecordsForInsert(positionStart, itemCount);
+ mItemsAddedOrRemoved = true;
+ }
+
+ @Override
+ public void offsetPositionsForMove(int from, int to) {
+ offsetPositionRecordsForMove(from, to);
+ // should we create mItemsMoved ?
+ mItemsAddedOrRemoved = true;
+ }
+ });
+ }
+
+ /**
+ * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's
+ * size is not affected by the adapter contents. RecyclerView can still change its size based
+ * on other factors (e.g. its parent's size) but this size calculation cannot depend on the
+ * size of its children or contents of its adapter (except the number of items in the adapter).
+ * <p>
+ * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow
+ * RecyclerView to avoid invalidating the whole layout when its adapter contents change.
+ *
+ * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
+ */
+ public void setHasFixedSize(boolean hasFixedSize) {
+ mHasFixedSize = hasFixedSize;
+ }
+
+ /**
+ * @return true if the app has specified that changes in adapter content cannot change
+ * the size of the RecyclerView itself.
+ */
+ public boolean hasFixedSize() {
+ return mHasFixedSize;
+ }
+
+ @Override
+ public void setClipToPadding(boolean clipToPadding) {
+ if (clipToPadding != mClipToPadding) {
+ invalidateGlows();
+ }
+ mClipToPadding = clipToPadding;
+ super.setClipToPadding(clipToPadding);
+ if (mFirstLayoutComplete) {
+ requestLayout();
+ }
+ }
+
+ /**
+ * Returns whether this RecyclerView will clip its children to its padding, and resize (but
+ * not clip) any EdgeEffect to the padded region, if padding is present.
+ * <p>
+ * By default, children are clipped to the padding of their parent
+ * RecyclerView. This clipping behavior is only enabled if padding is non-zero.
+ *
+ * @return true if this RecyclerView clips children to its padding and resizes (but doesn't
+ * clip) any EdgeEffect to the padded region, false otherwise.
+ *
+ * @attr name android:clipToPadding
+ */
+ @Override
+ public boolean getClipToPadding() {
+ return mClipToPadding;
+ }
+
+ /**
+ * Configure the scrolling touch slop for a specific use case.
+ *
+ * Set up the RecyclerView's scrolling motion threshold based on common usages.
+ * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}.
+ *
+ * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing
+ * the intended usage of this RecyclerView
+ */
+ public void setScrollingTouchSlop(int slopConstant) {
+ final ViewConfiguration vc = ViewConfiguration.get(getContext());
+ switch (slopConstant) {
+ default:
+ Log.w(TAG, "setScrollingTouchSlop(): bad argument constant "
+ + slopConstant + "; using default value");
+ // fall-through
+ case TOUCH_SLOP_DEFAULT:
+ mTouchSlop = vc.getScaledTouchSlop();
+ break;
+
+ case TOUCH_SLOP_PAGING:
+ mTouchSlop = vc.getScaledPagingTouchSlop();
+ break;
+ }
+ }
+
+ /**
+ * Swaps the current adapter with the provided one. It is similar to
+ * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same
+ * {@link ViewHolder} and does not clear the RecycledViewPool.
+ * <p>
+ * Note that it still calls onAdapterChanged callbacks.
+ *
+ * @param adapter The new adapter to set, or null to set no adapter.
+ * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing
+ * Views. If adapters have stable ids and/or you want to
+ * animate the disappearing views, you may prefer to set
+ * this to false.
+ * @see #setAdapter(Adapter)
+ */
+ public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
+ // bail out if layout is frozen
+ setLayoutFrozen(false);
+ setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
+ setDataSetChangedAfterLayout();
+ requestLayout();
+ }
+ /**
+ * Set a new adapter to provide child views on demand.
+ * <p>
+ * When adapter is changed, all existing views are recycled back to the pool. If the pool has
+ * only one adapter, it will be cleared.
+ *
+ * @param adapter The new adapter to set, or null to set no adapter.
+ * @see #swapAdapter(Adapter, boolean)
+ */
+ public void setAdapter(Adapter adapter) {
+ // bail out if layout is frozen
+ setLayoutFrozen(false);
+ setAdapterInternal(adapter, false, true);
+ requestLayout();
+ }
+
+ /**
+ * Removes and recycles all views - both those currently attached, and those in the Recycler.
+ */
+ void removeAndRecycleViews() {
+ // end all running animations
+ if (mItemAnimator != null) {
+ mItemAnimator.endAnimations();
+ }
+ // Since animations are ended, mLayout.children should be equal to
+ // recyclerView.children. This may not be true if item animator's end does not work as
+ // expected. (e.g. not release children instantly). It is safer to use mLayout's child
+ // count.
+ if (mLayout != null) {
+ mLayout.removeAndRecycleAllViews(mRecycler);
+ mLayout.removeAndRecycleScrapInt(mRecycler);
+ }
+ // we should clear it here before adapters are swapped to ensure correct callbacks.
+ mRecycler.clear();
+ }
+
+ /**
+ * Replaces the current adapter with the new one and triggers listeners.
+ * @param adapter The new adapter
+ * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
+ * item types with the current adapter (helps us avoid cache
+ * invalidation).
+ * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If
+ * compatibleWithPrevious is false, this parameter is ignored.
+ */
+ private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
+ boolean removeAndRecycleViews) {
+ if (mAdapter != null) {
+ mAdapter.unregisterAdapterDataObserver(mObserver);
+ mAdapter.onDetachedFromRecyclerView(this);
+ }
+ if (!compatibleWithPrevious || removeAndRecycleViews) {
+ removeAndRecycleViews();
+ }
+ mAdapterHelper.reset();
+ final Adapter oldAdapter = mAdapter;
+ mAdapter = adapter;
+ if (adapter != null) {
+ adapter.registerAdapterDataObserver(mObserver);
+ adapter.onAttachedToRecyclerView(this);
+ }
+ if (mLayout != null) {
+ mLayout.onAdapterChanged(oldAdapter, mAdapter);
+ }
+ mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
+ mState.mStructureChanged = true;
+ markKnownViewsInvalid();
+ }
+
+ /**
+ * Retrieves the previously set adapter or null if no adapter is set.
+ *
+ * @return The previously set adapter
+ * @see #setAdapter(Adapter)
+ */
+ public Adapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Register a listener that will be notified whenever a child view is recycled.
+ *
+ * <p>This listener will be called when a LayoutManager or the RecyclerView decides
+ * that a child view is no longer needed. If an application associates expensive
+ * or heavyweight data with item views, this may be a good place to release
+ * or free those resources.</p>
+ *
+ * @param listener Listener to register, or null to clear
+ */
+ public void setRecyclerListener(RecyclerListener listener) {
+ mRecyclerListener = listener;
+ }
+
+ /**
+ * <p>Return the offset of the RecyclerView's text baseline from the its top
+ * boundary. If the LayoutManager of this RecyclerView does not support baseline alignment,
+ * this method returns -1.</p>
+ *
+ * @return the offset of the baseline within the RecyclerView's bounds or -1
+ * if baseline alignment is not supported
+ */
+ @Override
+ public int getBaseline() {
+ if (mLayout != null) {
+ return mLayout.getBaseline();
+ } else {
+ return super.getBaseline();
+ }
+ }
+
+ /**
+ * Register a listener that will be notified whenever a child view is attached to or detached
+ * from RecyclerView.
+ *
+ * <p>This listener will be called when a LayoutManager or the RecyclerView decides
+ * that a child view is no longer needed. If an application associates expensive
+ * or heavyweight data with item views, this may be a good place to release
+ * or free those resources.</p>
+ *
+ * @param listener Listener to register
+ */
+ public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
+ if (mOnChildAttachStateListeners == null) {
+ mOnChildAttachStateListeners = new ArrayList<>();
+ }
+ mOnChildAttachStateListeners.add(listener);
+ }
+
+ /**
+ * Removes the provided listener from child attached state listeners list.
+ *
+ * @param listener Listener to unregister
+ */
+ public void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
+ if (mOnChildAttachStateListeners == null) {
+ return;
+ }
+ mOnChildAttachStateListeners.remove(listener);
+ }
+
+ /**
+ * Removes all listeners that were added via
+ * {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}.
+ */
+ public void clearOnChildAttachStateChangeListeners() {
+ if (mOnChildAttachStateListeners != null) {
+ mOnChildAttachStateListeners.clear();
+ }
+ }
+
+ /**
+ * Set the {@link LayoutManager} that this RecyclerView will use.
+ *
+ * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
+ * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom
+ * layout arrangements for child views. These arrangements are controlled by the
+ * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p>
+ *
+ * <p>Several default strategies are provided for common uses such as lists and grids.</p>
+ *
+ * @param layout LayoutManager to use
+ */
+ public void setLayoutManager(LayoutManager layout) {
+ if (layout == mLayout) {
+ return;
+ }
+ stopScroll();
+ // TODO We should do this switch a dispatchLayout pass and animate children. There is a good
+ // chance that LayoutManagers will re-use views.
+ if (mLayout != null) {
+ // end all running animations
+ if (mItemAnimator != null) {
+ mItemAnimator.endAnimations();
+ }
+ mLayout.removeAndRecycleAllViews(mRecycler);
+ mLayout.removeAndRecycleScrapInt(mRecycler);
+ mRecycler.clear();
+
+ if (mIsAttached) {
+ mLayout.dispatchDetachedFromWindow(this, mRecycler);
+ }
+ mLayout.setRecyclerView(null);
+ mLayout = null;
+ } else {
+ mRecycler.clear();
+ }
+ // this is just a defensive measure for faulty item animators.
+ mChildHelper.removeAllViewsUnfiltered();
+ mLayout = layout;
+ if (layout != null) {
+ if (layout.mRecyclerView != null) {
+ throw new IllegalArgumentException("LayoutManager " + layout
+ + " is already attached to a RecyclerView: " + layout.mRecyclerView);
+ }
+ mLayout.setRecyclerView(this);
+ if (mIsAttached) {
+ mLayout.dispatchAttachedToWindow(this);
+ }
+ }
+ mRecycler.updateViewCacheSize();
+ requestLayout();
+ }
+
+ /**
+ * Set a {@link OnFlingListener} for this {@link RecyclerView}.
+ * <p>
+ * If the {@link OnFlingListener} is set then it will receive
+ * calls to {@link #fling(int,int)} and will be able to intercept them.
+ *
+ * @param onFlingListener The {@link OnFlingListener} instance.
+ */
+ public void setOnFlingListener(@Nullable OnFlingListener onFlingListener) {
+ mOnFlingListener = onFlingListener;
+ }
+
+ /**
+ * Get the current {@link OnFlingListener} from this {@link RecyclerView}.
+ *
+ * @return The {@link OnFlingListener} instance currently set (can be null).
+ */
+ @Nullable
+ public OnFlingListener getOnFlingListener() {
+ return mOnFlingListener;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ SavedState state = new SavedState(super.onSaveInstanceState());
+ if (mPendingSavedState != null) {
+ state.copyFrom(mPendingSavedState);
+ } else if (mLayout != null) {
+ state.mLayoutState = mLayout.onSaveInstanceState();
+ } else {
+ state.mLayoutState = null;
+ }
+
+ return state;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ mPendingSavedState = (SavedState) state;
+ super.onRestoreInstanceState(mPendingSavedState.getSuperState());
+ if (mLayout != null && mPendingSavedState.mLayoutState != null) {
+ mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
+ }
+ }
+
+ /**
+ * Override to prevent freezing of any views created by the adapter.
+ */
+ @Override
+ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+ dispatchFreezeSelfOnly(container);
+ }
+
+ /**
+ * Override to prevent thawing of any views created by the adapter.
+ */
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ dispatchThawSelfOnly(container);
+ }
+
+ /**
+ * Adds a view to the animatingViews list.
+ * mAnimatingViews holds the child views that are currently being kept around
+ * purely for the purpose of being animated out of view. They are drawn as a regular
+ * part of the child list of the RecyclerView, but they are invisible to the LayoutManager
+ * as they are managed separately from the regular child views.
+ * @param viewHolder The ViewHolder to be removed
+ */
+ private void addAnimatingView(ViewHolder viewHolder) {
+ final View view = viewHolder.itemView;
+ final boolean alreadyParented = view.getParent() == this;
+ mRecycler.unscrapView(getChildViewHolder(view));
+ if (viewHolder.isTmpDetached()) {
+ // re-attach
+ mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
+ } else if (!alreadyParented) {
+ mChildHelper.addView(view, true);
+ } else {
+ mChildHelper.hide(view);
+ }
+ }
+
+ /**
+ * Removes a view from the animatingViews list.
+ * @param view The view to be removed
+ * @see #addAnimatingView(RecyclerView.ViewHolder)
+ * @return true if an animating view is removed
+ */
+ boolean removeAnimatingView(View view) {
+ eatRequestLayout();
+ final boolean removed = mChildHelper.removeViewIfHidden(view);
+ if (removed) {
+ final ViewHolder viewHolder = getChildViewHolderInt(view);
+ mRecycler.unscrapView(viewHolder);
+ mRecycler.recycleViewHolderInternal(viewHolder);
+ if (DEBUG) {
+ Log.d(TAG, "after removing animated view: " + view + ", " + this);
+ }
+ }
+ // only clear request eaten flag if we removed the view.
+ resumeRequestLayout(!removed);
+ return removed;
+ }
+
+ /**
+ * Return the {@link LayoutManager} currently responsible for
+ * layout policy for this RecyclerView.
+ *
+ * @return The currently bound LayoutManager
+ */
+ public LayoutManager getLayoutManager() {
+ return mLayout;
+ }
+
+ /**
+ * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
+ * if no pool is set for this view a new one will be created. See
+ * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
+ *
+ * @return The pool used to store recycled item views for reuse.
+ * @see #setRecycledViewPool(RecycledViewPool)
+ */
+ public RecycledViewPool getRecycledViewPool() {
+ return mRecycler.getRecycledViewPool();
+ }
+
+ /**
+ * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
+ * This can be useful if you have multiple RecyclerViews with adapters that use the same
+ * view types, for example if you have several data sets with the same kinds of item views
+ * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
+ *
+ * @param pool Pool to set. If this parameter is null a new pool will be created and used.
+ */
+ public void setRecycledViewPool(RecycledViewPool pool) {
+ mRecycler.setRecycledViewPool(pool);
+ }
+
+ /**
+ * Sets a new {@link ViewCacheExtension} to be used by the Recycler.
+ *
+ * @param extension ViewCacheExtension to be used or null if you want to clear the existing one.
+ *
+ * @see {@link ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)}
+ */
+ public void setViewCacheExtension(ViewCacheExtension extension) {
+ mRecycler.setViewCacheExtension(extension);
+ }
+
+ /**
+ * Set the number of offscreen views to retain before adding them to the potentially shared
+ * {@link #getRecycledViewPool() recycled view pool}.
+ *
+ * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing
+ * a LayoutManager to reuse those views unmodified without needing to return to the adapter
+ * to rebind them.</p>
+ *
+ * @param size Number of views to cache offscreen before returning them to the general
+ * recycled view pool
+ */
+ public void setItemViewCacheSize(int size) {
+ mRecycler.setViewCacheSize(size);
+ }
+
+ /**
+ * Return the current scrolling state of the RecyclerView.
+ *
+ * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or
+ * {@link #SCROLL_STATE_SETTLING}
+ */
+ public int getScrollState() {
+ return mScrollState;
+ }
+
+ void setScrollState(int state) {
+ if (state == mScrollState) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState,
+ new Exception());
+ }
+ mScrollState = state;
+ if (state != SCROLL_STATE_SETTLING) {
+ stopScrollersInternal();
+ }
+ dispatchOnScrollStateChanged(state);
+ }
+
+ /**
+ * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
+ * affect both measurement and drawing of individual item views.
+ *
+ * <p>Item decorations are ordered. Decorations placed earlier in the list will
+ * be run/queried/drawn first for their effects on item views. Padding added to views
+ * will be nested; a padding added by an earlier decoration will mean further
+ * item decorations in the list will be asked to draw/pad within the previous decoration's
+ * given area.</p>
+ *
+ * @param decor Decoration to add
+ * @param index Position in the decoration chain to insert this decoration at. If this value
+ * is negative the decoration will be added at the end.
+ */
+ public void addItemDecoration(ItemDecoration decor, int index) {
+ if (mLayout != null) {
+ mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
+ + " layout");
+ }
+ if (mItemDecorations.isEmpty()) {
+ setWillNotDraw(false);
+ }
+ if (index < 0) {
+ mItemDecorations.add(decor);
+ } else {
+ mItemDecorations.add(index, decor);
+ }
+ markItemDecorInsetsDirty();
+ requestLayout();
+ }
+
+ /**
+ * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
+ * affect both measurement and drawing of individual item views.
+ *
+ * <p>Item decorations are ordered. Decorations placed earlier in the list will
+ * be run/queried/drawn first for their effects on item views. Padding added to views
+ * will be nested; a padding added by an earlier decoration will mean further
+ * item decorations in the list will be asked to draw/pad within the previous decoration's
+ * given area.</p>
+ *
+ * @param decor Decoration to add
+ */
+ public void addItemDecoration(ItemDecoration decor) {
+ addItemDecoration(decor, -1);
+ }
+
+ /**
+ * Remove an {@link ItemDecoration} from this RecyclerView.
+ *
+ * <p>The given decoration will no longer impact the measurement and drawing of
+ * item views.</p>
+ *
+ * @param decor Decoration to remove
+ * @see #addItemDecoration(ItemDecoration)
+ */
+ public void removeItemDecoration(ItemDecoration decor) {
+ if (mLayout != null) {
+ mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or"
+ + " layout");
+ }
+ mItemDecorations.remove(decor);
+ if (mItemDecorations.isEmpty()) {
+ setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
+ }
+ markItemDecorInsetsDirty();
+ requestLayout();
+ }
+
+ /**
+ * Sets the {@link ChildDrawingOrderCallback} to be used for drawing children.
+ * <p>
+ * See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will
+ * always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be
+ * true if childDrawingOrderCallback is not null, false otherwise.
+ * <p>
+ * Note that child drawing order may be overridden by View's elevation.
+ *
+ * @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing
+ * system.
+ */
+ public void setChildDrawingOrderCallback(ChildDrawingOrderCallback childDrawingOrderCallback) {
+ if (childDrawingOrderCallback == mChildDrawingOrderCallback) {
+ return;
+ }
+ mChildDrawingOrderCallback = childDrawingOrderCallback;
+ setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null);
+ }
+
+ /**
+ * Set a listener that will be notified of any changes in scroll state or position.
+ *
+ * @param listener Listener to set or null to clear
+ *
+ * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and
+ * {@link #removeOnScrollListener(OnScrollListener)}
+ */
+ @Deprecated
+ public void setOnScrollListener(OnScrollListener listener) {
+ mScrollListener = listener;
+ }
+
+ /**
+ * Add a listener that will be notified of any changes in scroll state or position.
+ *
+ * <p>Components that add a listener should take care to remove it when finished.
+ * Other components that take ownership of a view may call {@link #clearOnScrollListeners()}
+ * to remove all attached listeners.</p>
+ *
+ * @param listener listener to set or null to clear
+ */
+ public void addOnScrollListener(OnScrollListener listener) {
+ if (mScrollListeners == null) {
+ mScrollListeners = new ArrayList<>();
+ }
+ mScrollListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener that was notified of any changes in scroll state or position.
+ *
+ * @param listener listener to set or null to clear
+ */
+ public void removeOnScrollListener(OnScrollListener listener) {
+ if (mScrollListeners != null) {
+ mScrollListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Remove all secondary listener that were notified of any changes in scroll state or position.
+ */
+ public void clearOnScrollListeners() {
+ if (mScrollListeners != null) {
+ mScrollListeners.clear();
+ }
+ }
+
+ /**
+ * Convenience method to scroll to a certain position.
+ *
+ * RecyclerView does not implement scrolling logic, rather forwards the call to
+ * {@link com.android.internal.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
+ * @param position Scroll to this adapter position
+ * @see com.android.internal.widget.RecyclerView.LayoutManager#scrollToPosition(int)
+ */
+ public void scrollToPosition(int position) {
+ if (mLayoutFrozen) {
+ return;
+ }
+ stopScroll();
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot scroll to position a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return;
+ }
+ mLayout.scrollToPosition(position);
+ awakenScrollBars();
+ }
+
+ void jumpToPositionForSmoothScroller(int position) {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.scrollToPosition(position);
+ awakenScrollBars();
+ }
+
+ /**
+ * Starts a smooth scroll to an adapter position.
+ * <p>
+ * To support smooth scrolling, you must override
+ * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
+ * {@link SmoothScroller}.
+ * <p>
+ * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
+ * provide a custom smooth scroll logic, override
+ * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
+ * LayoutManager.
+ *
+ * @param position The adapter position to scroll to
+ * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
+ */
+ public void smoothScrollToPosition(int position) {
+ if (mLayoutFrozen) {
+ return;
+ }
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return;
+ }
+ mLayout.smoothScrollToPosition(this, mState, position);
+ }
+
+ @Override
+ public void scrollTo(int x, int y) {
+ Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. "
+ + "Use scrollToPosition instead");
+ }
+
+ @Override
+ public void scrollBy(int x, int y) {
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot scroll without a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return;
+ }
+ if (mLayoutFrozen) {
+ return;
+ }
+ final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
+ final boolean canScrollVertical = mLayout.canScrollVertically();
+ if (canScrollHorizontal || canScrollVertical) {
+ scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null);
+ }
+ }
+
+ /**
+ * Helper method reflect data changes to the state.
+ * <p>
+ * Adapter changes during a scroll may trigger a crash because scroll assumes no data change
+ * but data actually changed.
+ * <p>
+ * This method consumes all deferred changes to avoid that case.
+ */
+ void consumePendingUpdateOperations() {
+ if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
+ Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
+ dispatchLayout();
+ Trace.endSection();
+ return;
+ }
+ if (!mAdapterHelper.hasPendingUpdates()) {
+ return;
+ }
+
+ // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
+ // of the visible items is affected and if not, just ignore the change.
+ if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
+ .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
+ | AdapterHelper.UpdateOp.MOVE)) {
+ Trace.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ mAdapterHelper.preProcess();
+ if (!mLayoutRequestEaten) {
+ if (hasUpdatedView()) {
+ dispatchLayout();
+ } else {
+ // no need to layout, clean state
+ mAdapterHelper.consumePostponedUpdates();
+ }
+ }
+ resumeRequestLayout(true);
+ onExitLayoutOrScroll();
+ Trace.endSection();
+ } else if (mAdapterHelper.hasPendingUpdates()) {
+ Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
+ dispatchLayout();
+ Trace.endSection();
+ }
+ }
+
+ /**
+ * @return True if an existing view holder needs to be updated
+ */
+ private boolean hasUpdatedView() {
+ final int childCount = mChildHelper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder == null || holder.shouldIgnore()) {
+ continue;
+ }
+ if (holder.isUpdated()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Does not perform bounds checking. Used by internal methods that have already validated input.
+ * <p>
+ * It also reports any unused scroll request to the related EdgeEffect.
+ *
+ * @param x The amount of horizontal scroll request
+ * @param y The amount of vertical scroll request
+ * @param ev The originating MotionEvent, or null if not from a touch event.
+ *
+ * @return Whether any scroll was consumed in either direction.
+ */
+ boolean scrollByInternal(int x, int y, MotionEvent ev) {
+ int unconsumedX = 0, unconsumedY = 0;
+ int consumedX = 0, consumedY = 0;
+
+ consumePendingUpdateOperations();
+ if (mAdapter != null) {
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ Trace.beginSection(TRACE_SCROLL_TAG);
+ if (x != 0) {
+ consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
+ unconsumedX = x - consumedX;
+ }
+ if (y != 0) {
+ consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
+ unconsumedY = y - consumedY;
+ }
+ Trace.endSection();
+ repositionShadowingViews();
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ }
+ if (!mItemDecorations.isEmpty()) {
+ invalidate();
+ }
+
+ if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
+ // Update the last touch co-ords, taking any scroll offset into account
+ mLastTouchX -= mScrollOffset[0];
+ mLastTouchY -= mScrollOffset[1];
+ if (ev != null) {
+ ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
+ }
+ mNestedOffsets[0] += mScrollOffset[0];
+ mNestedOffsets[1] += mScrollOffset[1];
+ } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
+ if (ev != null) {
+ pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
+ }
+ considerReleasingGlowsOnScroll(x, y);
+ }
+ if (consumedX != 0 || consumedY != 0) {
+ dispatchOnScrolled(consumedX, consumedY);
+ }
+ if (!awakenScrollBars()) {
+ invalidate();
+ }
+ return consumedX != 0 || consumedY != 0;
+ }
+
+ /**
+ * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal
+ * range. This value is used to compute the length of the thumb within the scrollbar's track.
+ * </p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your
+ * LayoutManager. </p>
+ *
+ * @return The horizontal offset of the scrollbar's thumb
+ * @see com.android.internal.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset
+ * (RecyclerView.State)
+ */
+ @Override
+ public int computeHorizontalScrollOffset() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the
+ * horizontal range. This value is used to compute the length of the thumb within the
+ * scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The horizontal extent of the scrollbar's thumb
+ * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)
+ */
+ @Override
+ public int computeHorizontalScrollExtent() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the horizontal range that the horizontal scrollbar represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The total horizontal range represented by the vertical scrollbar
+ * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
+ */
+ @Override
+ public int computeHorizontalScrollRange() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
+ * This value is used to compute the length of the thumb within the scrollbar's track. </p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The vertical offset of the scrollbar's thumb
+ * @see com.android.internal.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset
+ * (RecyclerView.State)
+ */
+ @Override
+ public int computeVerticalScrollOffset() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range.
+ * This value is used to compute the length of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The vertical extent of the scrollbar's thumb
+ * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)
+ */
+ @Override
+ public int computeVerticalScrollExtent() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The total vertical range represented by the vertical scrollbar
+ * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)
+ */
+ @Override
+ public int computeVerticalScrollRange() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
+ }
+
+
+ void eatRequestLayout() {
+ mEatRequestLayout++;
+ if (mEatRequestLayout == 1 && !mLayoutFrozen) {
+ mLayoutRequestEaten = false;
+ }
+ }
+
+ void resumeRequestLayout(boolean performLayoutChildren) {
+ if (mEatRequestLayout < 1) {
+ //noinspection PointlessBooleanExpression
+ if (DEBUG) {
+ throw new IllegalStateException("invalid eat request layout count");
+ }
+ mEatRequestLayout = 1;
+ }
+ if (!performLayoutChildren) {
+ // Reset the layout request eaten counter.
+ // This is necessary since eatRequest calls can be nested in which case the other
+ // call will override the inner one.
+ // for instance:
+ // eat layout for process adapter updates
+ // eat layout for dispatchLayout
+ // a bunch of req layout calls arrive
+
+ mLayoutRequestEaten = false;
+ }
+ if (mEatRequestLayout == 1) {
+ // when layout is frozen we should delay dispatchLayout()
+ if (performLayoutChildren && mLayoutRequestEaten && !mLayoutFrozen
+ && mLayout != null && mAdapter != null) {
+ dispatchLayout();
+ }
+ if (!mLayoutFrozen) {
+ mLayoutRequestEaten = false;
+ }
+ }
+ mEatRequestLayout--;
+ }
+
+ /**
+ * Enable or disable layout and scroll. After <code>setLayoutFrozen(true)</code> is called,
+ * Layout requests will be postponed until <code>setLayoutFrozen(false)</code> is called;
+ * child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)},
+ * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and
+ * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are
+ * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be
+ * called.
+ *
+ * <p>
+ * <code>setLayoutFrozen(true)</code> does not prevent app from directly calling {@link
+ * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition(
+ * RecyclerView, State, int)}.
+ * <p>
+ * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically
+ * stop frozen.
+ * <p>
+ * Note: Running ItemAnimator is not stopped automatically, it's caller's
+ * responsibility to call ItemAnimator.end().
+ *
+ * @param frozen true to freeze layout and scroll, false to re-enable.
+ */
+ public void setLayoutFrozen(boolean frozen) {
+ if (frozen != mLayoutFrozen) {
+ assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
+ if (!frozen) {
+ mLayoutFrozen = false;
+ if (mLayoutRequestEaten && mLayout != null && mAdapter != null) {
+ requestLayout();
+ }
+ mLayoutRequestEaten = false;
+ } else {
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent cancelEvent = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ onTouchEvent(cancelEvent);
+ mLayoutFrozen = true;
+ mIgnoreMotionEventTillDown = true;
+ stopScroll();
+ }
+ }
+ }
+
+ /**
+ * Returns true if layout and scroll are frozen.
+ *
+ * @return true if layout and scroll are frozen
+ * @see #setLayoutFrozen(boolean)
+ */
+ public boolean isLayoutFrozen() {
+ return mLayoutFrozen;
+ }
+
+ /**
+ * Animate a scroll by the given amount of pixels along either axis.
+ *
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ */
+ public void smoothScrollBy(int dx, int dy) {
+ smoothScrollBy(dx, dy, null);
+ }
+
+ /**
+ * Animate a scroll by the given amount of pixels along either axis.
+ *
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ * @param interpolator {@link Interpolator} to be used for scrolling. If it is
+ * {@code null}, RecyclerView is going to use the default interpolator.
+ */
+ public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return;
+ }
+ if (mLayoutFrozen) {
+ return;
+ }
+ if (!mLayout.canScrollHorizontally()) {
+ dx = 0;
+ }
+ if (!mLayout.canScrollVertically()) {
+ dy = 0;
+ }
+ if (dx != 0 || dy != 0) {
+ mViewFlinger.smoothScrollBy(dx, dy, interpolator);
+ }
+ }
+
+ /**
+ * Begin a standard fling with an initial velocity along each axis in pixels per second.
+ * If the velocity given is below the system-defined minimum this method will return false
+ * and no fling will occur.
+ *
+ * @param velocityX Initial horizontal velocity in pixels per second
+ * @param velocityY Initial vertical velocity in pixels per second
+ * @return true if the fling was started, false if the velocity was too low to fling or
+ * LayoutManager does not support scrolling in the axis fling is issued.
+ *
+ * @see LayoutManager#canScrollVertically()
+ * @see LayoutManager#canScrollHorizontally()
+ */
+ public boolean fling(int velocityX, int velocityY) {
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot fling without a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return false;
+ }
+ if (mLayoutFrozen) {
+ return false;
+ }
+
+ final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
+ final boolean canScrollVertical = mLayout.canScrollVertically();
+
+ if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) {
+ velocityX = 0;
+ }
+ if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) {
+ velocityY = 0;
+ }
+ if (velocityX == 0 && velocityY == 0) {
+ // If we don't have any velocity, return false
+ return false;
+ }
+
+ if (!dispatchNestedPreFling(velocityX, velocityY)) {
+ final boolean canScroll = canScrollHorizontal || canScrollVertical;
+ dispatchNestedFling(velocityX, velocityY, canScroll);
+
+ if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
+ return true;
+ }
+
+ if (canScroll) {
+ velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
+ velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
+ mViewFlinger.fling(velocityX, velocityY);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Stop any current scroll in progress, such as one started by
+ * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling.
+ */
+ public void stopScroll() {
+ setScrollState(SCROLL_STATE_IDLE);
+ stopScrollersInternal();
+ }
+
+ /**
+ * Similar to {@link #stopScroll()} but does not set the state.
+ */
+ private void stopScrollersInternal() {
+ mViewFlinger.stop();
+ if (mLayout != null) {
+ mLayout.stopSmoothScroller();
+ }
+ }
+
+ /**
+ * Returns the minimum velocity to start a fling.
+ *
+ * @return The minimum velocity to start a fling
+ */
+ public int getMinFlingVelocity() {
+ return mMinFlingVelocity;
+ }
+
+
+ /**
+ * Returns the maximum fling velocity used by this RecyclerView.
+ *
+ * @return The maximum fling velocity used by this RecyclerView.
+ */
+ public int getMaxFlingVelocity() {
+ return mMaxFlingVelocity;
+ }
+
+ /**
+ * Apply a pull to relevant overscroll glow effects
+ */
+ private void pullGlows(float x, float overscrollX, float y, float overscrollY) {
+ boolean invalidate = false;
+ if (overscrollX < 0) {
+ ensureLeftGlow();
+ mLeftGlow.onPull(-overscrollX / getWidth(), 1f - y / getHeight());
+ invalidate = true;
+ } else if (overscrollX > 0) {
+ ensureRightGlow();
+ mRightGlow.onPull(overscrollX / getWidth(), y / getHeight());
+ invalidate = true;
+ }
+
+ if (overscrollY < 0) {
+ ensureTopGlow();
+ mTopGlow.onPull(-overscrollY / getHeight(), x / getWidth());
+ invalidate = true;
+ } else if (overscrollY > 0) {
+ ensureBottomGlow();
+ mBottomGlow.onPull(overscrollY / getHeight(), 1f - x / getWidth());
+ invalidate = true;
+ }
+
+ if (invalidate || overscrollX != 0 || overscrollY != 0) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ private void releaseGlows() {
+ boolean needsInvalidate = false;
+ if (mLeftGlow != null) {
+ mLeftGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mTopGlow != null) {
+ mTopGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mRightGlow != null) {
+ mRightGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mBottomGlow != null) {
+ mBottomGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (needsInvalidate) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ void considerReleasingGlowsOnScroll(int dx, int dy) {
+ boolean needsInvalidate = false;
+ if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) {
+ mLeftGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) {
+ mRightGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) {
+ mTopGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) {
+ mBottomGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (needsInvalidate) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ void absorbGlows(int velocityX, int velocityY) {
+ if (velocityX < 0) {
+ ensureLeftGlow();
+ mLeftGlow.onAbsorb(-velocityX);
+ } else if (velocityX > 0) {
+ ensureRightGlow();
+ mRightGlow.onAbsorb(velocityX);
+ }
+
+ if (velocityY < 0) {
+ ensureTopGlow();
+ mTopGlow.onAbsorb(-velocityY);
+ } else if (velocityY > 0) {
+ ensureBottomGlow();
+ mBottomGlow.onAbsorb(velocityY);
+ }
+
+ if (velocityX != 0 || velocityY != 0) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ void ensureLeftGlow() {
+ if (mLeftGlow != null) {
+ return;
+ }
+ mLeftGlow = new EdgeEffect(getContext());
+ if (mClipToPadding) {
+ mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+ getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+ } else {
+ mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth());
+ }
+ }
+
+ void ensureRightGlow() {
+ if (mRightGlow != null) {
+ return;
+ }
+ mRightGlow = new EdgeEffect(getContext());
+ if (mClipToPadding) {
+ mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+ getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+ } else {
+ mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth());
+ }
+ }
+
+ void ensureTopGlow() {
+ if (mTopGlow != null) {
+ return;
+ }
+ mTopGlow = new EdgeEffect(getContext());
+ if (mClipToPadding) {
+ mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+ getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+ } else {
+ mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ }
+
+ void ensureBottomGlow() {
+ if (mBottomGlow != null) {
+ return;
+ }
+ mBottomGlow = new EdgeEffect(getContext());
+ if (mClipToPadding) {
+ mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+ getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+ } else {
+ mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight());
+ }
+ }
+
+ void invalidateGlows() {
+ mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null;
+ }
+
+ /**
+ * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
+ * in the Adapter but not visible in the UI), it employs a more involved focus search strategy
+ * that differs from other ViewGroups.
+ * <p>
+ * It first does a focus search within the RecyclerView. If this search finds a View that is in
+ * the focus direction with respect to the currently focused View, RecyclerView returns that
+ * child as the next focus target. When it cannot find such child, it calls
+ * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views
+ * in the focus search direction. If LayoutManager adds a View that matches the
+ * focus search criteria, it will be returned as the focus search result. Otherwise,
+ * RecyclerView will call parent to handle the focus search like a regular ViewGroup.
+ * <p>
+ * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that
+ * is not in the focus direction is still valid focus target which may not be the desired
+ * behavior if the Adapter has more children in the focus direction. To handle this case,
+ * RecyclerView converts the focus direction to an absolute direction and makes a preliminary
+ * focus search in that direction. If there are no Views to gain focus, it will call
+ * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a
+ * focus search with the original (relative) direction. This allows RecyclerView to provide
+ * better candidates to the focus search while still allowing the view system to take focus from
+ * the RecyclerView and give it to a more suitable child if such child exists.
+ *
+ * @param focused The view that currently has focus
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD},
+ * {@link View#FOCUS_BACKWARD} or 0 for not applicable.
+ *
+ * @return A new View that can be the next focus after the focused View
+ */
+ @Override
+ public View focusSearch(View focused, int direction) {
+ View result = mLayout.onInterceptFocusSearch(focused, direction);
+ if (result != null) {
+ return result;
+ }
+ final boolean canRunFocusFailure = mAdapter != null && mLayout != null
+ && !isComputingLayout() && !mLayoutFrozen;
+
+ final FocusFinder ff = FocusFinder.getInstance();
+ if (canRunFocusFailure
+ && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) {
+ // convert direction to absolute direction and see if we have a view there and if not
+ // tell LayoutManager to add if it can.
+ boolean needsFocusFailureLayout = false;
+ if (mLayout.canScrollVertically()) {
+ final int absDir =
+ direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
+ final View found = ff.findNextFocus(this, focused, absDir);
+ needsFocusFailureLayout = found == null;
+ if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
+ // Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
+ direction = absDir;
+ }
+ }
+ if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) {
+ boolean rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
+ ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ final View found = ff.findNextFocus(this, focused, absDir);
+ needsFocusFailureLayout = found == null;
+ if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
+ // Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
+ direction = absDir;
+ }
+ }
+ if (needsFocusFailureLayout) {
+ consumePendingUpdateOperations();
+ final View focusedItemView = findContainingItemView(focused);
+ if (focusedItemView == null) {
+ // panic, focused view is not a child anymore, cannot call super.
+ return null;
+ }
+ eatRequestLayout();
+ mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
+ resumeRequestLayout(false);
+ }
+ result = ff.findNextFocus(this, focused, direction);
+ } else {
+ result = ff.findNextFocus(this, focused, direction);
+ if (result == null && canRunFocusFailure) {
+ consumePendingUpdateOperations();
+ final View focusedItemView = findContainingItemView(focused);
+ if (focusedItemView == null) {
+ // panic, focused view is not a child anymore, cannot call super.
+ return null;
+ }
+ eatRequestLayout();
+ result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
+ resumeRequestLayout(false);
+ }
+ }
+ return isPreferredNextFocus(focused, result, direction)
+ ? result : super.focusSearch(focused, direction);
+ }
+
+ /**
+ * Checks if the new focus candidate is a good enough candidate such that RecyclerView will
+ * assign it as the next focus View instead of letting view hierarchy decide.
+ * A good candidate means a View that is aligned in the focus direction wrt the focused View
+ * and is not the RecyclerView itself.
+ * When this method returns false, RecyclerView will let the parent make the decision so the
+ * same View may still get the focus as a result of that search.
+ */
+ private boolean isPreferredNextFocus(View focused, View next, int direction) {
+ if (next == null || next == this) {
+ return false;
+ }
+ if (focused == null) {
+ return true;
+ }
+
+ if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
+ final boolean rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ final int absHorizontal = (direction == View.FOCUS_FORWARD) ^ rtl
+ ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ if (isPreferredNextFocusAbsolute(focused, next, absHorizontal)) {
+ return true;
+ }
+ if (direction == View.FOCUS_FORWARD) {
+ return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_DOWN);
+ } else {
+ return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_UP);
+ }
+ } else {
+ return isPreferredNextFocusAbsolute(focused, next, direction);
+ }
+
+ }
+
+ /**
+ * Logic taken from FocusSearch#isCandidate
+ */
+ private boolean isPreferredNextFocusAbsolute(View focused, View next, int direction) {
+ mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
+ mTempRect2.set(0, 0, next.getWidth(), next.getHeight());
+ offsetDescendantRectToMyCoords(focused, mTempRect);
+ offsetDescendantRectToMyCoords(next, mTempRect2);
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return (mTempRect.right > mTempRect2.right
+ || mTempRect.left >= mTempRect2.right)
+ && mTempRect.left > mTempRect2.left;
+ case View.FOCUS_RIGHT:
+ return (mTempRect.left < mTempRect2.left
+ || mTempRect.right <= mTempRect2.left)
+ && mTempRect.right < mTempRect2.right;
+ case View.FOCUS_UP:
+ return (mTempRect.bottom > mTempRect2.bottom
+ || mTempRect.top >= mTempRect2.bottom)
+ && mTempRect.top > mTempRect2.top;
+ case View.FOCUS_DOWN:
+ return (mTempRect.top < mTempRect2.top
+ || mTempRect.bottom <= mTempRect2.top)
+ && mTempRect.bottom < mTempRect2.bottom;
+ }
+ throw new IllegalArgumentException("direction must be absolute. received:" + direction);
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) {
+ mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
+
+ // get item decor offsets w/o refreshing. If they are invalid, there will be another
+ // layout pass to fix them, then it is LayoutManager's responsibility to keep focused
+ // View in viewport.
+ final ViewGroup.LayoutParams focusedLayoutParams = focused.getLayoutParams();
+ if (focusedLayoutParams instanceof LayoutParams) {
+ // if focused child has item decors, use them. Otherwise, ignore.
+ final LayoutParams lp = (LayoutParams) focusedLayoutParams;
+ if (!lp.mInsetsDirty) {
+ final Rect insets = lp.mDecorInsets;
+ mTempRect.left -= insets.left;
+ mTempRect.right += insets.right;
+ mTempRect.top -= insets.top;
+ mTempRect.bottom += insets.bottom;
+ }
+ }
+
+ offsetDescendantRectToMyCoords(focused, mTempRect);
+ offsetRectIntoDescendantCoords(child, mTempRect);
+ requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete);
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
+ return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate);
+ }
+
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) {
+ super.addFocusables(views, direction, focusableMode);
+ }
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ if (isComputingLayout()) {
+ // if we are in the middle of a layout calculation, don't let any child take focus.
+ // RV will handle it after layout calculation is finished.
+ return false;
+ }
+ return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mLayoutOrScrollCounter = 0;
+ mIsAttached = true;
+ mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested();
+ if (mLayout != null) {
+ mLayout.dispatchAttachedToWindow(this);
+ }
+ mPostedAnimatorRunner = false;
+
+ if (ALLOW_THREAD_GAP_WORK) {
+ // Register with gap worker
+ mGapWorker = GapWorker.sGapWorker.get();
+ if (mGapWorker == null) {
+ mGapWorker = new GapWorker();
+
+ // break 60 fps assumption if data from display appears valid
+ // NOTE: we only do this query once, statically, because it's very expensive (> 1ms)
+ Display display = getDisplay();
+ float refreshRate = 60.0f;
+ if (!isInEditMode() && display != null) {
+ float displayRefreshRate = display.getRefreshRate();
+ if (displayRefreshRate >= 30.0f) {
+ refreshRate = displayRefreshRate;
+ }
+ }
+ mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate);
+ GapWorker.sGapWorker.set(mGapWorker);
+ }
+ mGapWorker.add(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mItemAnimator != null) {
+ mItemAnimator.endAnimations();
+ }
+ stopScroll();
+ mIsAttached = false;
+ if (mLayout != null) {
+ mLayout.dispatchDetachedFromWindow(this, mRecycler);
+ }
+ mPendingAccessibilityImportanceChange.clear();
+ removeCallbacks(mItemAnimatorRunner);
+ mViewInfoStore.onDetach();
+
+ if (ALLOW_THREAD_GAP_WORK) {
+ // Unregister with gap worker
+ mGapWorker.remove(this);
+ mGapWorker = null;
+ }
+ }
+
+ /**
+ * Returns true if RecyclerView is attached to window.
+ */
+ // @override
+ public boolean isAttachedToWindow() {
+ return mIsAttached;
+ }
+
+ /**
+ * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+ * {@link IllegalStateException} if it <b>is not</b>.
+ *
+ * @param message The message for the exception. Can be null.
+ * @see #assertNotInLayoutOrScroll(String)
+ */
+ void assertInLayoutOrScroll(String message) {
+ if (!isComputingLayout()) {
+ if (message == null) {
+ throw new IllegalStateException("Cannot call this method unless RecyclerView is "
+ + "computing a layout or scrolling");
+ }
+ throw new IllegalStateException(message);
+
+ }
+ }
+
+ /**
+ * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+ * {@link IllegalStateException} if it <b>is</b>.
+ *
+ * @param message The message for the exception. Can be null.
+ * @see #assertInLayoutOrScroll(String)
+ */
+ void assertNotInLayoutOrScroll(String message) {
+ if (isComputingLayout()) {
+ if (message == null) {
+ throw new IllegalStateException("Cannot call this method while RecyclerView is "
+ + "computing a layout or scrolling");
+ }
+ throw new IllegalStateException(message);
+ }
+ if (mDispatchScrollCounter > 0) {
+ Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might be run"
+ + " during a measure & layout pass where you cannot change the RecyclerView"
+ + " data. Any method call that might change the structure of the RecyclerView"
+ + " or the adapter contents should be postponed to the next frame.",
+ new IllegalStateException(""));
+ }
+ }
+
+ /**
+ * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
+ * to child views or this view's standard scrolling behavior.
+ *
+ * <p>Client code may use listeners to implement item manipulation behavior. Once a listener
+ * returns true from
+ * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
+ * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
+ * for each incoming MotionEvent until the end of the gesture.</p>
+ *
+ * @param listener Listener to add
+ * @see SimpleOnItemTouchListener
+ */
+ public void addOnItemTouchListener(OnItemTouchListener listener) {
+ mOnItemTouchListeners.add(listener);
+ }
+
+ /**
+ * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events.
+ *
+ * @param listener Listener to remove
+ */
+ public void removeOnItemTouchListener(OnItemTouchListener listener) {
+ mOnItemTouchListeners.remove(listener);
+ if (mActiveOnItemTouchListener == listener) {
+ mActiveOnItemTouchListener = null;
+ }
+ }
+
+ private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
+ final int action = e.getAction();
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
+ mActiveOnItemTouchListener = null;
+ }
+
+ final int listenerCount = mOnItemTouchListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+ if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
+ mActiveOnItemTouchListener = listener;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean dispatchOnItemTouch(MotionEvent e) {
+ final int action = e.getAction();
+ if (mActiveOnItemTouchListener != null) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ // Stale state from a previous gesture, we're starting a new one. Clear it.
+ mActiveOnItemTouchListener = null;
+ } else {
+ mActiveOnItemTouchListener.onTouchEvent(this, e);
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ // Clean up for the next gesture.
+ mActiveOnItemTouchListener = null;
+ }
+ return true;
+ }
+ }
+
+ // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept
+ // as called from onInterceptTouchEvent; skip it.
+ if (action != MotionEvent.ACTION_DOWN) {
+ final int listenerCount = mOnItemTouchListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+ if (listener.onInterceptTouchEvent(this, e)) {
+ mActiveOnItemTouchListener = listener;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent e) {
+ if (mLayoutFrozen) {
+ // When layout is frozen, RV does not intercept the motion event.
+ // A child view e.g. a button may still get the click.
+ return false;
+ }
+ if (dispatchOnItemTouchIntercept(e)) {
+ cancelTouch();
+ return true;
+ }
+
+ if (mLayout == null) {
+ return false;
+ }
+
+ final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
+ final boolean canScrollVertically = mLayout.canScrollVertically();
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(e);
+
+ final int action = e.getActionMasked();
+ final int actionIndex = e.getActionIndex();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (mIgnoreMotionEventTillDown) {
+ mIgnoreMotionEventTillDown = false;
+ }
+ mScrollPointerId = e.getPointerId(0);
+ mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
+
+ if (mScrollState == SCROLL_STATE_SETTLING) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ setScrollState(SCROLL_STATE_DRAGGING);
+ }
+
+ // Clear the nested offsets
+ mNestedOffsets[0] = mNestedOffsets[1] = 0;
+
+ int nestedScrollAxis = View.SCROLL_AXIS_NONE;
+ if (canScrollHorizontally) {
+ nestedScrollAxis |= View.SCROLL_AXIS_HORIZONTAL;
+ }
+ if (canScrollVertically) {
+ nestedScrollAxis |= View.SCROLL_AXIS_VERTICAL;
+ }
+ startNestedScroll(nestedScrollAxis);
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ mScrollPointerId = e.getPointerId(actionIndex);
+ mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
+ break;
+
+ case MotionEvent.ACTION_MOVE: {
+ final int index = e.findPointerIndex(mScrollPointerId);
+ if (index < 0) {
+ Log.e(TAG, "Error processing scroll; pointer index for id "
+ + mScrollPointerId + " not found. Did any MotionEvents get skipped?");
+ return false;
+ }
+
+ final int x = (int) (e.getX(index) + 0.5f);
+ final int y = (int) (e.getY(index) + 0.5f);
+ if (mScrollState != SCROLL_STATE_DRAGGING) {
+ final int dx = x - mInitialTouchX;
+ final int dy = y - mInitialTouchY;
+ boolean startScroll = false;
+ if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
+ mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
+ startScroll = true;
+ }
+ if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
+ mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
+ startScroll = true;
+ }
+ if (startScroll) {
+ setScrollState(SCROLL_STATE_DRAGGING);
+ }
+ }
+ } break;
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ onPointerUp(e);
+ } break;
+
+ case MotionEvent.ACTION_UP: {
+ mVelocityTracker.clear();
+ stopNestedScroll();
+ } break;
+
+ case MotionEvent.ACTION_CANCEL: {
+ cancelTouch();
+ }
+ }
+ return mScrollState == SCROLL_STATE_DRAGGING;
+ }
+
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ final int listenerCount = mOnItemTouchListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+ listener.onRequestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ if (mLayoutFrozen || mIgnoreMotionEventTillDown) {
+ return false;
+ }
+ if (dispatchOnItemTouch(e)) {
+ cancelTouch();
+ return true;
+ }
+
+ if (mLayout == null) {
+ return false;
+ }
+
+ final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
+ final boolean canScrollVertically = mLayout.canScrollVertically();
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ boolean eventAddedToVelocityTracker = false;
+
+ final MotionEvent vtev = MotionEvent.obtain(e);
+ final int action = e.getActionMasked();
+ final int actionIndex = e.getActionIndex();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ mNestedOffsets[0] = mNestedOffsets[1] = 0;
+ }
+ vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mScrollPointerId = e.getPointerId(0);
+ mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
+
+ int nestedScrollAxis = View.SCROLL_AXIS_NONE;
+ if (canScrollHorizontally) {
+ nestedScrollAxis |= View.SCROLL_AXIS_HORIZONTAL;
+ }
+ if (canScrollVertically) {
+ nestedScrollAxis |= View.SCROLL_AXIS_VERTICAL;
+ }
+ startNestedScroll(nestedScrollAxis);
+ } break;
+
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ mScrollPointerId = e.getPointerId(actionIndex);
+ mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
+ } break;
+
+ case MotionEvent.ACTION_MOVE: {
+ final int index = e.findPointerIndex(mScrollPointerId);
+ if (index < 0) {
+ Log.e(TAG, "Error processing scroll; pointer index for id "
+ + mScrollPointerId + " not found. Did any MotionEvents get skipped?");
+ return false;
+ }
+
+ final int x = (int) (e.getX(index) + 0.5f);
+ final int y = (int) (e.getY(index) + 0.5f);
+ int dx = mLastTouchX - x;
+ int dy = mLastTouchY - y;
+
+ if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
+ dx -= mScrollConsumed[0];
+ dy -= mScrollConsumed[1];
+ vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
+ // Updated the nested offsets
+ mNestedOffsets[0] += mScrollOffset[0];
+ mNestedOffsets[1] += mScrollOffset[1];
+ }
+
+ if (mScrollState != SCROLL_STATE_DRAGGING) {
+ boolean startScroll = false;
+ if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
+ if (dx > 0) {
+ dx -= mTouchSlop;
+ } else {
+ dx += mTouchSlop;
+ }
+ startScroll = true;
+ }
+ if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
+ if (dy > 0) {
+ dy -= mTouchSlop;
+ } else {
+ dy += mTouchSlop;
+ }
+ startScroll = true;
+ }
+ if (startScroll) {
+ setScrollState(SCROLL_STATE_DRAGGING);
+ }
+ }
+
+ if (mScrollState == SCROLL_STATE_DRAGGING) {
+ mLastTouchX = x - mScrollOffset[0];
+ mLastTouchY = y - mScrollOffset[1];
+
+ if (scrollByInternal(
+ canScrollHorizontally ? dx : 0,
+ canScrollVertically ? dy : 0,
+ vtev)) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ if (mGapWorker != null && (dx != 0 || dy != 0)) {
+ mGapWorker.postFromTraversal(this, dx, dy);
+ }
+ }
+ } break;
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ onPointerUp(e);
+ } break;
+
+ case MotionEvent.ACTION_UP: {
+ mVelocityTracker.addMovement(vtev);
+ eventAddedToVelocityTracker = true;
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
+ final float xvel = canScrollHorizontally
+ ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
+ final float yvel = canScrollVertically
+ ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
+ if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
+ setScrollState(SCROLL_STATE_IDLE);
+ }
+ resetTouch();
+ } break;
+
+ case MotionEvent.ACTION_CANCEL: {
+ cancelTouch();
+ } break;
+ }
+
+ if (!eventAddedToVelocityTracker) {
+ mVelocityTracker.addMovement(vtev);
+ }
+ vtev.recycle();
+
+ return true;
+ }
+
+ private void resetTouch() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ stopNestedScroll();
+ releaseGlows();
+ }
+
+ private void cancelTouch() {
+ resetTouch();
+ setScrollState(SCROLL_STATE_IDLE);
+ }
+
+ private void onPointerUp(MotionEvent e) {
+ final int actionIndex = e.getActionIndex();
+ if (e.getPointerId(actionIndex) == mScrollPointerId) {
+ // Pick a new pointer to pick up the slack.
+ final int newIndex = actionIndex == 0 ? 1 : 0;
+ mScrollPointerId = e.getPointerId(newIndex);
+ mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f);
+ }
+ }
+
+ // @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if (mLayout == null) {
+ return false;
+ }
+ if (mLayoutFrozen) {
+ return false;
+ }
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ if (event.getAction() == MotionEvent.ACTION_SCROLL) {
+ final float vScroll, hScroll;
+ if (mLayout.canScrollVertically()) {
+ // Inverse the sign of the vertical scroll to align the scroll orientation
+ // with AbsListView.
+ vScroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ } else {
+ vScroll = 0f;
+ }
+ if (mLayout.canScrollHorizontally()) {
+ hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
+ } else {
+ hScroll = 0f;
+ }
+
+ if (vScroll != 0 || hScroll != 0) {
+ final float scrollFactor = getScrollFactor();
+ scrollByInternal((int) (hScroll * scrollFactor),
+ (int) (vScroll * scrollFactor), event);
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Ported from View.getVerticalScrollFactor.
+ */
+ private float getScrollFactor() {
+ if (mScrollFactor == Float.MIN_VALUE) {
+ TypedValue outValue = new TypedValue();
+ if (getContext().getTheme().resolveAttribute(
+ android.R.attr.listPreferredItemHeight, outValue, true)) {
+ mScrollFactor = outValue.getDimension(
+ getContext().getResources().getDisplayMetrics());
+ } else {
+ return 0; //listPreferredItemHeight is not defined, no generic scrolling
+ }
+ }
+ return mScrollFactor;
+ }
+
+ @Override
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ if (mLayout == null) {
+ defaultOnMeasure(widthSpec, heightSpec);
+ return;
+ }
+ if (mLayout.mAutoMeasure) {
+ final int widthMode = MeasureSpec.getMode(widthSpec);
+ final int heightMode = MeasureSpec.getMode(heightSpec);
+ final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
+ && heightMode == MeasureSpec.EXACTLY;
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ if (skipMeasure || mAdapter == null) {
+ return;
+ }
+ if (mState.mLayoutStep == State.STEP_START) {
+ dispatchLayoutStep1();
+ }
+ // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
+ // consistency
+ mLayout.setMeasureSpecs(widthSpec, heightSpec);
+ mState.mIsMeasuring = true;
+ dispatchLayoutStep2();
+
+ // now we can get the width and height from the children.
+ mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
+
+ // if RecyclerView has non-exact width and height and if there is at least one child
+ // which also has non-exact width & height, we have to re-measure.
+ if (mLayout.shouldMeasureTwice()) {
+ mLayout.setMeasureSpecs(
+ MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
+ mState.mIsMeasuring = true;
+ dispatchLayoutStep2();
+ // now we can get the width and height from the children.
+ mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
+ }
+ } else {
+ if (mHasFixedSize) {
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ return;
+ }
+ // custom onMeasure
+ if (mAdapterUpdateDuringMeasure) {
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ processAdapterUpdatesAndSetAnimationFlags();
+ onExitLayoutOrScroll();
+
+ if (mState.mRunPredictiveAnimations) {
+ mState.mInPreLayout = true;
+ } else {
+ // consume remaining updates to provide a consistent state with the layout pass.
+ mAdapterHelper.consumeUpdatesInOnePass();
+ mState.mInPreLayout = false;
+ }
+ mAdapterUpdateDuringMeasure = false;
+ resumeRequestLayout(false);
+ }
+
+ if (mAdapter != null) {
+ mState.mItemCount = mAdapter.getItemCount();
+ } else {
+ mState.mItemCount = 0;
+ }
+ eatRequestLayout();
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ resumeRequestLayout(false);
+ mState.mInPreLayout = false; // clear
+ }
+ }
+
+ /**
+ * Used when onMeasure is called before layout manager is set
+ */
+ void defaultOnMeasure(int widthSpec, int heightSpec) {
+ // calling LayoutManager here is not pretty but that API is already public and it is better
+ // than creating another method since this is internal.
+ final int width = LayoutManager.chooseSize(widthSpec,
+ getPaddingLeft() + getPaddingRight(),
+ getMinimumWidth());
+ final int height = LayoutManager.chooseSize(heightSpec,
+ getPaddingTop() + getPaddingBottom(),
+ getMinimumHeight());
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (w != oldw || h != oldh) {
+ invalidateGlows();
+ // layout's w/h are updated during measure/layout steps.
+ }
+ }
+
+ /**
+ * Sets the {@link ItemAnimator} that will handle animations involving changes
+ * to the items in this RecyclerView. By default, RecyclerView instantiates and
+ * uses an instance of {@link DefaultItemAnimator}. Whether item animations are
+ * enabled for the RecyclerView depends on the ItemAnimator and whether
+ * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations()
+ * supports item animations}.
+ *
+ * @param animator The ItemAnimator being set. If null, no animations will occur
+ * when changes occur to the items in this RecyclerView.
+ */
+ public void setItemAnimator(ItemAnimator animator) {
+ if (mItemAnimator != null) {
+ mItemAnimator.endAnimations();
+ mItemAnimator.setListener(null);
+ }
+ mItemAnimator = animator;
+ if (mItemAnimator != null) {
+ mItemAnimator.setListener(mItemAnimatorListener);
+ }
+ }
+
+ void onEnterLayoutOrScroll() {
+ mLayoutOrScrollCounter++;
+ }
+
+ void onExitLayoutOrScroll() {
+ mLayoutOrScrollCounter--;
+ if (mLayoutOrScrollCounter < 1) {
+ if (DEBUG && mLayoutOrScrollCounter < 0) {
+ throw new IllegalStateException("layout or scroll counter cannot go below zero."
+ + "Some calls are not matching");
+ }
+ mLayoutOrScrollCounter = 0;
+ dispatchContentChangedIfNecessary();
+ dispatchPendingImportantForAccessibilityChanges();
+ }
+ }
+
+ boolean isAccessibilityEnabled() {
+ return mAccessibilityManager != null && mAccessibilityManager.isEnabled();
+ }
+
+ private void dispatchContentChangedIfNecessary() {
+ final int flags = mEatenAccessibilityChangeFlags;
+ mEatenAccessibilityChangeFlags = 0;
+ if (flags != 0 && isAccessibilityEnabled()) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(flags);
+ sendAccessibilityEventUnchecked(event);
+ }
+ }
+
+ /**
+ * Returns whether RecyclerView is currently computing a layout.
+ * <p>
+ * If this method returns true, it means that RecyclerView is in a lockdown state and any
+ * attempt to update adapter contents will result in an exception because adapter contents
+ * cannot be changed while RecyclerView is trying to compute the layout.
+ * <p>
+ * It is very unlikely that your code will be running during this state as it is
+ * called by the framework when a layout traversal happens or RecyclerView starts to scroll
+ * in response to system events (touch, accessibility etc).
+ * <p>
+ * This case may happen if you have some custom logic to change adapter contents in
+ * response to a View callback (e.g. focus change callback) which might be triggered during a
+ * layout calculation. In these cases, you should just postpone the change using a Handler or a
+ * similar mechanism.
+ *
+ * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code>
+ * otherwise
+ */
+ public boolean isComputingLayout() {
+ return mLayoutOrScrollCounter > 0;
+ }
+
+ /**
+ * Returns true if an accessibility event should not be dispatched now. This happens when an
+ * accessibility request arrives while RecyclerView does not have a stable state which is very
+ * hard to handle for a LayoutManager. Instead, this method records necessary information about
+ * the event and dispatches a window change event after the critical section is finished.
+ *
+ * @return True if the accessibility event should be postponed.
+ */
+ boolean shouldDeferAccessibilityEvent(AccessibilityEvent event) {
+ if (isComputingLayout()) {
+ int type = 0;
+ if (event != null) {
+ type = event.getContentChangeTypes();
+ }
+ if (type == 0) {
+ type = AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+ }
+ mEatenAccessibilityChangeFlags |= type;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
+ if (shouldDeferAccessibilityEvent(event)) {
+ return;
+ }
+ super.sendAccessibilityEventUnchecked(event);
+ }
+
+ /**
+ * Gets the current ItemAnimator for this RecyclerView. A null return value
+ * indicates that there is no animator and that item changes will happen without
+ * any animations. By default, RecyclerView instantiates and
+ * uses an instance of {@link DefaultItemAnimator}.
+ *
+ * @return ItemAnimator The current ItemAnimator. If null, no animations will occur
+ * when changes occur to the items in this RecyclerView.
+ */
+ public ItemAnimator getItemAnimator() {
+ return mItemAnimator;
+ }
+
+ /**
+ * Post a runnable to the next frame to run pending item animations. Only the first such
+ * request will be posted, governed by the mPostedAnimatorRunner flag.
+ */
+ void postAnimationRunner() {
+ if (!mPostedAnimatorRunner && mIsAttached) {
+ postOnAnimation(mItemAnimatorRunner);
+ mPostedAnimatorRunner = true;
+ }
+ }
+
+ private boolean predictiveItemAnimationsEnabled() {
+ return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
+ }
+
+ /**
+ * Consumes adapter updates and calculates which type of animations we want to run.
+ * Called in onMeasure and dispatchLayout.
+ * <p>
+ * This method may process only the pre-layout state of updates or all of them.
+ */
+ private void processAdapterUpdatesAndSetAnimationFlags() {
+ if (mDataSetHasChangedAfterLayout) {
+ // Processing these items have no value since data set changed unexpectedly.
+ // Instead, we just reset it.
+ mAdapterHelper.reset();
+ mLayout.onItemsChanged(this);
+ }
+ // simple animations are a subset of advanced animations (which will cause a
+ // pre-layout step)
+ // If layout supports predictive animations, pre-process to decide if we want to run them
+ if (predictiveItemAnimationsEnabled()) {
+ mAdapterHelper.preProcess();
+ } else {
+ mAdapterHelper.consumeUpdatesInOnePass();
+ }
+ boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
+ mState.mRunSimpleAnimations = mFirstLayoutComplete
+ && mItemAnimator != null
+ && (mDataSetHasChangedAfterLayout
+ || animationTypeSupported
+ || mLayout.mRequestedSimpleAnimations)
+ && (!mDataSetHasChangedAfterLayout
+ || mAdapter.hasStableIds());
+ mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
+ && animationTypeSupported
+ && !mDataSetHasChangedAfterLayout
+ && predictiveItemAnimationsEnabled();
+ }
+
+ /**
+ * Wrapper around layoutChildren() that handles animating changes caused by layout.
+ * Animations work on the assumption that there are five different kinds of items
+ * in play:
+ * PERSISTENT: items are visible before and after layout
+ * REMOVED: items were visible before layout and were removed by the app
+ * ADDED: items did not exist before layout and were added by the app
+ * DISAPPEARING: items exist in the data set before/after, but changed from
+ * visible to non-visible in the process of layout (they were moved off
+ * screen as a side-effect of other changes)
+ * APPEARING: items exist in the data set before/after, but changed from
+ * non-visible to visible in the process of layout (they were moved on
+ * screen as a side-effect of other changes)
+ * The overall approach figures out what items exist before/after layout and
+ * infers one of the five above states for each of the items. Then the animations
+ * are set up accordingly:
+ * PERSISTENT views are animated via
+ * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
+ * DISAPPEARING views are animated via
+ * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
+ * APPEARING views are animated via
+ * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
+ * and changed views are animated via
+ * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}.
+ */
+ void dispatchLayout() {
+ if (mAdapter == null) {
+ Log.e(TAG, "No adapter attached; skipping layout");
+ // leave the state in START
+ return;
+ }
+ if (mLayout == null) {
+ Log.e(TAG, "No layout manager attached; skipping layout");
+ // leave the state in START
+ return;
+ }
+ mState.mIsMeasuring = false;
+ if (mState.mLayoutStep == State.STEP_START) {
+ dispatchLayoutStep1();
+ mLayout.setExactMeasureSpecsFrom(this);
+ dispatchLayoutStep2();
+ } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
+ || mLayout.getHeight() != getHeight()) {
+ // First 2 steps are done in onMeasure but looks like we have to run again due to
+ // changed size.
+ mLayout.setExactMeasureSpecsFrom(this);
+ dispatchLayoutStep2();
+ } else {
+ // always make sure we sync them (to ensure mode is exact)
+ mLayout.setExactMeasureSpecsFrom(this);
+ }
+ dispatchLayoutStep3();
+ }
+
+ private void saveFocusInfo() {
+ View child = null;
+ if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) {
+ child = getFocusedChild();
+ }
+
+ final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child);
+ if (focusedVh == null) {
+ resetFocusInfo();
+ } else {
+ mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID;
+ // mFocusedItemPosition should hold the current adapter position of the previously
+ // focused item. If the item is removed, we store the previous adapter position of the
+ // removed item.
+ mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION
+ : (focusedVh.isRemoved() ? focusedVh.mOldPosition
+ : focusedVh.getAdapterPosition());
+ mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView);
+ }
+ }
+
+ private void resetFocusInfo() {
+ mState.mFocusedItemId = NO_ID;
+ mState.mFocusedItemPosition = NO_POSITION;
+ mState.mFocusedSubChildId = View.NO_ID;
+ }
+
+ /**
+ * Finds the best view candidate to request focus on using mFocusedItemPosition index of the
+ * previously focused item. It first traverses the adapter forward to find a focusable candidate
+ * and if no such candidate is found, it reverses the focus search direction for the items
+ * before the mFocusedItemPosition'th index;
+ * @return The best candidate to request focus on, or null if no such candidate exists. Null
+ * indicates all the existing adapter items are unfocusable.
+ */
+ @Nullable
+ private View findNextViewToFocus() {
+ int startFocusSearchIndex = mState.mFocusedItemPosition != -1 ? mState.mFocusedItemPosition
+ : 0;
+ ViewHolder nextFocus;
+ final int itemCount = mState.getItemCount();
+ for (int i = startFocusSearchIndex; i < itemCount; i++) {
+ nextFocus = findViewHolderForAdapterPosition(i);
+ if (nextFocus == null) {
+ break;
+ }
+ if (nextFocus.itemView.hasFocusable()) {
+ return nextFocus.itemView;
+ }
+ }
+ final int limit = Math.min(itemCount, startFocusSearchIndex);
+ for (int i = limit - 1; i >= 0; i--) {
+ nextFocus = findViewHolderForAdapterPosition(i);
+ if (nextFocus == null) {
+ return null;
+ }
+ if (nextFocus.itemView.hasFocusable()) {
+ return nextFocus.itemView;
+ }
+ }
+ return null;
+ }
+
+ private void recoverFocusFromState() {
+ if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus()
+ || getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS
+ || (getDescendantFocusability() == FOCUS_BEFORE_DESCENDANTS && isFocused())) {
+ // No-op if either of these cases happens:
+ // 1. RV has no focus, or 2. RV blocks focus to its children, or 3. RV takes focus
+ // before its children and is focused (i.e. it already stole the focus away from its
+ // descendants).
+ return;
+ }
+ // only recover focus if RV itself has the focus or the focused view is hidden
+ if (!isFocused()) {
+ final View focusedChild = getFocusedChild();
+ if (IGNORE_DETACHED_FOCUSED_CHILD
+ && (focusedChild.getParent() == null || !focusedChild.hasFocus())) {
+ // Special handling of API 15-. A focused child can be invalid because mFocus is not
+ // cleared when the child is detached (mParent = null),
+ // This happens because clearFocus on API 15- does not invalidate mFocus of its
+ // parent when this child is detached.
+ // For API 16+, this is not an issue because requestFocus takes care of clearing the
+ // prior detached focused child. For API 15- the problem happens in 2 cases because
+ // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called
+ // for the current focused item which calls clearChild or 2. when the prior focused
+ // child is removed, removeDetachedView called in layout step 3 which calls
+ // clearChild. We should ignore this invalid focused child in all our calculations
+ // for the next view to receive focus, and apply the focus recovery logic instead.
+ if (mChildHelper.getChildCount() == 0) {
+ // No children left. Request focus on the RV itself since one of its children
+ // was holding focus previously.
+ requestFocus();
+ return;
+ }
+ } else if (!mChildHelper.isHidden(focusedChild)) {
+ // If the currently focused child is hidden, apply the focus recovery logic.
+ // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/.
+ return;
+ }
+ }
+ ViewHolder focusTarget = null;
+ // RV first attempts to locate the previously focused item to request focus on using
+ // mFocusedItemId. If such an item no longer exists, it then makes a best-effort attempt to
+ // find the next best candidate to request focus on based on mFocusedItemPosition.
+ if (mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) {
+ focusTarget = findViewHolderForItemId(mState.mFocusedItemId);
+ }
+ View viewToFocus = null;
+ if (focusTarget == null || mChildHelper.isHidden(focusTarget.itemView)
+ || !focusTarget.itemView.hasFocusable()) {
+ if (mChildHelper.getChildCount() > 0) {
+ // At this point, RV has focus and either of these conditions are true:
+ // 1. There's no previously focused item either because RV received focused before
+ // layout, or the previously focused item was removed, or RV doesn't have stable IDs
+ // 2. Previous focus child is hidden, or 3. Previous focused child is no longer
+ // focusable. In either of these cases, we make sure that RV still passes down the
+ // focus to one of its focusable children using a best-effort algorithm.
+ viewToFocus = findNextViewToFocus();
+ }
+ } else {
+ // looks like the focused item has been replaced with another view that represents the
+ // same item in the adapter. Request focus on that.
+ viewToFocus = focusTarget.itemView;
+ }
+
+ if (viewToFocus != null) {
+ if (mState.mFocusedSubChildId != NO_ID) {
+ View child = viewToFocus.findViewById(mState.mFocusedSubChildId);
+ if (child != null && child.isFocusable()) {
+ viewToFocus = child;
+ }
+ }
+ viewToFocus.requestFocus();
+ }
+ }
+
+ private int getDeepestFocusedViewWithId(View view) {
+ int lastKnownId = view.getId();
+ while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) {
+ view = ((ViewGroup) view).getFocusedChild();
+ final int id = view.getId();
+ if (id != View.NO_ID) {
+ lastKnownId = view.getId();
+ }
+ }
+ return lastKnownId;
+ }
+
+ /**
+ * The first step of a layout where we;
+ * - process adapter updates
+ * - decide which animation should run
+ * - save information about current views
+ * - If necessary, run predictive layout and save its information
+ */
+ private void dispatchLayoutStep1() {
+ mState.assertLayoutStep(State.STEP_START);
+ mState.mIsMeasuring = false;
+ eatRequestLayout();
+ mViewInfoStore.clear();
+ onEnterLayoutOrScroll();
+ processAdapterUpdatesAndSetAnimationFlags();
+ saveFocusInfo();
+ mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
+ mItemsAddedOrRemoved = mItemsChanged = false;
+ mState.mInPreLayout = mState.mRunPredictiveAnimations;
+ mState.mItemCount = mAdapter.getItemCount();
+ findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
+
+ if (mState.mRunSimpleAnimations) {
+ // Step 0: Find out where all non-removed items are, pre-layout
+ int count = mChildHelper.getChildCount();
+ for (int i = 0; i < count; ++i) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
+ continue;
+ }
+ final ItemHolderInfo animationInfo = mItemAnimator
+ .recordPreLayoutInformation(mState, holder,
+ ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
+ holder.getUnmodifiedPayloads());
+ mViewInfoStore.addToPreLayout(holder, animationInfo);
+ if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
+ && !holder.shouldIgnore() && !holder.isInvalid()) {
+ long key = getChangedHolderKey(holder);
+ // This is NOT the only place where a ViewHolder is added to old change holders
+ // list. There is another case where:
+ // * A VH is currently hidden but not deleted
+ // * The hidden item is changed in the adapter
+ // * Layout manager decides to layout the item in the pre-Layout pass (step1)
+ // When this case is detected, RV will un-hide that view and add to the old
+ // change holders list.
+ mViewInfoStore.addToOldChangeHolders(key, holder);
+ }
+ }
+ }
+ if (mState.mRunPredictiveAnimations) {
+ // Step 1: run prelayout: This will use the old positions of items. The layout manager
+ // is expected to layout everything, even removed items (though not to add removed
+ // items back to the container). This gives the pre-layout position of APPEARING views
+ // which come into existence as part of the real layout.
+
+ // Save old positions so that LayoutManager can run its mapping logic.
+ saveOldPositions();
+ final boolean didStructureChange = mState.mStructureChanged;
+ mState.mStructureChanged = false;
+ // temporarily disable flag because we are asking for previous layout
+ mLayout.onLayoutChildren(mRecycler, mState);
+ mState.mStructureChanged = didStructureChange;
+
+ for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
+ final View child = mChildHelper.getChildAt(i);
+ final ViewHolder viewHolder = getChildViewHolderInt(child);
+ if (viewHolder.shouldIgnore()) {
+ continue;
+ }
+ if (!mViewInfoStore.isInPreLayout(viewHolder)) {
+ int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
+ boolean wasHidden = viewHolder
+ .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
+ if (!wasHidden) {
+ flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
+ }
+ final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
+ mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
+ if (wasHidden) {
+ recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
+ } else {
+ mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
+ }
+ }
+ }
+ // we don't process disappearing list because they may re-appear in post layout pass.
+ clearOldPositions();
+ } else {
+ clearOldPositions();
+ }
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ mState.mLayoutStep = State.STEP_LAYOUT;
+ }
+
+ /**
+ * The second layout step where we do the actual layout of the views for the final state.
+ * This step might be run multiple times if necessary (e.g. measure).
+ */
+ private void dispatchLayoutStep2() {
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
+ mAdapterHelper.consumeUpdatesInOnePass();
+ mState.mItemCount = mAdapter.getItemCount();
+ mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
+
+ // Step 2: Run layout
+ mState.mInPreLayout = false;
+ mLayout.onLayoutChildren(mRecycler, mState);
+
+ mState.mStructureChanged = false;
+ mPendingSavedState = null;
+
+ // onLayoutChildren may have caused client code to disable item animations; re-check
+ mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
+ mState.mLayoutStep = State.STEP_ANIMATIONS;
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ }
+
+ /**
+ * The final step of the layout where we save the information about views for animations,
+ * trigger animations and do any necessary cleanup.
+ */
+ private void dispatchLayoutStep3() {
+ mState.assertLayoutStep(State.STEP_ANIMATIONS);
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ mState.mLayoutStep = State.STEP_START;
+ if (mState.mRunSimpleAnimations) {
+ // Step 3: Find out where things are now, and process change animations.
+ // traverse list in reverse because we may call animateChange in the loop which may
+ // remove the target view holder.
+ for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
+ ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder.shouldIgnore()) {
+ continue;
+ }
+ long key = getChangedHolderKey(holder);
+ final ItemHolderInfo animationInfo = mItemAnimator
+ .recordPostLayoutInformation(mState, holder);
+ ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
+ if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
+ // run a change animation
+
+ // If an Item is CHANGED but the updated version is disappearing, it creates
+ // a conflicting case.
+ // Since a view that is marked as disappearing is likely to be going out of
+ // bounds, we run a change animation. Both views will be cleaned automatically
+ // once their animations finish.
+ // On the other hand, if it is the same view holder instance, we run a
+ // disappearing animation instead because we are not going to rebind the updated
+ // VH unless it is enforced by the layout manager.
+ final boolean oldDisappearing = mViewInfoStore.isDisappearing(
+ oldChangeViewHolder);
+ final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
+ if (oldDisappearing && oldChangeViewHolder == holder) {
+ // run disappear animation instead of change
+ mViewInfoStore.addToPostLayout(holder, animationInfo);
+ } else {
+ final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
+ oldChangeViewHolder);
+ // we add and remove so that any post info is merged.
+ mViewInfoStore.addToPostLayout(holder, animationInfo);
+ ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
+ if (preInfo == null) {
+ handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
+ } else {
+ animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
+ oldDisappearing, newDisappearing);
+ }
+ }
+ } else {
+ mViewInfoStore.addToPostLayout(holder, animationInfo);
+ }
+ }
+
+ // Step 4: Process view info lists and trigger animations
+ mViewInfoStore.process(mViewInfoProcessCallback);
+ }
+
+ mLayout.removeAndRecycleScrapInt(mRecycler);
+ mState.mPreviousLayoutItemCount = mState.mItemCount;
+ mDataSetHasChangedAfterLayout = false;
+ mState.mRunSimpleAnimations = false;
+
+ mState.mRunPredictiveAnimations = false;
+ mLayout.mRequestedSimpleAnimations = false;
+ if (mRecycler.mChangedScrap != null) {
+ mRecycler.mChangedScrap.clear();
+ }
+ if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
+ // Initial prefetch has expanded cache, so reset until next prefetch.
+ // This prevents initial prefetches from expanding the cache permanently.
+ mLayout.mPrefetchMaxCountObserved = 0;
+ mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
+ mRecycler.updateViewCacheSize();
+ }
+
+ mLayout.onLayoutCompleted(mState);
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ mViewInfoStore.clear();
+ if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
+ dispatchOnScrolled(0, 0);
+ }
+ recoverFocusFromState();
+ resetFocusInfo();
+ }
+
+ /**
+ * This handles the case where there is an unexpected VH missing in the pre-layout map.
+ * <p>
+ * We might be able to detect the error in the application which will help the developer to
+ * resolve the issue.
+ * <p>
+ * If it is not an expected error, we at least print an error to notify the developer and ignore
+ * the animation.
+ *
+ * https://code.google.com/p/android/issues/detail?id=193958
+ *
+ * @param key The change key
+ * @param holder Current ViewHolder
+ * @param oldChangeViewHolder Changed ViewHolder
+ */
+ private void handleMissingPreInfoForChangeError(long key,
+ ViewHolder holder, ViewHolder oldChangeViewHolder) {
+ // check if two VH have the same key, if so, print that as an error
+ final int childCount = mChildHelper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mChildHelper.getChildAt(i);
+ ViewHolder other = getChildViewHolderInt(view);
+ if (other == holder) {
+ continue;
+ }
+ final long otherKey = getChangedHolderKey(other);
+ if (otherKey == key) {
+ if (mAdapter != null && mAdapter.hasStableIds()) {
+ throw new IllegalStateException("Two different ViewHolders have the same stable"
+ + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT"
+ + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder);
+ } else {
+ throw new IllegalStateException("Two different ViewHolders have the same change"
+ + " ID. This might happen due to inconsistent Adapter update events or"
+ + " if the LayoutManager lays out the same View multiple times."
+ + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder);
+ }
+ }
+ }
+ // Very unlikely to happen but if it does, notify the developer.
+ Log.e(TAG, "Problem while matching changed view holders with the new"
+ + "ones. The pre-layout information for the change holder " + oldChangeViewHolder
+ + " cannot be found but it is necessary for " + holder);
+ }
+
+ /**
+ * Records the animation information for a view holder that was bounced from hidden list. It
+ * also clears the bounce back flag.
+ */
+ void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder,
+ ItemHolderInfo animationInfo) {
+ // looks like this view bounced back from hidden list!
+ viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
+ if (mState.mTrackOldChangeHolders && viewHolder.isUpdated()
+ && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) {
+ long key = getChangedHolderKey(viewHolder);
+ mViewInfoStore.addToOldChangeHolders(key, viewHolder);
+ }
+ mViewInfoStore.addToPreLayout(viewHolder, animationInfo);
+ }
+
+ private void findMinMaxChildLayoutPositions(int[] into) {
+ final int count = mChildHelper.getChildCount();
+ if (count == 0) {
+ into[0] = NO_POSITION;
+ into[1] = NO_POSITION;
+ return;
+ }
+ int minPositionPreLayout = Integer.MAX_VALUE;
+ int maxPositionPreLayout = Integer.MIN_VALUE;
+ for (int i = 0; i < count; ++i) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder.shouldIgnore()) {
+ continue;
+ }
+ final int pos = holder.getLayoutPosition();
+ if (pos < minPositionPreLayout) {
+ minPositionPreLayout = pos;
+ }
+ if (pos > maxPositionPreLayout) {
+ maxPositionPreLayout = pos;
+ }
+ }
+ into[0] = minPositionPreLayout;
+ into[1] = maxPositionPreLayout;
+ }
+
+ private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) {
+ findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
+ return mMinMaxLayoutPositions[0] != minPositionPreLayout
+ || mMinMaxLayoutPositions[1] != maxPositionPreLayout;
+ }
+
+ @Override
+ protected void removeDetachedView(View child, boolean animate) {
+ ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ if (vh.isTmpDetached()) {
+ vh.clearTmpDetachFlag();
+ } else if (!vh.shouldIgnore()) {
+ throw new IllegalArgumentException("Called removeDetachedView with a view which"
+ + " is not flagged as tmp detached." + vh);
+ }
+ }
+ dispatchChildDetached(child);
+ super.removeDetachedView(child, animate);
+ }
+
+ /**
+ * Returns a unique key to be used while handling change animations.
+ * It might be child's position or stable id depending on the adapter type.
+ */
+ long getChangedHolderKey(ViewHolder holder) {
+ return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition;
+ }
+
+ void animateAppearance(@NonNull ViewHolder itemHolder,
+ @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
+ itemHolder.setIsRecyclable(false);
+ if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
+ postAnimationRunner();
+ }
+ }
+
+ void animateDisappearance(@NonNull ViewHolder holder,
+ @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
+ addAnimatingView(holder);
+ holder.setIsRecyclable(false);
+ if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
+ postAnimationRunner();
+ }
+ }
+
+ private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
+ @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
+ boolean oldHolderDisappearing, boolean newHolderDisappearing) {
+ oldHolder.setIsRecyclable(false);
+ if (oldHolderDisappearing) {
+ addAnimatingView(oldHolder);
+ }
+ if (oldHolder != newHolder) {
+ if (newHolderDisappearing) {
+ addAnimatingView(newHolder);
+ }
+ oldHolder.mShadowedHolder = newHolder;
+ // old holder should disappear after animation ends
+ addAnimatingView(oldHolder);
+ mRecycler.unscrapView(oldHolder);
+ newHolder.setIsRecyclable(false);
+ newHolder.mShadowingHolder = oldHolder;
+ }
+ if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
+ postAnimationRunner();
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ Trace.beginSection(TRACE_ON_LAYOUT_TAG);
+ dispatchLayout();
+ Trace.endSection();
+ mFirstLayoutComplete = true;
+ }
+
+ @Override
+ public void requestLayout() {
+ if (mEatRequestLayout == 0 && !mLayoutFrozen) {
+ super.requestLayout();
+ } else {
+ mLayoutRequestEaten = true;
+ }
+ }
+
+ void markItemDecorInsetsDirty() {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = mChildHelper.getUnfilteredChildAt(i);
+ ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
+ }
+ mRecycler.markItemDecorInsetsDirty();
+ }
+
+ @Override
+ public void draw(Canvas c) {
+ super.draw(c);
+
+ final int count = mItemDecorations.size();
+ for (int i = 0; i < count; i++) {
+ mItemDecorations.get(i).onDrawOver(c, this, mState);
+ }
+ // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
+ // need find children closest to edges. Not sure if it is worth the effort.
+ boolean needsInvalidate = false;
+ if (mLeftGlow != null && !mLeftGlow.isFinished()) {
+ final int restore = c.save();
+ final int padding = mClipToPadding ? getPaddingBottom() : 0;
+ c.rotate(270);
+ c.translate(-getHeight() + padding, 0);
+ needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
+ c.restoreToCount(restore);
+ }
+ if (mTopGlow != null && !mTopGlow.isFinished()) {
+ final int restore = c.save();
+ if (mClipToPadding) {
+ c.translate(getPaddingLeft(), getPaddingTop());
+ }
+ needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);
+ c.restoreToCount(restore);
+ }
+ if (mRightGlow != null && !mRightGlow.isFinished()) {
+ final int restore = c.save();
+ final int width = getWidth();
+ final int padding = mClipToPadding ? getPaddingTop() : 0;
+ c.rotate(90);
+ c.translate(-padding, -width);
+ needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
+ c.restoreToCount(restore);
+ }
+ if (mBottomGlow != null && !mBottomGlow.isFinished()) {
+ final int restore = c.save();
+ c.rotate(180);
+ if (mClipToPadding) {
+ c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom());
+ } else {
+ c.translate(-getWidth(), -getHeight());
+ }
+ needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);
+ c.restoreToCount(restore);
+ }
+
+ // If some views are animating, ItemDecorators are likely to move/change with them.
+ // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
+ // display lists are not invalidated.
+ if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
+ && mItemAnimator.isRunning()) {
+ needsInvalidate = true;
+ }
+
+ if (needsInvalidate) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ super.onDraw(c);
+
+ final int count = mItemDecorations.size();
+ for (int i = 0; i < count; i++) {
+ mItemDecorations.get(i).onDraw(c, this, mState);
+ }
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p);
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ if (mLayout == null) {
+ throw new IllegalStateException("RecyclerView has no LayoutManager");
+ }
+ return mLayout.generateDefaultLayoutParams();
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ if (mLayout == null) {
+ throw new IllegalStateException("RecyclerView has no LayoutManager");
+ }
+ return mLayout.generateLayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ if (mLayout == null) {
+ throw new IllegalStateException("RecyclerView has no LayoutManager");
+ }
+ return mLayout.generateLayoutParams(p);
+ }
+
+ /**
+ * Returns true if RecyclerView is currently running some animations.
+ * <p>
+ * If you want to be notified when animations are finished, use
+ * {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}.
+ *
+ * @return True if there are some item animations currently running or waiting to be started.
+ */
+ public boolean isAnimating() {
+ return mItemAnimator != null && mItemAnimator.isRunning();
+ }
+
+ void saveOldPositions() {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) {
+ throw new IllegalStateException("view holder cannot have position -1 unless it"
+ + " is removed");
+ }
+ if (!holder.shouldIgnore()) {
+ holder.saveOldPosition();
+ }
+ }
+ }
+
+ void clearOldPositions() {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (!holder.shouldIgnore()) {
+ holder.clearOldPosition();
+ }
+ }
+ mRecycler.clearOldPositions();
+ }
+
+ void offsetPositionRecordsForMove(int from, int to) {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ final int start, end, inBetweenOffset;
+ if (from < to) {
+ start = from;
+ end = to;
+ inBetweenOffset = -1;
+ } else {
+ start = to;
+ end = from;
+ inBetweenOffset = 1;
+ }
+
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder == null || holder.mPosition < start || holder.mPosition > end) {
+ continue;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder "
+ + holder);
+ }
+ if (holder.mPosition == from) {
+ holder.offsetPosition(to - from, false);
+ } else {
+ holder.offsetPosition(inBetweenOffset, false);
+ }
+
+ mState.mStructureChanged = true;
+ }
+ mRecycler.offsetPositionRecordsForMove(from, to);
+ requestLayout();
+ }
+
+ void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder "
+ + holder + " now at position " + (holder.mPosition + itemCount));
+ }
+ holder.offsetPosition(itemCount, false);
+ mState.mStructureChanged = true;
+ }
+ }
+ mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
+ requestLayout();
+ }
+
+ void offsetPositionRecordsForRemove(int positionStart, int itemCount,
+ boolean applyToPreLayout) {
+ final int positionEnd = positionStart + itemCount;
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.shouldIgnore()) {
+ if (holder.mPosition >= positionEnd) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
+ + " holder " + holder + " now at position "
+ + (holder.mPosition - itemCount));
+ }
+ holder.offsetPosition(-itemCount, applyToPreLayout);
+ mState.mStructureChanged = true;
+ } else if (holder.mPosition >= positionStart) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
+ + " holder " + holder + " now REMOVED");
+ }
+ holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount,
+ applyToPreLayout);
+ mState.mStructureChanged = true;
+ }
+ }
+ }
+ mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
+ requestLayout();
+ }
+
+ /**
+ * Rebind existing views for the given range, or create as needed.
+ *
+ * @param positionStart Adapter position to start at
+ * @param itemCount Number of views that must explicitly be rebound
+ */
+ void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ final int positionEnd = positionStart + itemCount;
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = mChildHelper.getUnfilteredChildAt(i);
+ final ViewHolder holder = getChildViewHolderInt(child);
+ if (holder == null || holder.shouldIgnore()) {
+ continue;
+ }
+ if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
+ // We re-bind these view holders after pre-processing is complete so that
+ // ViewHolders have their final positions assigned.
+ holder.addFlags(ViewHolder.FLAG_UPDATE);
+ holder.addChangePayload(payload);
+ // lp cannot be null since we get ViewHolder from it.
+ ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
+ }
+ }
+ mRecycler.viewRangeUpdate(positionStart, itemCount);
+ }
+
+ boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
+ return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
+ viewHolder.getUnmodifiedPayloads());
+ }
+
+
+ /**
+ * Call this method to signal that *all* adapter content has changed (generally, because of
+ * swapAdapter, or notifyDataSetChanged), and that once layout occurs, all attached items should
+ * be discarded or animated. Note that this work is deferred because RecyclerView requires a
+ * layout to resolve non-incremental changes to the data set.
+ *
+ * Attached items are labeled as position unknown, and may no longer be cached.
+ *
+ * It is still possible for items to be prefetched while mDataSetHasChangedAfterLayout == true,
+ * so calling this method *must* be associated with marking the cache invalid, so that the
+ * only valid items that remain in the cache, once layout occurs, are prefetched items.
+ */
+ void setDataSetChangedAfterLayout() {
+ if (mDataSetHasChangedAfterLayout) {
+ return;
+ }
+ mDataSetHasChangedAfterLayout = true;
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.shouldIgnore()) {
+ holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+ }
+ }
+ mRecycler.setAdapterPositionsAsUnknown();
+
+ // immediately mark all views as invalid, so prefetched views can be
+ // differentiated from views bound to previous data set - both in children, and cache
+ markKnownViewsInvalid();
+ }
+
+ /**
+ * Mark all known views as invalid. Used in response to a, "the whole world might have changed"
+ * data change event.
+ */
+ void markKnownViewsInvalid() {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.shouldIgnore()) {
+ holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+ }
+ }
+ markItemDecorInsetsDirty();
+ mRecycler.markKnownViewsInvalid();
+ }
+
+ /**
+ * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method
+ * will trigger a {@link #requestLayout()} call.
+ */
+ public void invalidateItemDecorations() {
+ if (mItemDecorations.size() == 0) {
+ return;
+ }
+ if (mLayout != null) {
+ mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll"
+ + " or layout");
+ }
+ markItemDecorInsetsDirty();
+ requestLayout();
+ }
+
+ /**
+ * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's
+ * focus even if the View representing the Item is replaced during a layout calculation.
+ * <p>
+ * By default, this value is {@code true}.
+ *
+ * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses
+ * focus.
+ *
+ * @see #setPreserveFocusAfterLayout(boolean)
+ */
+ public boolean getPreserveFocusAfterLayout() {
+ return mPreserveFocusAfterLayout;
+ }
+
+ /**
+ * Set whether the RecyclerView should try to keep the same Item focused after a layout
+ * calculation or not.
+ * <p>
+ * Usually, LayoutManagers keep focused views visible before and after layout but sometimes,
+ * views may lose focus during a layout calculation as their state changes or they are replaced
+ * with another view due to type change or animation. In these cases, RecyclerView can request
+ * focus on the new view automatically.
+ *
+ * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a
+ * layout calculations. Defaults to true.
+ *
+ * @see #getPreserveFocusAfterLayout()
+ */
+ public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) {
+ mPreserveFocusAfterLayout = preserveFocusAfterLayout;
+ }
+
+ /**
+ * Retrieve the {@link ViewHolder} for the given child view.
+ *
+ * @param child Child of this RecyclerView to query for its ViewHolder
+ * @return The child view's ViewHolder
+ */
+ public ViewHolder getChildViewHolder(View child) {
+ final ViewParent parent = child.getParent();
+ if (parent != null && parent != this) {
+ throw new IllegalArgumentException("View " + child + " is not a direct child of "
+ + this);
+ }
+ return getChildViewHolderInt(child);
+ }
+
+ /**
+ * Traverses the ancestors of the given view and returns the item view that contains it and
+ * also a direct child of the RecyclerView. This returned view can be used to get the
+ * ViewHolder by calling {@link #getChildViewHolder(View)}.
+ *
+ * @param view The view that is a descendant of the RecyclerView.
+ *
+ * @return The direct child of the RecyclerView which contains the given view or null if the
+ * provided view is not a descendant of this RecyclerView.
+ *
+ * @see #getChildViewHolder(View)
+ * @see #findContainingViewHolder(View)
+ */
+ @Nullable
+ public View findContainingItemView(View view) {
+ ViewParent parent = view.getParent();
+ while (parent != null && parent != this && parent instanceof View) {
+ view = (View) parent;
+ parent = view.getParent();
+ }
+ return parent == this ? view : null;
+ }
+
+ /**
+ * Returns the ViewHolder that contains the given view.
+ *
+ * @param view The view that is a descendant of the RecyclerView.
+ *
+ * @return The ViewHolder that contains the given view or null if the provided view is not a
+ * descendant of this RecyclerView.
+ */
+ @Nullable
+ public ViewHolder findContainingViewHolder(View view) {
+ View itemView = findContainingItemView(view);
+ return itemView == null ? null : getChildViewHolder(itemView);
+ }
+
+
+ static ViewHolder getChildViewHolderInt(View child) {
+ if (child == null) {
+ return null;
+ }
+ return ((LayoutParams) child.getLayoutParams()).mViewHolder;
+ }
+
+ /**
+ * @deprecated use {@link #getChildAdapterPosition(View)} or
+ * {@link #getChildLayoutPosition(View)}.
+ */
+ @Deprecated
+ public int getChildPosition(View child) {
+ return getChildAdapterPosition(child);
+ }
+
+ /**
+ * Return the adapter position that the given child view corresponds to.
+ *
+ * @param child Child View to query
+ * @return Adapter position corresponding to the given view or {@link #NO_POSITION}
+ */
+ public int getChildAdapterPosition(View child) {
+ final ViewHolder holder = getChildViewHolderInt(child);
+ return holder != null ? holder.getAdapterPosition() : NO_POSITION;
+ }
+
+ /**
+ * Return the adapter position of the given child view as of the latest completed layout pass.
+ * <p>
+ * This position may not be equal to Item's adapter position if there are pending changes
+ * in the adapter which have not been reflected to the layout yet.
+ *
+ * @param child Child View to query
+ * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if
+ * the View is representing a removed item.
+ */
+ public int getChildLayoutPosition(View child) {
+ final ViewHolder holder = getChildViewHolderInt(child);
+ return holder != null ? holder.getLayoutPosition() : NO_POSITION;
+ }
+
+ /**
+ * Return the stable item id that the given child view corresponds to.
+ *
+ * @param child Child View to query
+ * @return Item id corresponding to the given view or {@link #NO_ID}
+ */
+ public long getChildItemId(View child) {
+ if (mAdapter == null || !mAdapter.hasStableIds()) {
+ return NO_ID;
+ }
+ final ViewHolder holder = getChildViewHolderInt(child);
+ return holder != null ? holder.getItemId() : NO_ID;
+ }
+
+ /**
+ * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or
+ * {@link #findViewHolderForAdapterPosition(int)}
+ */
+ @Deprecated
+ public ViewHolder findViewHolderForPosition(int position) {
+ return findViewHolderForPosition(position, false);
+ }
+
+ /**
+ * Return the ViewHolder for the item in the given position of the data set as of the latest
+ * layout pass.
+ * <p>
+ * This method checks only the children of RecyclerView. If the item at the given
+ * <code>position</code> is not laid out, it <em>will not</em> create a new one.
+ * <p>
+ * Note that when Adapter contents change, ViewHolder positions are not updated until the
+ * next layout calculation. If there are pending adapter updates, the return value of this
+ * method may not match your adapter contents. You can use
+ * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder.
+ * <p>
+ * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
+ * with the same layout position representing the same Item. In this case, the updated
+ * ViewHolder will be returned.
+ *
+ * @param position The position of the item in the data set of the adapter
+ * @return The ViewHolder at <code>position</code> or null if there is no such item
+ */
+ public ViewHolder findViewHolderForLayoutPosition(int position) {
+ return findViewHolderForPosition(position, false);
+ }
+
+ /**
+ * Return the ViewHolder for the item in the given position of the data set. Unlike
+ * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending
+ * adapter changes that may not be reflected to the layout yet. On the other hand, if
+ * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been
+ * calculated yet, this method will return <code>null</code> since the new positions of views
+ * are unknown until the layout is calculated.
+ * <p>
+ * This method checks only the children of RecyclerView. If the item at the given
+ * <code>position</code> is not laid out, it <em>will not</em> create a new one.
+ * <p>
+ * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
+ * representing the same Item. In this case, the updated ViewHolder will be returned.
+ *
+ * @param position The position of the item in the data set of the adapter
+ * @return The ViewHolder at <code>position</code> or null if there is no such item
+ */
+ public ViewHolder findViewHolderForAdapterPosition(int position) {
+ if (mDataSetHasChangedAfterLayout) {
+ return null;
+ }
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ // hidden VHs are not preferred but if that is the only one we find, we rather return it
+ ViewHolder hidden = null;
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.isRemoved()
+ && getAdapterPositionFor(holder) == position) {
+ if (mChildHelper.isHidden(holder.itemView)) {
+ hidden = holder;
+ } else {
+ return holder;
+ }
+ }
+ }
+ return hidden;
+ }
+
+ ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ ViewHolder hidden = null;
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.isRemoved()) {
+ if (checkNewPosition) {
+ if (holder.mPosition != position) {
+ continue;
+ }
+ } else if (holder.getLayoutPosition() != position) {
+ continue;
+ }
+ if (mChildHelper.isHidden(holder.itemView)) {
+ hidden = holder;
+ } else {
+ return holder;
+ }
+ }
+ }
+ // This method should not query cached views. It creates a problem during adapter updates
+ // when we are dealing with already laid out views. Also, for the public method, it is more
+ // reasonable to return null if position is not laid out.
+ return hidden;
+ }
+
+ /**
+ * Return the ViewHolder for the item with the given id. The RecyclerView must
+ * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to
+ * return a non-null value.
+ * <p>
+ * This method checks only the children of RecyclerView. If the item with the given
+ * <code>id</code> is not laid out, it <em>will not</em> create a new one.
+ *
+ * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the
+ * same id. In this case, the updated ViewHolder will be returned.
+ *
+ * @param id The id for the requested item
+ * @return The ViewHolder with the given <code>id</code> or null if there is no such item
+ */
+ public ViewHolder findViewHolderForItemId(long id) {
+ if (mAdapter == null || !mAdapter.hasStableIds()) {
+ return null;
+ }
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ ViewHolder hidden = null;
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.isRemoved() && holder.getItemId() == id) {
+ if (mChildHelper.isHidden(holder.itemView)) {
+ hidden = holder;
+ } else {
+ return holder;
+ }
+ }
+ }
+ return hidden;
+ }
+
+ /**
+ * Find the topmost view under the given point.
+ *
+ * @param x Horizontal position in pixels to search
+ * @param y Vertical position in pixels to search
+ * @return The child view under (x, y) or null if no matching child is found
+ */
+ public View findChildViewUnder(float x, float y) {
+ final int count = mChildHelper.getChildCount();
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = mChildHelper.getChildAt(i);
+ final float translationX = child.getTranslationX();
+ final float translationY = child.getTranslationY();
+ if (x >= child.getLeft() + translationX
+ && x <= child.getRight() + translationX
+ && y >= child.getTop() + translationY
+ && y <= child.getBottom() + translationY) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ return super.drawChild(canvas, child, drawingTime);
+ }
+
+ /**
+ * Offset the bounds of all child views by <code>dy</code> pixels.
+ * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
+ *
+ * @param dy Vertical pixel offset to apply to the bounds of all child views
+ */
+ public void offsetChildrenVertical(int dy) {
+ final int childCount = mChildHelper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mChildHelper.getChildAt(i).offsetTopAndBottom(dy);
+ }
+ }
+
+ /**
+ * Called when an item view is attached to this RecyclerView.
+ *
+ * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
+ * of child views as they become attached. This will be called before a
+ * {@link LayoutManager} measures or lays out the view and is a good time to perform these
+ * changes.</p>
+ *
+ * @param child Child view that is now attached to this RecyclerView and its associated window
+ */
+ public void onChildAttachedToWindow(View child) {
+ }
+
+ /**
+ * Called when an item view is detached from this RecyclerView.
+ *
+ * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
+ * of child views as they become detached. This will be called as a
+ * {@link LayoutManager} fully detaches the child view from the parent and its window.</p>
+ *
+ * @param child Child view that is now detached from this RecyclerView and its associated window
+ */
+ public void onChildDetachedFromWindow(View child) {
+ }
+
+ /**
+ * Offset the bounds of all child views by <code>dx</code> pixels.
+ * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
+ *
+ * @param dx Horizontal pixel offset to apply to the bounds of all child views
+ */
+ public void offsetChildrenHorizontal(int dx) {
+ final int childCount = mChildHelper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mChildHelper.getChildAt(i).offsetLeftAndRight(dx);
+ }
+ }
+
+ /**
+ * Returns the bounds of the view including its decoration and margins.
+ *
+ * @param view The view element to check
+ * @param outBounds A rect that will receive the bounds of the element including its
+ * decoration and margins.
+ */
+ public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
+ getDecoratedBoundsWithMarginsInt(view, outBounds);
+ }
+
+ static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) {
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ final Rect insets = lp.mDecorInsets;
+ outBounds.set(view.getLeft() - insets.left - lp.leftMargin,
+ view.getTop() - insets.top - lp.topMargin,
+ view.getRight() + insets.right + lp.rightMargin,
+ view.getBottom() + insets.bottom + lp.bottomMargin);
+ }
+
+ Rect getItemDecorInsetsForChild(View child) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.mInsetsDirty) {
+ return lp.mDecorInsets;
+ }
+
+ if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
+ // changed/invalid items should not be updated until they are rebound.
+ return lp.mDecorInsets;
+ }
+ final Rect insets = lp.mDecorInsets;
+ insets.set(0, 0, 0, 0);
+ final int decorCount = mItemDecorations.size();
+ for (int i = 0; i < decorCount; i++) {
+ mTempRect.set(0, 0, 0, 0);
+ mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
+ insets.left += mTempRect.left;
+ insets.top += mTempRect.top;
+ insets.right += mTempRect.right;
+ insets.bottom += mTempRect.bottom;
+ }
+ lp.mInsetsDirty = false;
+ return insets;
+ }
+
+ /**
+ * Called when the scroll position of this RecyclerView changes. Subclasses should use
+ * this method to respond to scrolling within the adapter's data set instead of an explicit
+ * listener.
+ *
+ * <p>This method will always be invoked before listeners. If a subclass needs to perform
+ * any additional upkeep or bookkeeping after scrolling but before listeners run,
+ * this is a good place to do so.</p>
+ *
+ * <p>This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives
+ * the distance scrolled in either direction within the adapter's data set instead of absolute
+ * scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from
+ * any arbitrary point in the data set, <code>onScrollChanged</code> will always receive
+ * the current {@link View#getScrollX()} and {@link View#getScrollY()} values which
+ * do not correspond to the data set scroll position. However, some subclasses may choose
+ * to use these fields as special offsets.</p>
+ *
+ * @param dx horizontal distance scrolled in pixels
+ * @param dy vertical distance scrolled in pixels
+ */
+ public void onScrolled(int dx, int dy) {
+ // Do nothing
+ }
+
+ void dispatchOnScrolled(int hresult, int vresult) {
+ mDispatchScrollCounter++;
+ // Pass the current scrollX/scrollY values; no actual change in these properties occurred
+ // but some general-purpose code may choose to respond to changes this way.
+ final int scrollX = getScrollX();
+ final int scrollY = getScrollY();
+ onScrollChanged(scrollX, scrollY, scrollX, scrollY);
+
+ // Pass the real deltas to onScrolled, the RecyclerView-specific method.
+ onScrolled(hresult, vresult);
+
+ // Invoke listeners last. Subclassed view methods always handle the event first.
+ // All internal state is consistent by the time listeners are invoked.
+ if (mScrollListener != null) {
+ mScrollListener.onScrolled(this, hresult, vresult);
+ }
+ if (mScrollListeners != null) {
+ for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
+ mScrollListeners.get(i).onScrolled(this, hresult, vresult);
+ }
+ }
+ mDispatchScrollCounter--;
+ }
+
+ /**
+ * Called when the scroll state of this RecyclerView changes. Subclasses should use this
+ * method to respond to state changes instead of an explicit listener.
+ *
+ * <p>This method will always be invoked before listeners, but after the LayoutManager
+ * responds to the scroll state change.</p>
+ *
+ * @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE},
+ * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}
+ */
+ public void onScrollStateChanged(int state) {
+ // Do nothing
+ }
+
+ void dispatchOnScrollStateChanged(int state) {
+ // Let the LayoutManager go first; this allows it to bring any properties into
+ // a consistent state before the RecyclerView subclass responds.
+ if (mLayout != null) {
+ mLayout.onScrollStateChanged(state);
+ }
+
+ // Let the RecyclerView subclass handle this event next; any LayoutManager property
+ // changes will be reflected by this time.
+ onScrollStateChanged(state);
+
+ // Listeners go last. All other internal state is consistent by this point.
+ if (mScrollListener != null) {
+ mScrollListener.onScrollStateChanged(this, state);
+ }
+ if (mScrollListeners != null) {
+ for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
+ mScrollListeners.get(i).onScrollStateChanged(this, state);
+ }
+ }
+ }
+
+ /**
+ * Returns whether there are pending adapter updates which are not yet applied to the layout.
+ * <p>
+ * If this method returns <code>true</code>, it means that what user is currently seeing may not
+ * reflect them adapter contents (depending on what has changed).
+ * You may use this information to defer or cancel some operations.
+ * <p>
+ * This method returns true if RecyclerView has not yet calculated the first layout after it is
+ * attached to the Window or the Adapter has been replaced.
+ *
+ * @return True if there are some adapter updates which are not yet reflected to layout or false
+ * if layout is up to date.
+ */
+ public boolean hasPendingAdapterUpdates() {
+ return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout
+ || mAdapterHelper.hasPendingUpdates();
+ }
+
+ class ViewFlinger implements Runnable {
+ private int mLastFlingX;
+ private int mLastFlingY;
+ private OverScroller mScroller;
+ Interpolator mInterpolator = sQuinticInterpolator;
+
+
+ // When set to true, postOnAnimation callbacks are delayed until the run method completes
+ private boolean mEatRunOnAnimationRequest = false;
+
+ // Tracks if postAnimationCallback should be re-attached when it is done
+ private boolean mReSchedulePostAnimationCallback = false;
+
+ ViewFlinger() {
+ mScroller = new OverScroller(getContext(), sQuinticInterpolator);
+ }
+
+ @Override
+ public void run() {
+ if (mLayout == null) {
+ stop();
+ return; // no layout, cannot scroll.
+ }
+ disableRunOnAnimationRequests();
+ consumePendingUpdateOperations();
+ // keep a local reference so that if it is changed during onAnimation method, it won't
+ // cause unexpected behaviors
+ final OverScroller scroller = mScroller;
+ final SmoothScroller smoothScroller = mLayout.mSmoothScroller;
+ if (scroller.computeScrollOffset()) {
+ final int x = scroller.getCurrX();
+ final int y = scroller.getCurrY();
+ final int dx = x - mLastFlingX;
+ final int dy = y - mLastFlingY;
+ int hresult = 0;
+ int vresult = 0;
+ mLastFlingX = x;
+ mLastFlingY = y;
+ int overscrollX = 0, overscrollY = 0;
+ if (mAdapter != null) {
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ Trace.beginSection(TRACE_SCROLL_TAG);
+ if (dx != 0) {
+ hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
+ overscrollX = dx - hresult;
+ }
+ if (dy != 0) {
+ vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
+ overscrollY = dy - vresult;
+ }
+ Trace.endSection();
+ repositionShadowingViews();
+
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+
+ if (smoothScroller != null && !smoothScroller.isPendingInitialRun()
+ && smoothScroller.isRunning()) {
+ final int adapterSize = mState.getItemCount();
+ if (adapterSize == 0) {
+ smoothScroller.stop();
+ } else if (smoothScroller.getTargetPosition() >= adapterSize) {
+ smoothScroller.setTargetPosition(adapterSize - 1);
+ smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
+ } else {
+ smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
+ }
+ }
+ }
+ if (!mItemDecorations.isEmpty()) {
+ invalidate();
+ }
+ if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
+ considerReleasingGlowsOnScroll(dx, dy);
+ }
+ if (overscrollX != 0 || overscrollY != 0) {
+ final int vel = (int) scroller.getCurrVelocity();
+
+ int velX = 0;
+ if (overscrollX != x) {
+ velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0;
+ }
+
+ int velY = 0;
+ if (overscrollY != y) {
+ velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0;
+ }
+
+ if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
+ absorbGlows(velX, velY);
+ }
+ if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0)
+ && (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) {
+ scroller.abortAnimation();
+ }
+ }
+ if (hresult != 0 || vresult != 0) {
+ dispatchOnScrolled(hresult, vresult);
+ }
+
+ if (!awakenScrollBars()) {
+ invalidate();
+ }
+
+ final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically()
+ && vresult == dy;
+ final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally()
+ && hresult == dx;
+ final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal
+ || fullyConsumedVertical;
+
+ if (scroller.isFinished() || !fullyConsumedAny) {
+ setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
+ if (ALLOW_THREAD_GAP_WORK) {
+ mPrefetchRegistry.clearPrefetchPositions();
+ }
+ } else {
+ postOnAnimation();
+ if (mGapWorker != null) {
+ mGapWorker.postFromTraversal(RecyclerView.this, dx, dy);
+ }
+ }
+ }
+ // call this after the onAnimation is complete not to have inconsistent callbacks etc.
+ if (smoothScroller != null) {
+ if (smoothScroller.isPendingInitialRun()) {
+ smoothScroller.onAnimation(0, 0);
+ }
+ if (!mReSchedulePostAnimationCallback) {
+ smoothScroller.stop(); //stop if it does not trigger any scroll
+ }
+ }
+ enableRunOnAnimationRequests();
+ }
+
+ private void disableRunOnAnimationRequests() {
+ mReSchedulePostAnimationCallback = false;
+ mEatRunOnAnimationRequest = true;
+ }
+
+ private void enableRunOnAnimationRequests() {
+ mEatRunOnAnimationRequest = false;
+ if (mReSchedulePostAnimationCallback) {
+ postOnAnimation();
+ }
+ }
+
+ void postOnAnimation() {
+ if (mEatRunOnAnimationRequest) {
+ mReSchedulePostAnimationCallback = true;
+ } else {
+ removeCallbacks(this);
+ RecyclerView.this.postOnAnimation(this);
+ }
+ }
+
+ public void fling(int velocityX, int velocityY) {
+ setScrollState(SCROLL_STATE_SETTLING);
+ mLastFlingX = mLastFlingY = 0;
+ mScroller.fling(0, 0, velocityX, velocityY,
+ Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+ postOnAnimation();
+ }
+
+ public void smoothScrollBy(int dx, int dy) {
+ smoothScrollBy(dx, dy, 0, 0);
+ }
+
+ public void smoothScrollBy(int dx, int dy, int vx, int vy) {
+ smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy));
+ }
+
+ private float distanceInfluenceForSnapDuration(float f) {
+ f -= 0.5f; // center the values about 0.
+ f *= 0.3f * Math.PI / 2.0f;
+ return (float) Math.sin(f);
+ }
+
+ private int computeScrollDuration(int dx, int dy, int vx, int vy) {
+ final int absDx = Math.abs(dx);
+ final int absDy = Math.abs(dy);
+ final boolean horizontal = absDx > absDy;
+ final int velocity = (int) Math.sqrt(vx * vx + vy * vy);
+ final int delta = (int) Math.sqrt(dx * dx + dy * dy);
+ final int containerSize = horizontal ? getWidth() : getHeight();
+ final int halfContainerSize = containerSize / 2;
+ final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize);
+ final float distance = halfContainerSize + halfContainerSize
+ * distanceInfluenceForSnapDuration(distanceRatio);
+
+ final int duration;
+ if (velocity > 0) {
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+ } else {
+ float absDelta = (float) (horizontal ? absDx : absDy);
+ duration = (int) (((absDelta / containerSize) + 1) * 300);
+ }
+ return Math.min(duration, MAX_SCROLL_DURATION);
+ }
+
+ public void smoothScrollBy(int dx, int dy, int duration) {
+ smoothScrollBy(dx, dy, duration, sQuinticInterpolator);
+ }
+
+ public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
+ smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, 0, 0),
+ interpolator == null ? sQuinticInterpolator : interpolator);
+ }
+
+ public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {
+ if (mInterpolator != interpolator) {
+ mInterpolator = interpolator;
+ mScroller = new OverScroller(getContext(), interpolator);
+ }
+ setScrollState(SCROLL_STATE_SETTLING);
+ mLastFlingX = mLastFlingY = 0;
+ mScroller.startScroll(0, 0, dx, dy, duration);
+ postOnAnimation();
+ }
+
+ public void stop() {
+ removeCallbacks(this);
+ mScroller.abortAnimation();
+ }
+
+ }
+
+ void repositionShadowingViews() {
+ // Fix up shadow views used by change animations
+ int count = mChildHelper.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View view = mChildHelper.getChildAt(i);
+ ViewHolder holder = getChildViewHolder(view);
+ if (holder != null && holder.mShadowingHolder != null) {
+ View shadowingView = holder.mShadowingHolder.itemView;
+ int left = view.getLeft();
+ int top = view.getTop();
+ if (left != shadowingView.getLeft() || top != shadowingView.getTop()) {
+ shadowingView.layout(left, top,
+ left + shadowingView.getWidth(),
+ top + shadowingView.getHeight());
+ }
+ }
+ }
+ }
+
+ private class RecyclerViewDataObserver extends AdapterDataObserver {
+ RecyclerViewDataObserver() {
+ }
+
+ @Override
+ public void onChanged() {
+ assertNotInLayoutOrScroll(null);
+ mState.mStructureChanged = true;
+
+ setDataSetChangedAfterLayout();
+ if (!mAdapterHelper.hasPendingUpdates()) {
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ assertNotInLayoutOrScroll(null);
+ if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
+ triggerUpdateProcessor();
+ }
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ assertNotInLayoutOrScroll(null);
+ if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
+ triggerUpdateProcessor();
+ }
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ assertNotInLayoutOrScroll(null);
+ if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
+ triggerUpdateProcessor();
+ }
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ assertNotInLayoutOrScroll(null);
+ if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
+ triggerUpdateProcessor();
+ }
+ }
+
+ void triggerUpdateProcessor() {
+ if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
+ RecyclerView.this.postOnAnimation(mUpdateChildViewsRunnable);
+ } else {
+ mAdapterUpdateDuringMeasure = true;
+ requestLayout();
+ }
+ }
+ }
+
+ /**
+ * RecycledViewPool lets you share Views between multiple RecyclerViews.
+ * <p>
+ * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
+ * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
+ * <p>
+ * RecyclerView automatically creates a pool for itself if you don't provide one.
+ *
+ */
+ public static class RecycledViewPool {
+ private static final int DEFAULT_MAX_SCRAP = 5;
+
+ /**
+ * Tracks both pooled holders, as well as create/bind timing metadata for the given type.
+ *
+ * Note that this tracks running averages of create/bind time across all RecyclerViews
+ * (and, indirectly, Adapters) that use this pool.
+ *
+ * 1) This enables us to track average create and bind times across multiple adapters. Even
+ * though create (and especially bind) may behave differently for different Adapter
+ * subclasses, sharing the pool is a strong signal that they'll perform similarly, per type.
+ *
+ * 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return
+ * false for all other views of its type for the same deadline. This prevents items
+ * constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch.
+ */
+ static class ScrapData {
+ ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
+ int mMaxScrap = DEFAULT_MAX_SCRAP;
+ long mCreateRunningAverageNs = 0;
+ long mBindRunningAverageNs = 0;
+ }
+ SparseArray<ScrapData> mScrap = new SparseArray<>();
+
+ private int mAttachCount = 0;
+
+ public void clear() {
+ for (int i = 0; i < mScrap.size(); i++) {
+ ScrapData data = mScrap.valueAt(i);
+ data.mScrapHeap.clear();
+ }
+ }
+
+ public void setMaxRecycledViews(int viewType, int max) {
+ ScrapData scrapData = getScrapDataForType(viewType);
+ scrapData.mMaxScrap = max;
+ final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
+ if (scrapHeap != null) {
+ while (scrapHeap.size() > max) {
+ scrapHeap.remove(scrapHeap.size() - 1);
+ }
+ }
+ }
+
+ /**
+ * Returns the current number of Views held by the RecycledViewPool of the given view type.
+ */
+ public int getRecycledViewCount(int viewType) {
+ return getScrapDataForType(viewType).mScrapHeap.size();
+ }
+
+ public ViewHolder getRecycledView(int viewType) {
+ final ScrapData scrapData = mScrap.get(viewType);
+ if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
+ final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
+ return scrapHeap.remove(scrapHeap.size() - 1);
+ }
+ return null;
+ }
+
+ int size() {
+ int count = 0;
+ for (int i = 0; i < mScrap.size(); i++) {
+ ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i).mScrapHeap;
+ if (viewHolders != null) {
+ count += viewHolders.size();
+ }
+ }
+ return count;
+ }
+
+ public void putRecycledView(ViewHolder scrap) {
+ final int viewType = scrap.getItemViewType();
+ final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
+ if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
+ return;
+ }
+ if (DEBUG && scrapHeap.contains(scrap)) {
+ throw new IllegalArgumentException("this scrap item already exists");
+ }
+ scrap.resetInternal();
+ scrapHeap.add(scrap);
+ }
+
+ long runningAverage(long oldAverage, long newValue) {
+ if (oldAverage == 0) {
+ return newValue;
+ }
+ return (oldAverage / 4 * 3) + (newValue / 4);
+ }
+
+ void factorInCreateTime(int viewType, long createTimeNs) {
+ ScrapData scrapData = getScrapDataForType(viewType);
+ scrapData.mCreateRunningAverageNs = runningAverage(
+ scrapData.mCreateRunningAverageNs, createTimeNs);
+ }
+
+ void factorInBindTime(int viewType, long bindTimeNs) {
+ ScrapData scrapData = getScrapDataForType(viewType);
+ scrapData.mBindRunningAverageNs = runningAverage(
+ scrapData.mBindRunningAverageNs, bindTimeNs);
+ }
+
+ boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {
+ long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;
+ return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
+ }
+
+ boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) {
+ long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs;
+ return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
+ }
+
+ void attach(Adapter adapter) {
+ mAttachCount++;
+ }
+
+ void detach() {
+ mAttachCount--;
+ }
+
+
+ /**
+ * Detaches the old adapter and attaches the new one.
+ * <p>
+ * RecycledViewPool will clear its cache if it has only one adapter attached and the new
+ * adapter uses a different ViewHolder than the oldAdapter.
+ *
+ * @param oldAdapter The previous adapter instance. Will be detached.
+ * @param newAdapter The new adapter instance. Will be attached.
+ * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
+ * ViewHolder and view types.
+ */
+ void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
+ boolean compatibleWithPrevious) {
+ if (oldAdapter != null) {
+ detach();
+ }
+ if (!compatibleWithPrevious && mAttachCount == 0) {
+ clear();
+ }
+ if (newAdapter != null) {
+ attach(newAdapter);
+ }
+ }
+
+ private ScrapData getScrapDataForType(int viewType) {
+ ScrapData scrapData = mScrap.get(viewType);
+ if (scrapData == null) {
+ scrapData = new ScrapData();
+ mScrap.put(viewType, scrapData);
+ }
+ return scrapData;
+ }
+ }
+
+ /**
+ * Utility method for finding an internal RecyclerView, if present
+ */
+ @Nullable
+ static RecyclerView findNestedRecyclerView(@NonNull View view) {
+ if (!(view instanceof ViewGroup)) {
+ return null;
+ }
+ if (view instanceof RecyclerView) {
+ return (RecyclerView) view;
+ }
+ final ViewGroup parent = (ViewGroup) view;
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView descendant = findNestedRecyclerView(child);
+ if (descendant != null) {
+ return descendant;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Utility method for clearing holder's internal RecyclerView, if present
+ */
+ static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) {
+ if (holder.mNestedRecyclerView != null) {
+ View item = holder.mNestedRecyclerView.get();
+ while (item != null) {
+ if (item == holder.itemView) {
+ return; // match found, don't need to clear
+ }
+
+ ViewParent parent = item.getParent();
+ if (parent instanceof View) {
+ item = (View) parent;
+ } else {
+ item = null;
+ }
+ }
+ holder.mNestedRecyclerView = null; // not nested
+ }
+ }
+
+ /**
+ * Time base for deadline-aware work scheduling. Overridable for testing.
+ *
+ * Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling
+ * isn't relevant.
+ */
+ long getNanoTime() {
+ if (ALLOW_THREAD_GAP_WORK) {
+ return System.nanoTime();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * A Recycler is responsible for managing scrapped or detached item views for reuse.
+ *
+ * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
+ * that has been marked for removal or reuse.</p>
+ *
+ * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
+ * an adapter's data set representing the data at a given position or item ID.
+ * If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
+ * If not, the view can be quickly reused by the LayoutManager with no further work.
+ * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
+ * may be repositioned by a LayoutManager without remeasurement.</p>
+ */
+ public final class Recycler {
+ final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
+ ArrayList<ViewHolder> mChangedScrap = null;
+
+ final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
+
+ private final List<ViewHolder>
+ mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
+
+ private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
+ int mViewCacheMax = DEFAULT_CACHE_SIZE;
+
+ RecycledViewPool mRecyclerPool;
+
+ private ViewCacheExtension mViewCacheExtension;
+
+ static final int DEFAULT_CACHE_SIZE = 2;
+
+ /**
+ * Clear scrap views out of this recycler. Detached views contained within a
+ * recycled view pool will remain.
+ */
+ public void clear() {
+ mAttachedScrap.clear();
+ recycleAndClearCachedViews();
+ }
+
+ /**
+ * Set the maximum number of detached, valid views we should retain for later use.
+ *
+ * @param viewCount Number of views to keep before sending views to the shared pool
+ */
+ public void setViewCacheSize(int viewCount) {
+ mRequestedCacheMax = viewCount;
+ updateViewCacheSize();
+ }
+
+ void updateViewCacheSize() {
+ int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
+ mViewCacheMax = mRequestedCacheMax + extraCache;
+
+ // first, try the views that can be recycled
+ for (int i = mCachedViews.size() - 1;
+ i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
+ recycleCachedViewAt(i);
+ }
+ }
+
+ /**
+ * Returns an unmodifiable list of ViewHolders that are currently in the scrap list.
+ *
+ * @return List of ViewHolders in the scrap list.
+ */
+ public List<ViewHolder> getScrapList() {
+ return mUnmodifiableAttachedScrap;
+ }
+
+ /**
+ * Helper method for getViewForPosition.
+ * <p>
+ * Checks whether a given view holder can be used for the provided position.
+ *
+ * @param holder ViewHolder
+ * @return true if ViewHolder matches the provided position, false otherwise
+ */
+ boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
+ // if it is a removed holder, nothing to verify since we cannot ask adapter anymore
+ // if it is not removed, verify the type and id.
+ if (holder.isRemoved()) {
+ if (DEBUG && !mState.isPreLayout()) {
+ throw new IllegalStateException("should not receive a removed view unless it"
+ + " is pre layout");
+ }
+ return mState.isPreLayout();
+ }
+ if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
+ throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
+ + "adapter position" + holder);
+ }
+ if (!mState.isPreLayout()) {
+ // don't check type if it is pre-layout.
+ final int type = mAdapter.getItemViewType(holder.mPosition);
+ if (type != holder.getItemViewType()) {
+ return false;
+ }
+ }
+ if (mAdapter.hasStableIds()) {
+ return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
+ }
+ return true;
+ }
+
+ /**
+ * Attempts to bind view, and account for relevant timing information. If
+ * deadlineNs != FOREVER_NS, this method may fail to bind, and return false.
+ *
+ * @param holder Holder to be bound.
+ * @param offsetPosition Position of item to be bound.
+ * @param position Pre-layout position of item to be bound.
+ * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should
+ * complete. If FOREVER_NS is passed, this method will not fail to
+ * bind the holder.
+ * @return
+ */
+ private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
+ int position, long deadlineNs) {
+ holder.mOwnerRecyclerView = RecyclerView.this;
+ final int viewType = holder.getItemViewType();
+ long startBindNs = getNanoTime();
+ if (deadlineNs != FOREVER_NS
+ && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
+ // abort - we have a deadline we can't meet
+ return false;
+ }
+ mAdapter.bindViewHolder(holder, offsetPosition);
+ long endBindNs = getNanoTime();
+ mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
+ attachAccessibilityDelegate(holder.itemView);
+ if (mState.isPreLayout()) {
+ holder.mPreLayoutPosition = position;
+ }
+ return true;
+ }
+
+ /**
+ * Binds the given View to the position. The View can be a View previously retrieved via
+ * {@link #getViewForPosition(int)} or created by
+ * {@link Adapter#onCreateViewHolder(ViewGroup, int)}.
+ * <p>
+ * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)}
+ * and let the RecyclerView handle caching. This is a helper method for LayoutManager who
+ * wants to handle its own recycling logic.
+ * <p>
+ * Note that, {@link #getViewForPosition(int)} already binds the View to the position so
+ * you don't need to call this method unless you want to bind this View to another position.
+ *
+ * @param view The view to update.
+ * @param position The position of the item to bind to this View.
+ */
+ public void bindViewToPosition(View view, int position) {
+ ViewHolder holder = getChildViewHolderInt(view);
+ if (holder == null) {
+ throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot"
+ + " pass arbitrary views to this method, they should be created by the "
+ + "Adapter");
+ }
+ final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+ if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
+ throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ + "position " + position + "(offset:" + offsetPosition + ")."
+ + "state:" + mState.getItemCount());
+ }
+ tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS);
+
+ final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+ final LayoutParams rvLayoutParams;
+ if (lp == null) {
+ rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
+ holder.itemView.setLayoutParams(rvLayoutParams);
+ } else if (!checkLayoutParams(lp)) {
+ rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
+ holder.itemView.setLayoutParams(rvLayoutParams);
+ } else {
+ rvLayoutParams = (LayoutParams) lp;
+ }
+
+ rvLayoutParams.mInsetsDirty = true;
+ rvLayoutParams.mViewHolder = holder;
+ rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null;
+ }
+
+ /**
+ * RecyclerView provides artificial position range (item count) in pre-layout state and
+ * automatically maps these positions to {@link Adapter} positions when
+ * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called.
+ * <p>
+ * Usually, LayoutManager does not need to worry about this. However, in some cases, your
+ * LayoutManager may need to call some custom component with item positions in which
+ * case you need the actual adapter position instead of the pre layout position. You
+ * can use this method to convert a pre-layout position to adapter (post layout) position.
+ * <p>
+ * Note that if the provided position belongs to a deleted ViewHolder, this method will
+ * return -1.
+ * <p>
+ * Calling this method in post-layout state returns the same value back.
+ *
+ * @param position The pre-layout position to convert. Must be greater or equal to 0 and
+ * less than {@link State#getItemCount()}.
+ */
+ public int convertPreLayoutPositionToPostLayout(int position) {
+ if (position < 0 || position >= mState.getItemCount()) {
+ throw new IndexOutOfBoundsException("invalid position " + position + ". State "
+ + "item count is " + mState.getItemCount());
+ }
+ if (!mState.isPreLayout()) {
+ return position;
+ }
+ return mAdapterHelper.findPositionOffset(position);
+ }
+
+ /**
+ * Obtain a view initialized for the given position.
+ *
+ * This method should be used by {@link LayoutManager} implementations to obtain
+ * views to represent data from an {@link Adapter}.
+ * <p>
+ * The Recycler may reuse a scrap or detached view from a shared pool if one is
+ * available for the correct view type. If the adapter has not indicated that the
+ * data at the given position has changed, the Recycler will attempt to hand back
+ * a scrap view that was previously initialized for that data without rebinding.
+ *
+ * @param position Position to obtain a view for
+ * @return A view representing the data at <code>position</code> from <code>adapter</code>
+ */
+ public View getViewForPosition(int position) {
+ return getViewForPosition(position, false);
+ }
+
+ View getViewForPosition(int position, boolean dryRun) {
+ return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
+ }
+
+ /**
+ * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
+ * cache, the RecycledViewPool, or creating it directly.
+ * <p>
+ * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return
+ * rather than constructing or binding a ViewHolder if it doesn't think it has time.
+ * If a ViewHolder must be constructed and not enough time remains, null is returned. If a
+ * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is
+ * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this.
+ *
+ * @param position Position of ViewHolder to be returned.
+ * @param dryRun True if the ViewHolder should not be removed from scrap/cache/
+ * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should
+ * complete. If FOREVER_NS is passed, this method will not fail to
+ * create/bind the holder if needed.
+ *
+ * @return ViewHolder for requested position
+ */
+ @Nullable
+ ViewHolder tryGetViewHolderForPositionByDeadline(int position,
+ boolean dryRun, long deadlineNs) {
+ if (position < 0 || position >= mState.getItemCount()) {
+ throw new IndexOutOfBoundsException("Invalid item position " + position
+ + "(" + position + "). Item count:" + mState.getItemCount());
+ }
+ boolean fromScrapOrHiddenOrCache = false;
+ ViewHolder holder = null;
+ // 0) If there is a changed scrap, try to find from there
+ if (mState.isPreLayout()) {
+ holder = getChangedScrapViewForPosition(position);
+ fromScrapOrHiddenOrCache = holder != null;
+ }
+ // 1) Find by position from scrap/hidden list/cache
+ if (holder == null) {
+ holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
+ if (holder != null) {
+ if (!validateViewHolderForOffsetPosition(holder)) {
+ // recycle holder (and unscrap if relevant) since it can't be used
+ if (!dryRun) {
+ // we would like to recycle this but need to make sure it is not used by
+ // animation logic etc.
+ holder.addFlags(ViewHolder.FLAG_INVALID);
+ if (holder.isScrap()) {
+ removeDetachedView(holder.itemView, false);
+ holder.unScrap();
+ } else if (holder.wasReturnedFromScrap()) {
+ holder.clearReturnedFromScrapFlag();
+ }
+ recycleViewHolderInternal(holder);
+ }
+ holder = null;
+ } else {
+ fromScrapOrHiddenOrCache = true;
+ }
+ }
+ }
+ if (holder == null) {
+ final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+ if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
+ throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ + "position " + position + "(offset:" + offsetPosition + ")."
+ + "state:" + mState.getItemCount());
+ }
+
+ final int type = mAdapter.getItemViewType(offsetPosition);
+ // 2) Find from scrap/cache via stable ids, if exists
+ if (mAdapter.hasStableIds()) {
+ holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
+ type, dryRun);
+ if (holder != null) {
+ // update position
+ holder.mPosition = offsetPosition;
+ fromScrapOrHiddenOrCache = true;
+ }
+ }
+ if (holder == null && mViewCacheExtension != null) {
+ // We are NOT sending the offsetPosition because LayoutManager does not
+ // know it.
+ final View view = mViewCacheExtension
+ .getViewForPositionAndType(this, position, type);
+ if (view != null) {
+ holder = getChildViewHolder(view);
+ if (holder == null) {
+ throw new IllegalArgumentException("getViewForPositionAndType returned"
+ + " a view which does not have a ViewHolder");
+ } else if (holder.shouldIgnore()) {
+ throw new IllegalArgumentException("getViewForPositionAndType returned"
+ + " a view that is ignored. You must call stopIgnoring before"
+ + " returning this view.");
+ }
+ }
+ }
+ if (holder == null) { // fallback to pool
+ if (DEBUG) {
+ Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ + position + ") fetching from shared pool");
+ }
+ holder = getRecycledViewPool().getRecycledView(type);
+ if (holder != null) {
+ holder.resetInternal();
+ if (FORCE_INVALIDATE_DISPLAY_LIST) {
+ invalidateDisplayListInt(holder);
+ }
+ }
+ }
+ if (holder == null) {
+ long start = getNanoTime();
+ if (deadlineNs != FOREVER_NS
+ && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
+ // abort - we have a deadline we can't meet
+ return null;
+ }
+ holder = mAdapter.createViewHolder(RecyclerView.this, type);
+ if (ALLOW_THREAD_GAP_WORK) {
+ // only bother finding nested RV if prefetching
+ RecyclerView innerView = findNestedRecyclerView(holder.itemView);
+ if (innerView != null) {
+ holder.mNestedRecyclerView = new WeakReference<>(innerView);
+ }
+ }
+
+ long end = getNanoTime();
+ mRecyclerPool.factorInCreateTime(type, end - start);
+ if (DEBUG) {
+ Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
+ }
+ }
+ }
+
+ // This is very ugly but the only place we can grab this information
+ // before the View is rebound and returned to the LayoutManager for post layout ops.
+ // We don't need this in pre-layout since the VH is not updated by the LM.
+ if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
+ .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
+ holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
+ if (mState.mRunSimpleAnimations) {
+ int changeFlags = ItemAnimator
+ .buildAdapterChangeFlagsForAnimations(holder);
+ changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
+ final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
+ holder, changeFlags, holder.getUnmodifiedPayloads());
+ recordAnimationInfoIfBouncedHiddenView(holder, info);
+ }
+ }
+
+ boolean bound = false;
+ if (mState.isPreLayout() && holder.isBound()) {
+ // do not update unless we absolutely have to.
+ holder.mPreLayoutPosition = position;
+ } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
+ if (DEBUG && holder.isRemoved()) {
+ throw new IllegalStateException("Removed holder should be bound and it should"
+ + " come here only in pre-layout. Holder: " + holder);
+ }
+ final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+ bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
+ }
+
+ final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+ final LayoutParams rvLayoutParams;
+ if (lp == null) {
+ rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
+ holder.itemView.setLayoutParams(rvLayoutParams);
+ } else if (!checkLayoutParams(lp)) {
+ rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
+ holder.itemView.setLayoutParams(rvLayoutParams);
+ } else {
+ rvLayoutParams = (LayoutParams) lp;
+ }
+ rvLayoutParams.mViewHolder = holder;
+ rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
+ return holder;
+ }
+
+ private void attachAccessibilityDelegate(View itemView) {
+ if (isAccessibilityEnabled()) {
+ if (itemView.getImportantForAccessibility()
+ == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ itemView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ if (itemView.getAccessibilityDelegate() == null) {
+ itemView.setAccessibilityDelegate(mAccessibilityDelegate.getItemDelegate());
+ }
+ }
+ }
+
+ private void invalidateDisplayListInt(ViewHolder holder) {
+ if (holder.itemView instanceof ViewGroup) {
+ invalidateDisplayListInt((ViewGroup) holder.itemView, false);
+ }
+ }
+
+ private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) {
+ for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
+ final View view = viewGroup.getChildAt(i);
+ if (view instanceof ViewGroup) {
+ invalidateDisplayListInt((ViewGroup) view, true);
+ }
+ }
+ if (!invalidateThis) {
+ return;
+ }
+ // we need to force it to become invisible
+ if (viewGroup.getVisibility() == View.INVISIBLE) {
+ viewGroup.setVisibility(View.VISIBLE);
+ viewGroup.setVisibility(View.INVISIBLE);
+ } else {
+ final int visibility = viewGroup.getVisibility();
+ viewGroup.setVisibility(View.INVISIBLE);
+ viewGroup.setVisibility(visibility);
+ }
+ }
+
+ /**
+ * Recycle a detached view. The specified view will be added to a pool of views
+ * for later rebinding and reuse.
+ *
+ * <p>A view must be fully detached (removed from parent) before it may be recycled. If the
+ * View is scrapped, it will be removed from scrap list.</p>
+ *
+ * @param view Removed view for recycling
+ * @see LayoutManager#removeAndRecycleView(View, Recycler)
+ */
+ public void recycleView(View view) {
+ // This public recycle method tries to make view recycle-able since layout manager
+ // intended to recycle this view (e.g. even if it is in scrap or change cache)
+ ViewHolder holder = getChildViewHolderInt(view);
+ if (holder.isTmpDetached()) {
+ removeDetachedView(view, false);
+ }
+ if (holder.isScrap()) {
+ holder.unScrap();
+ } else if (holder.wasReturnedFromScrap()) {
+ holder.clearReturnedFromScrapFlag();
+ }
+ recycleViewHolderInternal(holder);
+ }
+
+ /**
+ * Internally, use this method instead of {@link #recycleView(android.view.View)} to
+ * catch potential bugs.
+ * @param view
+ */
+ void recycleViewInternal(View view) {
+ recycleViewHolderInternal(getChildViewHolderInt(view));
+ }
+
+ void recycleAndClearCachedViews() {
+ final int count = mCachedViews.size();
+ for (int i = count - 1; i >= 0; i--) {
+ recycleCachedViewAt(i);
+ }
+ mCachedViews.clear();
+ if (ALLOW_THREAD_GAP_WORK) {
+ mPrefetchRegistry.clearPrefetchPositions();
+ }
+ }
+
+ /**
+ * Recycles a cached view and removes the view from the list. Views are added to cache
+ * if and only if they are recyclable, so this method does not check it again.
+ * <p>
+ * A small exception to this rule is when the view does not have an animator reference
+ * but transient state is true (due to animations created outside ItemAnimator). In that
+ * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is
+ * still recyclable since Adapter wants to do so.
+ *
+ * @param cachedViewIndex The index of the view in cached views list
+ */
+ void recycleCachedViewAt(int cachedViewIndex) {
+ if (DEBUG) {
+ Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
+ }
+ ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
+ if (DEBUG) {
+ Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
+ }
+ addViewHolderToRecycledViewPool(viewHolder, true);
+ mCachedViews.remove(cachedViewIndex);
+ }
+
+ /**
+ * internal implementation checks if view is scrapped or attached and throws an exception
+ * if so.
+ * Public version un-scraps before calling recycle.
+ */
+ void recycleViewHolderInternal(ViewHolder holder) {
+ if (holder.isScrap() || holder.itemView.getParent() != null) {
+ throw new IllegalArgumentException(
+ "Scrapped or attached views may not be recycled. isScrap:"
+ + holder.isScrap() + " isAttached:"
+ + (holder.itemView.getParent() != null));
+ }
+
+ if (holder.isTmpDetached()) {
+ throw new IllegalArgumentException("Tmp detached view should be removed "
+ + "from RecyclerView before it can be recycled: " + holder);
+ }
+
+ if (holder.shouldIgnore()) {
+ throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
+ + " should first call stopIgnoringView(view) before calling recycle.");
+ }
+ //noinspection unchecked
+ final boolean transientStatePreventsRecycling = holder
+ .doesTransientStatePreventRecycling();
+ final boolean forceRecycle = mAdapter != null
+ && transientStatePreventsRecycling
+ && mAdapter.onFailedToRecycleView(holder);
+ boolean cached = false;
+ boolean recycled = false;
+ if (DEBUG && mCachedViews.contains(holder)) {
+ throw new IllegalArgumentException("cached view received recycle internal? "
+ + holder);
+ }
+ if (forceRecycle || holder.isRecyclable()) {
+ if (mViewCacheMax > 0
+ && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
+ | ViewHolder.FLAG_REMOVED
+ | ViewHolder.FLAG_UPDATE
+ | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
+ // Retire oldest cached view
+ int cachedViewSize = mCachedViews.size();
+ if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
+ recycleCachedViewAt(0);
+ cachedViewSize--;
+ }
+
+ int targetCacheIndex = cachedViewSize;
+ if (ALLOW_THREAD_GAP_WORK
+ && cachedViewSize > 0
+ && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
+ // when adding the view, skip past most recently prefetched views
+ int cacheIndex = cachedViewSize - 1;
+ while (cacheIndex >= 0) {
+ int cachedPos = mCachedViews.get(cacheIndex).mPosition;
+ if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
+ break;
+ }
+ cacheIndex--;
+ }
+ targetCacheIndex = cacheIndex + 1;
+ }
+ mCachedViews.add(targetCacheIndex, holder);
+ cached = true;
+ }
+ if (!cached) {
+ addViewHolderToRecycledViewPool(holder, true);
+ recycled = true;
+ }
+ } else {
+ // NOTE: A view can fail to be recycled when it is scrolled off while an animation
+ // runs. In this case, the item is eventually recycled by
+ // ItemAnimatorRestoreListener#onAnimationFinished.
+
+ // TODO: consider cancelling an animation when an item is removed scrollBy,
+ // to return it to the pool faster
+ if (DEBUG) {
+ Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ + "re-visit here. We are still removing it from animation lists");
+ }
+ }
+ // even if the holder is not removed, we still call this method so that it is removed
+ // from view holder lists.
+ mViewInfoStore.removeViewHolder(holder);
+ if (!cached && !recycled && transientStatePreventsRecycling) {
+ holder.mOwnerRecyclerView = null;
+ }
+ }
+
+ /**
+ * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool.
+ *
+ * Pass false to dispatchRecycled for views that have not been bound.
+ *
+ * @param holder Holder to be added to the pool.
+ * @param dispatchRecycled True to dispatch View recycled callbacks.
+ */
+ void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
+ clearNestedRecyclerViewIfNotNested(holder);
+ holder.itemView.setAccessibilityDelegate(null);
+ if (dispatchRecycled) {
+ dispatchViewRecycled(holder);
+ }
+ holder.mOwnerRecyclerView = null;
+ getRecycledViewPool().putRecycledView(holder);
+ }
+
+ /**
+ * Used as a fast path for unscrapping and recycling a view during a bulk operation.
+ * The caller must call {@link #clearScrap()} when it's done to update the recycler's
+ * internal bookkeeping.
+ */
+ void quickRecycleScrapView(View view) {
+ final ViewHolder holder = getChildViewHolderInt(view);
+ holder.mScrapContainer = null;
+ holder.mInChangeScrap = false;
+ holder.clearReturnedFromScrapFlag();
+ recycleViewHolderInternal(holder);
+ }
+
+ /**
+ * Mark an attached view as scrap.
+ *
+ * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
+ * for rebinding and reuse. Requests for a view for a given position may return a
+ * reused or rebound scrap view instance.</p>
+ *
+ * @param view View to scrap
+ */
+ void scrapView(View view) {
+ final ViewHolder holder = getChildViewHolderInt(view);
+ if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
+ || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
+ if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
+ throw new IllegalArgumentException("Called scrap view with an invalid view."
+ + " Invalid views cannot be reused from scrap, they should rebound from"
+ + " recycler pool.");
+ }
+ holder.setScrapContainer(this, false);
+ mAttachedScrap.add(holder);
+ } else {
+ if (mChangedScrap == null) {
+ mChangedScrap = new ArrayList<ViewHolder>();
+ }
+ holder.setScrapContainer(this, true);
+ mChangedScrap.add(holder);
+ }
+ }
+
+ /**
+ * Remove a previously scrapped view from the pool of eligible scrap.
+ *
+ * <p>This view will no longer be eligible for reuse until re-scrapped or
+ * until it is explicitly removed and recycled.</p>
+ */
+ void unscrapView(ViewHolder holder) {
+ if (holder.mInChangeScrap) {
+ mChangedScrap.remove(holder);
+ } else {
+ mAttachedScrap.remove(holder);
+ }
+ holder.mScrapContainer = null;
+ holder.mInChangeScrap = false;
+ holder.clearReturnedFromScrapFlag();
+ }
+
+ int getScrapCount() {
+ return mAttachedScrap.size();
+ }
+
+ View getScrapViewAt(int index) {
+ return mAttachedScrap.get(index).itemView;
+ }
+
+ void clearScrap() {
+ mAttachedScrap.clear();
+ if (mChangedScrap != null) {
+ mChangedScrap.clear();
+ }
+ }
+
+ ViewHolder getChangedScrapViewForPosition(int position) {
+ // If pre-layout, check the changed scrap for an exact match.
+ final int changedScrapSize;
+ if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
+ return null;
+ }
+ // find by position
+ for (int i = 0; i < changedScrapSize; i++) {
+ final ViewHolder holder = mChangedScrap.get(i);
+ if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
+ holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+ return holder;
+ }
+ }
+ // find by id
+ if (mAdapter.hasStableIds()) {
+ final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+ if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
+ final long id = mAdapter.getItemId(offsetPosition);
+ for (int i = 0; i < changedScrapSize; i++) {
+ final ViewHolder holder = mChangedScrap.get(i);
+ if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
+ holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+ return holder;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a view for the position either from attach scrap, hidden children, or cache.
+ *
+ * @param position Item position
+ * @param dryRun Does a dry run, finds the ViewHolder but does not remove
+ * @return a ViewHolder that can be re-used for this position.
+ */
+ ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
+ final int scrapCount = mAttachedScrap.size();
+
+ // Try first for an exact, non-invalid match from scrap.
+ for (int i = 0; i < scrapCount; i++) {
+ final ViewHolder holder = mAttachedScrap.get(i);
+ if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
+ && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
+ holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+ return holder;
+ }
+ }
+
+ if (!dryRun) {
+ View view = mChildHelper.findHiddenNonRemovedView(position);
+ if (view != null) {
+ // This View is good to be used. We just need to unhide, detach and move to the
+ // scrap list.
+ final ViewHolder vh = getChildViewHolderInt(view);
+ mChildHelper.unhide(view);
+ int layoutIndex = mChildHelper.indexOfChild(view);
+ if (layoutIndex == RecyclerView.NO_POSITION) {
+ throw new IllegalStateException("layout index should not be -1 after "
+ + "unhiding a view:" + vh);
+ }
+ mChildHelper.detachViewFromParent(layoutIndex);
+ scrapView(view);
+ vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
+ | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
+ return vh;
+ }
+ }
+
+ // Search in our first-level recycled view cache.
+ final int cacheSize = mCachedViews.size();
+ for (int i = 0; i < cacheSize; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ // invalid view holders may be in cache if adapter has stable ids as they can be
+ // retrieved via getScrapOrCachedViewForId
+ if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
+ if (!dryRun) {
+ mCachedViews.remove(i);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ + ") found match in cache: " + holder);
+ }
+ return holder;
+ }
+ }
+ return null;
+ }
+
+ ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
+ // Look in our attached views first
+ final int count = mAttachedScrap.size();
+ for (int i = count - 1; i >= 0; i--) {
+ final ViewHolder holder = mAttachedScrap.get(i);
+ if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
+ if (type == holder.getItemViewType()) {
+ holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+ if (holder.isRemoved()) {
+ // this might be valid in two cases:
+ // > item is removed but we are in pre-layout pass
+ // >> do nothing. return as is. make sure we don't rebind
+ // > item is removed then added to another position and we are in
+ // post layout.
+ // >> remove removed and invalid flags, add update flag to rebind
+ // because item was invisible to us and we don't know what happened in
+ // between.
+ if (!mState.isPreLayout()) {
+ holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
+ | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
+ }
+ }
+ return holder;
+ } else if (!dryRun) {
+ // if we are running animations, it is actually better to keep it in scrap
+ // but this would force layout manager to lay it out which would be bad.
+ // Recycle this scrap. Type mismatch.
+ mAttachedScrap.remove(i);
+ removeDetachedView(holder.itemView, false);
+ quickRecycleScrapView(holder.itemView);
+ }
+ }
+ }
+
+ // Search the first-level cache
+ final int cacheSize = mCachedViews.size();
+ for (int i = cacheSize - 1; i >= 0; i--) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder.getItemId() == id) {
+ if (type == holder.getItemViewType()) {
+ if (!dryRun) {
+ mCachedViews.remove(i);
+ }
+ return holder;
+ } else if (!dryRun) {
+ recycleCachedViewAt(i);
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ void dispatchViewRecycled(ViewHolder holder) {
+ if (mRecyclerListener != null) {
+ mRecyclerListener.onViewRecycled(holder);
+ }
+ if (mAdapter != null) {
+ mAdapter.onViewRecycled(holder);
+ }
+ if (mState != null) {
+ mViewInfoStore.removeViewHolder(holder);
+ }
+ if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
+ }
+
+ void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
+ boolean compatibleWithPrevious) {
+ clear();
+ getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
+ }
+
+ void offsetPositionRecordsForMove(int from, int to) {
+ final int start, end, inBetweenOffset;
+ if (from < to) {
+ start = from;
+ end = to;
+ inBetweenOffset = -1;
+ } else {
+ start = to;
+ end = from;
+ inBetweenOffset = 1;
+ }
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder == null || holder.mPosition < start || holder.mPosition > end) {
+ continue;
+ }
+ if (holder.mPosition == from) {
+ holder.offsetPosition(to - from, false);
+ } else {
+ holder.offsetPosition(inBetweenOffset, false);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder "
+ + holder);
+ }
+ }
+ }
+
+ void offsetPositionRecordsForInsert(int insertedAt, int count) {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null && holder.mPosition >= insertedAt) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder "
+ + holder + " now at position " + (holder.mPosition + count));
+ }
+ holder.offsetPosition(count, true);
+ }
+ }
+ }
+
+ /**
+ * @param removedFrom Remove start index
+ * @param count Remove count
+ * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if
+ * false, they'll be applied before the second layout pass
+ */
+ void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) {
+ final int removedEnd = removedFrom + count;
+ final int cachedCount = mCachedViews.size();
+ for (int i = cachedCount - 1; i >= 0; i--) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null) {
+ if (holder.mPosition >= removedEnd) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForRemove cached " + i
+ + " holder " + holder + " now at position "
+ + (holder.mPosition - count));
+ }
+ holder.offsetPosition(-count, applyToPreLayout);
+ } else if (holder.mPosition >= removedFrom) {
+ // Item for this view was removed. Dump it from the cache.
+ holder.addFlags(ViewHolder.FLAG_REMOVED);
+ recycleCachedViewAt(i);
+ }
+ }
+ }
+ }
+
+ void setViewCacheExtension(ViewCacheExtension extension) {
+ mViewCacheExtension = extension;
+ }
+
+ void setRecycledViewPool(RecycledViewPool pool) {
+ if (mRecyclerPool != null) {
+ mRecyclerPool.detach();
+ }
+ mRecyclerPool = pool;
+ if (pool != null) {
+ mRecyclerPool.attach(getAdapter());
+ }
+ }
+
+ RecycledViewPool getRecycledViewPool() {
+ if (mRecyclerPool == null) {
+ mRecyclerPool = new RecycledViewPool();
+ }
+ return mRecyclerPool;
+ }
+
+ void viewRangeUpdate(int positionStart, int itemCount) {
+ final int positionEnd = positionStart + itemCount;
+ final int cachedCount = mCachedViews.size();
+ for (int i = cachedCount - 1; i >= 0; i--) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder == null) {
+ continue;
+ }
+
+ final int pos = holder.getLayoutPosition();
+ if (pos >= positionStart && pos < positionEnd) {
+ holder.addFlags(ViewHolder.FLAG_UPDATE);
+ recycleCachedViewAt(i);
+ // cached views should not be flagged as changed because this will cause them
+ // to animate when they are returned from cache.
+ }
+ }
+ }
+
+ void setAdapterPositionsAsUnknown() {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null) {
+ holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+ }
+ }
+ }
+
+ void markKnownViewsInvalid() {
+ if (mAdapter != null && mAdapter.hasStableIds()) {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null) {
+ holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+ holder.addChangePayload(null);
+ }
+ }
+ } else {
+ // we cannot re-use cached views in this case. Recycle them all
+ recycleAndClearCachedViews();
+ }
+ }
+
+ void clearOldPositions() {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ holder.clearOldPosition();
+ }
+ final int scrapCount = mAttachedScrap.size();
+ for (int i = 0; i < scrapCount; i++) {
+ mAttachedScrap.get(i).clearOldPosition();
+ }
+ if (mChangedScrap != null) {
+ final int changedScrapCount = mChangedScrap.size();
+ for (int i = 0; i < changedScrapCount; i++) {
+ mChangedScrap.get(i).clearOldPosition();
+ }
+ }
+ }
+
+ void markItemDecorInsetsDirty() {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
+ if (layoutParams != null) {
+ layoutParams.mInsetsDirty = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * ViewCacheExtension is a helper class to provide an additional layer of view caching that can
+ * be controlled by the developer.
+ * <p>
+ * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
+ * first level cache to find a matching View. If it cannot find a suitable View, Recycler will
+ * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
+ * {@link RecycledViewPool}.
+ * <p>
+ * Note that, Recycler never sends Views to this method to be cached. It is developers
+ * responsibility to decide whether they want to keep their Views in this custom cache or let
+ * the default recycling policy handle it.
+ */
+ public abstract static class ViewCacheExtension {
+
+ /**
+ * Returns a View that can be binded to the given Adapter position.
+ * <p>
+ * This method should <b>not</b> create a new View. Instead, it is expected to return
+ * an already created View that can be re-used for the given type and position.
+ * If the View is marked as ignored, it should first call
+ * {@link LayoutManager#stopIgnoringView(View)} before returning the View.
+ * <p>
+ * RecyclerView will re-bind the returned View to the position if necessary.
+ *
+ * @param recycler The Recycler that can be used to bind the View
+ * @param position The adapter position
+ * @param type The type of the View, defined by adapter
+ * @return A View that is bound to the given position or NULL if there is no View to re-use
+ * @see LayoutManager#ignoreView(View)
+ */
+ public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
+ }
+
+ /**
+ * Base class for an Adapter
+ *
+ * <p>Adapters provide a binding from an app-specific data set to views that are displayed
+ * within a {@link RecyclerView}.</p>
+ *
+ * @param <VH> A class that extends ViewHolder that will be used by the adapter.
+ */
+ public abstract static class Adapter<VH extends ViewHolder> {
+ private final AdapterDataObservable mObservable = new AdapterDataObservable();
+ private boolean mHasStableIds = false;
+
+ /**
+ * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
+ * an item.
+ * <p>
+ * This new ViewHolder should be constructed with a new View that can represent the items
+ * of the given type. You can either create a new View manually or inflate it from an XML
+ * layout file.
+ * <p>
+ * The new ViewHolder will be used to display items of the adapter using
+ * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
+ * different items in the data set, it is a good idea to cache references to sub views of
+ * the View to avoid unnecessary {@link View#findViewById(int)} calls.
+ *
+ * @param parent The ViewGroup into which the new View will be added after it is bound to
+ * an adapter position.
+ * @param viewType The view type of the new View.
+ *
+ * @return A new ViewHolder that holds a View of the given view type.
+ * @see #getItemViewType(int)
+ * @see #onBindViewHolder(ViewHolder, int)
+ */
+ public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
+
+ /**
+ * Called by RecyclerView to display the data at the specified position. This method should
+ * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
+ * position.
+ * <p>
+ * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
+ * again if the position of the item changes in the data set unless the item itself is
+ * invalidated or the new position cannot be determined. For this reason, you should only
+ * use the <code>position</code> parameter while acquiring the related data item inside
+ * this method and should not keep a copy of it. If you need the position of an item later
+ * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
+ * have the updated adapter position.
+ *
+ * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
+ * handle efficient partial bind.
+ *
+ * @param holder The ViewHolder which should be updated to represent the contents of the
+ * item at the given position in the data set.
+ * @param position The position of the item within the adapter's data set.
+ */
+ public abstract void onBindViewHolder(VH holder, int position);
+
+ /**
+ * Called by RecyclerView to display the data at the specified position. This method
+ * should update the contents of the {@link ViewHolder#itemView} to reflect the item at
+ * the given position.
+ * <p>
+ * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
+ * again if the position of the item changes in the data set unless the item itself is
+ * invalidated or the new position cannot be determined. For this reason, you should only
+ * use the <code>position</code> parameter while acquiring the related data item inside
+ * this method and should not keep a copy of it. If you need the position of an item later
+ * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
+ * have the updated adapter position.
+ * <p>
+ * Partial bind vs full bind:
+ * <p>
+ * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or
+ * {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty,
+ * the ViewHolder is currently bound to old data and Adapter may run an efficient partial
+ * update using the payload info. If the payload is empty, Adapter must run a full bind.
+ * Adapter should not assume that the payload passed in notify methods will be received by
+ * onBindViewHolder(). For example when the view is not attached to the screen, the
+ * payload in notifyItemChange() will be simply dropped.
+ *
+ * @param holder The ViewHolder which should be updated to represent the contents of the
+ * item at the given position in the data set.
+ * @param position The position of the item within the adapter's data set.
+ * @param payloads A non-null list of merged payloads. Can be empty list if requires full
+ * update.
+ */
+ public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
+ onBindViewHolder(holder, position);
+ }
+
+ /**
+ * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new
+ * {@link ViewHolder} and initializes some private fields to be used by RecyclerView.
+ *
+ * @see #onCreateViewHolder(ViewGroup, int)
+ */
+ public final VH createViewHolder(ViewGroup parent, int viewType) {
+ Trace.beginSection(TRACE_CREATE_VIEW_TAG);
+ final VH holder = onCreateViewHolder(parent, viewType);
+ holder.mItemViewType = viewType;
+ Trace.endSection();
+ return holder;
+ }
+
+ /**
+ * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the
+ * {@link ViewHolder} contents with the item at the given position and also sets up some
+ * private fields to be used by RecyclerView.
+ *
+ * @see #onBindViewHolder(ViewHolder, int)
+ */
+ public final void bindViewHolder(VH holder, int position) {
+ holder.mPosition = position;
+ if (hasStableIds()) {
+ holder.mItemId = getItemId(position);
+ }
+ holder.setFlags(ViewHolder.FLAG_BOUND,
+ ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
+ | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+ Trace.beginSection(TRACE_BIND_VIEW_TAG);
+ onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
+ holder.clearPayload();
+ final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
+ if (layoutParams instanceof RecyclerView.LayoutParams) {
+ ((LayoutParams) layoutParams).mInsetsDirty = true;
+ }
+ Trace.endSection();
+ }
+
+ /**
+ * Return the view type of the item at <code>position</code> for the purposes
+ * of view recycling.
+ *
+ * <p>The default implementation of this method returns 0, making the assumption of
+ * a single view type for the adapter. Unlike ListView adapters, types need not
+ * be contiguous. Consider using id resources to uniquely identify item view types.
+ *
+ * @param position position to query
+ * @return integer value identifying the type of the view needed to represent the item at
+ * <code>position</code>. Type codes need not be contiguous.
+ */
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ /**
+ * Indicates whether each item in the data set can be represented with a unique identifier
+ * of type {@link java.lang.Long}.
+ *
+ * @param hasStableIds Whether items in data set have unique identifiers or not.
+ * @see #hasStableIds()
+ * @see #getItemId(int)
+ */
+ public void setHasStableIds(boolean hasStableIds) {
+ if (hasObservers()) {
+ throw new IllegalStateException("Cannot change whether this adapter has "
+ + "stable IDs while the adapter has registered observers.");
+ }
+ mHasStableIds = hasStableIds;
+ }
+
+ /**
+ * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()}
+ * would return false this method should return {@link #NO_ID}. The default implementation
+ * of this method returns {@link #NO_ID}.
+ *
+ * @param position Adapter position to query
+ * @return the stable ID of the item at position
+ */
+ public long getItemId(int position) {
+ return NO_ID;
+ }
+
+ /**
+ * Returns the total number of items in the data set held by the adapter.
+ *
+ * @return The total number of items in this adapter.
+ */
+ public abstract int getItemCount();
+
+ /**
+ * Returns true if this adapter publishes a unique <code>long</code> value that can
+ * act as a key for the item at a given position in the data set. If that item is relocated
+ * in the data set, the ID returned for that item should be the same.
+ *
+ * @return true if this adapter's items have stable IDs
+ */
+ public final boolean hasStableIds() {
+ return mHasStableIds;
+ }
+
+ /**
+ * Called when a view created by this adapter has been recycled.
+ *
+ * <p>A view is recycled when a {@link LayoutManager} decides that it no longer
+ * needs to be attached to its parent {@link RecyclerView}. This can be because it has
+ * fallen out of visibility or a set of cached views represented by views still
+ * attached to the parent RecyclerView. If an item view has large or expensive data
+ * bound to it such as large bitmaps, this may be a good place to release those
+ * resources.</p>
+ * <p>
+ * RecyclerView calls this method right before clearing ViewHolder's internal data and
+ * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
+ * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
+ * its adapter position.
+ *
+ * @param holder The ViewHolder for the view being recycled
+ */
+ public void onViewRecycled(VH holder) {
+ }
+
+ /**
+ * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled
+ * due to its transient state. Upon receiving this callback, Adapter can clear the
+ * animation(s) that effect the View's transient state and return <code>true</code> so that
+ * the View can be recycled. Keep in mind that the View in question is already removed from
+ * the RecyclerView.
+ * <p>
+ * In some cases, it is acceptable to recycle a View although it has transient state. Most
+ * of the time, this is a case where the transient state will be cleared in
+ * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position.
+ * For this reason, RecyclerView leaves the decision to the Adapter and uses the return
+ * value of this method to decide whether the View should be recycled or not.
+ * <p>
+ * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you
+ * should never receive this callback because RecyclerView keeps those Views as children
+ * until their animations are complete. This callback is useful when children of the item
+ * views create animations which may not be easy to implement using an {@link ItemAnimator}.
+ * <p>
+ * You should <em>never</em> fix this issue by calling
+ * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called
+ * <code>holder.itemView.setHasTransientState(true);</code>. Each
+ * <code>View.setHasTransientState(true)</code> call must be matched by a
+ * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View
+ * may become inconsistent. You should always prefer to end or cancel animations that are
+ * triggering the transient state instead of handling it manually.
+ *
+ * @param holder The ViewHolder containing the View that could not be recycled due to its
+ * transient state.
+ * @return True if the View should be recycled, false otherwise. Note that if this method
+ * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of
+ * the View and recycle it regardless. If this method returns <code>false</code>,
+ * RecyclerView will check the View's transient state again before giving a final decision.
+ * Default implementation returns false.
+ */
+ public boolean onFailedToRecycleView(VH holder) {
+ return false;
+ }
+
+ /**
+ * Called when a view created by this adapter has been attached to a window.
+ *
+ * <p>This can be used as a reasonable signal that the view is about to be seen
+ * by the user. If the adapter previously freed any resources in
+ * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow}
+ * those resources should be restored here.</p>
+ *
+ * @param holder Holder of the view being attached
+ */
+ public void onViewAttachedToWindow(VH holder) {
+ }
+
+ /**
+ * Called when a view created by this adapter has been detached from its window.
+ *
+ * <p>Becoming detached from the window is not necessarily a permanent condition;
+ * the consumer of an Adapter's views may choose to cache views offscreen while they
+ * are not visible, attaching and detaching them as appropriate.</p>
+ *
+ * @param holder Holder of the view being detached
+ */
+ public void onViewDetachedFromWindow(VH holder) {
+ }
+
+ /**
+ * Returns true if one or more observers are attached to this adapter.
+ *
+ * @return true if this adapter has observers
+ */
+ public final boolean hasObservers() {
+ return mObservable.hasObservers();
+ }
+
+ /**
+ * Register a new observer to listen for data changes.
+ *
+ * <p>The adapter may publish a variety of events describing specific changes.
+ * Not all adapters may support all change types and some may fall back to a generic
+ * {@link com.android.internal.widget.RecyclerView.AdapterDataObserver#onChanged()
+ * "something changed"} event if more specific data is not available.</p>
+ *
+ * <p>Components registering observers with an adapter are responsible for
+ * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
+ * unregistering} those observers when finished.</p>
+ *
+ * @param observer Observer to register
+ *
+ * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
+ */
+ public void registerAdapterDataObserver(AdapterDataObserver observer) {
+ mObservable.registerObserver(observer);
+ }
+
+ /**
+ * Unregister an observer currently listening for data changes.
+ *
+ * <p>The unregistered observer will no longer receive events about changes
+ * to the adapter.</p>
+ *
+ * @param observer Observer to unregister
+ *
+ * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver)
+ */
+ public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
+ mObservable.unregisterObserver(observer);
+ }
+
+ /**
+ * Called by RecyclerView when it starts observing this Adapter.
+ * <p>
+ * Keep in mind that same adapter may be observed by multiple RecyclerViews.
+ *
+ * @param recyclerView The RecyclerView instance which started observing this adapter.
+ * @see #onDetachedFromRecyclerView(RecyclerView)
+ */
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ }
+
+ /**
+ * Called by RecyclerView when it stops observing this Adapter.
+ *
+ * @param recyclerView The RecyclerView instance which stopped observing this adapter.
+ * @see #onAttachedToRecyclerView(RecyclerView)
+ */
+ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ }
+
+ /**
+ * Notify any registered observers that the data set has changed.
+ *
+ * <p>There are two different classes of data change events, item changes and structural
+ * changes. Item changes are when a single item has its data updated but no positional
+ * changes have occurred. Structural changes are when items are inserted, removed or moved
+ * within the data set.</p>
+ *
+ * <p>This event does not specify what about the data set has changed, forcing
+ * any observers to assume that all existing items and structure may no longer be valid.
+ * LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
+ *
+ * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
+ * for adapters that report that they have {@link #hasStableIds() stable IDs} when
+ * this method is used. This can help for the purposes of animation and visual
+ * object persistence but individual item views will still need to be rebound
+ * and relaid out.</p>
+ *
+ * <p>If you are writing an adapter it will always be more efficient to use the more
+ * specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
+ * as a last resort.</p>
+ *
+ * @see #notifyItemChanged(int)
+ * @see #notifyItemInserted(int)
+ * @see #notifyItemRemoved(int)
+ * @see #notifyItemRangeChanged(int, int)
+ * @see #notifyItemRangeInserted(int, int)
+ * @see #notifyItemRangeRemoved(int, int)
+ */
+ public final void notifyDataSetChanged() {
+ mObservable.notifyChanged();
+ }
+
+ /**
+ * Notify any registered observers that the item at <code>position</code> has changed.
+ * Equivalent to calling <code>notifyItemChanged(position, null);</code>.
+ *
+ * <p>This is an item change event, not a structural change event. It indicates that any
+ * reflection of the data at <code>position</code> is out of date and should be updated.
+ * The item at <code>position</code> retains the same identity.</p>
+ *
+ * @param position Position of the item that has changed
+ *
+ * @see #notifyItemRangeChanged(int, int)
+ */
+ public final void notifyItemChanged(int position) {
+ mObservable.notifyItemRangeChanged(position, 1);
+ }
+
+ /**
+ * Notify any registered observers that the item at <code>position</code> has changed with
+ * an optional payload object.
+ *
+ * <p>This is an item change event, not a structural change event. It indicates that any
+ * reflection of the data at <code>position</code> is out of date and should be updated.
+ * The item at <code>position</code> retains the same identity.
+ * </p>
+ *
+ * <p>
+ * Client can optionally pass a payload for partial change. These payloads will be merged
+ * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
+ * item is already represented by a ViewHolder and it will be rebound to the same
+ * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing
+ * payloads on that item and prevent future payload until
+ * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume
+ * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not
+ * attached, the payload will be simply dropped.
+ *
+ * @param position Position of the item that has changed
+ * @param payload Optional parameter, use null to identify a "full" update
+ *
+ * @see #notifyItemRangeChanged(int, int)
+ */
+ public final void notifyItemChanged(int position, Object payload) {
+ mObservable.notifyItemRangeChanged(position, 1, payload);
+ }
+
+ /**
+ * Notify any registered observers that the <code>itemCount</code> items starting at
+ * position <code>positionStart</code> have changed.
+ * Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>.
+ *
+ * <p>This is an item change event, not a structural change event. It indicates that
+ * any reflection of the data in the given position range is out of date and should
+ * be updated. The items in the given range retain the same identity.</p>
+ *
+ * @param positionStart Position of the first item that has changed
+ * @param itemCount Number of items that have changed
+ *
+ * @see #notifyItemChanged(int)
+ */
+ public final void notifyItemRangeChanged(int positionStart, int itemCount) {
+ mObservable.notifyItemRangeChanged(positionStart, itemCount);
+ }
+
+ /**
+ * Notify any registered observers that the <code>itemCount</code> items starting at
+ * position <code>positionStart</code> have changed. An optional payload can be
+ * passed to each changed item.
+ *
+ * <p>This is an item change event, not a structural change event. It indicates that any
+ * reflection of the data in the given position range is out of date and should be updated.
+ * The items in the given range retain the same identity.
+ * </p>
+ *
+ * <p>
+ * Client can optionally pass a payload for partial change. These payloads will be merged
+ * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
+ * item is already represented by a ViewHolder and it will be rebound to the same
+ * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing
+ * payloads on that item and prevent future payload until
+ * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume
+ * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not
+ * attached, the payload will be simply dropped.
+ *
+ * @param positionStart Position of the first item that has changed
+ * @param itemCount Number of items that have changed
+ * @param payload Optional parameter, use null to identify a "full" update
+ *
+ * @see #notifyItemChanged(int)
+ */
+ public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
+ }
+
+ /**
+ * Notify any registered observers that the item reflected at <code>position</code>
+ * has been newly inserted. The item previously at <code>position</code> is now at
+ * position <code>position + 1</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the
+ * data set are still considered up to date and will not be rebound, though their
+ * positions may be altered.</p>
+ *
+ * @param position Position of the newly inserted item in the data set
+ *
+ * @see #notifyItemRangeInserted(int, int)
+ */
+ public final void notifyItemInserted(int position) {
+ mObservable.notifyItemRangeInserted(position, 1);
+ }
+
+ /**
+ * Notify any registered observers that the item reflected at <code>fromPosition</code>
+ * has been moved to <code>toPosition</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the
+ * data set are still considered up to date and will not be rebound, though their
+ * positions may be altered.</p>
+ *
+ * @param fromPosition Previous position of the item.
+ * @param toPosition New position of the item.
+ */
+ public final void notifyItemMoved(int fromPosition, int toPosition) {
+ mObservable.notifyItemMoved(fromPosition, toPosition);
+ }
+
+ /**
+ * Notify any registered observers that the currently reflected <code>itemCount</code>
+ * items starting at <code>positionStart</code> have been newly inserted. The items
+ * previously located at <code>positionStart</code> and beyond can now be found starting
+ * at position <code>positionStart + itemCount</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the
+ * data set are still considered up to date and will not be rebound, though their positions
+ * may be altered.</p>
+ *
+ * @param positionStart Position of the first item that was inserted
+ * @param itemCount Number of items inserted
+ *
+ * @see #notifyItemInserted(int)
+ */
+ public final void notifyItemRangeInserted(int positionStart, int itemCount) {
+ mObservable.notifyItemRangeInserted(positionStart, itemCount);
+ }
+
+ /**
+ * Notify any registered observers that the item previously located at <code>position</code>
+ * has been removed from the data set. The items previously located at and after
+ * <code>position</code> may now be found at <code>oldPosition - 1</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the
+ * data set are still considered up to date and will not be rebound, though their positions
+ * may be altered.</p>
+ *
+ * @param position Position of the item that has now been removed
+ *
+ * @see #notifyItemRangeRemoved(int, int)
+ */
+ public final void notifyItemRemoved(int position) {
+ mObservable.notifyItemRangeRemoved(position, 1);
+ }
+
+ /**
+ * Notify any registered observers that the <code>itemCount</code> items previously
+ * located at <code>positionStart</code> have been removed from the data set. The items
+ * previously located at and after <code>positionStart + itemCount</code> may now be found
+ * at <code>oldPosition - itemCount</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the data
+ * set are still considered up to date and will not be rebound, though their positions
+ * may be altered.</p>
+ *
+ * @param positionStart Previous position of the first item that was removed
+ * @param itemCount Number of items removed from the data set
+ */
+ public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
+ mObservable.notifyItemRangeRemoved(positionStart, itemCount);
+ }
+ }
+
+ void dispatchChildDetached(View child) {
+ final ViewHolder viewHolder = getChildViewHolderInt(child);
+ onChildDetachedFromWindow(child);
+ if (mAdapter != null && viewHolder != null) {
+ mAdapter.onViewDetachedFromWindow(viewHolder);
+ }
+ if (mOnChildAttachStateListeners != null) {
+ final int cnt = mOnChildAttachStateListeners.size();
+ for (int i = cnt - 1; i >= 0; i--) {
+ mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child);
+ }
+ }
+ }
+
+ void dispatchChildAttached(View child) {
+ final ViewHolder viewHolder = getChildViewHolderInt(child);
+ onChildAttachedToWindow(child);
+ if (mAdapter != null && viewHolder != null) {
+ mAdapter.onViewAttachedToWindow(viewHolder);
+ }
+ if (mOnChildAttachStateListeners != null) {
+ final int cnt = mOnChildAttachStateListeners.size();
+ for (int i = cnt - 1; i >= 0; i--) {
+ mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
+ }
+ }
+ }
+
+ /**
+ * A <code>LayoutManager</code> is responsible for measuring and positioning item views
+ * within a <code>RecyclerView</code> as well as determining the policy for when to recycle
+ * item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
+ * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
+ * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
+ * layout managers are provided for general use.
+ * <p/>
+ * If the LayoutManager specifies a default constructor or one with the signature
+ * ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will
+ * instantiate and set the LayoutManager when being inflated. Most used properties can
+ * be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case
+ * a LayoutManager specifies both constructors, the non-default constructor will take
+ * precedence.
+ *
+ */
+ public abstract static class LayoutManager {
+ ChildHelper mChildHelper;
+ RecyclerView mRecyclerView;
+
+ @Nullable
+ SmoothScroller mSmoothScroller;
+
+ boolean mRequestedSimpleAnimations = false;
+
+ boolean mIsAttachedToWindow = false;
+
+ boolean mAutoMeasure = false;
+
+ /**
+ * LayoutManager has its own more strict measurement cache to avoid re-measuring a child
+ * if the space that will be given to it is already larger than what it has measured before.
+ */
+ private boolean mMeasurementCacheEnabled = true;
+
+ private boolean mItemPrefetchEnabled = true;
+
+ /**
+ * Written by {@link GapWorker} when prefetches occur to track largest number of view ever
+ * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or
+ * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call.
+ *
+ * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)},
+ * will be reset upon layout to prevent initial prefetches (often large, since they're
+ * proportional to expected child count) from expanding cache permanently.
+ */
+ int mPrefetchMaxCountObserved;
+
+ /**
+ * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset.
+ */
+ boolean mPrefetchMaxObservedInInitialPrefetch;
+
+ /**
+ * These measure specs might be the measure specs that were passed into RecyclerView's
+ * onMeasure method OR fake measure specs created by the RecyclerView.
+ * For example, when a layout is run, RecyclerView always sets these specs to be
+ * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass.
+ * <p>
+ * Also, to be able to use the hint in unspecified measure specs, RecyclerView checks the
+ * API level and sets the size to 0 pre-M to avoid any issue that might be caused by
+ * corrupt values. Older platforms have no responsibility to provide a size if they set
+ * mode to unspecified.
+ */
+ private int mWidthMode, mHeightMode;
+ private int mWidth, mHeight;
+
+
+ /**
+ * Interface for LayoutManagers to request items to be prefetched, based on position, with
+ * specified distance from viewport, which indicates priority.
+ *
+ * @see LayoutManager#collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)
+ * @see LayoutManager#collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
+ */
+ public interface LayoutPrefetchRegistry {
+ /**
+ * Requests an an item to be prefetched, based on position, with a specified distance,
+ * indicating priority.
+ *
+ * @param layoutPosition Position of the item to prefetch.
+ * @param pixelDistance Distance from the current viewport to the bounds of the item,
+ * must be non-negative.
+ */
+ void addPosition(int layoutPosition, int pixelDistance);
+ }
+
+ void setRecyclerView(RecyclerView recyclerView) {
+ if (recyclerView == null) {
+ mRecyclerView = null;
+ mChildHelper = null;
+ mWidth = 0;
+ mHeight = 0;
+ } else {
+ mRecyclerView = recyclerView;
+ mChildHelper = recyclerView.mChildHelper;
+ mWidth = recyclerView.getWidth();
+ mHeight = recyclerView.getHeight();
+ }
+ mWidthMode = MeasureSpec.EXACTLY;
+ mHeightMode = MeasureSpec.EXACTLY;
+ }
+
+ void setMeasureSpecs(int wSpec, int hSpec) {
+ mWidth = MeasureSpec.getSize(wSpec);
+ mWidthMode = MeasureSpec.getMode(wSpec);
+ if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
+ mWidth = 0;
+ }
+
+ mHeight = MeasureSpec.getSize(hSpec);
+ mHeightMode = MeasureSpec.getMode(hSpec);
+ if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
+ mHeight = 0;
+ }
+ }
+
+ /**
+ * Called after a layout is calculated during a measure pass when using auto-measure.
+ * <p>
+ * It simply traverses all children to calculate a bounding box then calls
+ * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method
+ * if they need to handle the bounding box differently.
+ * <p>
+ * For example, GridLayoutManager override that method to ensure that even if a column is
+ * empty, the GridLayoutManager still measures wide enough to include it.
+ *
+ * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure
+ * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure
+ */
+ void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) {
+ final int count = getChildCount();
+ if (count == 0) {
+ mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
+ return;
+ }
+ int minX = Integer.MAX_VALUE;
+ int minY = Integer.MAX_VALUE;
+ int maxX = Integer.MIN_VALUE;
+ int maxY = Integer.MIN_VALUE;
+
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ final Rect bounds = mRecyclerView.mTempRect;
+ getDecoratedBoundsWithMargins(child, bounds);
+ if (bounds.left < minX) {
+ minX = bounds.left;
+ }
+ if (bounds.right > maxX) {
+ maxX = bounds.right;
+ }
+ if (bounds.top < minY) {
+ minY = bounds.top;
+ }
+ if (bounds.bottom > maxY) {
+ maxY = bounds.bottom;
+ }
+ }
+ mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);
+ setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec);
+ }
+
+ /**
+ * Sets the measured dimensions from the given bounding box of the children and the
+ * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is
+ * called after the RecyclerView calls
+ * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a measurement pass.
+ * <p>
+ * This method should call {@link #setMeasuredDimension(int, int)}.
+ * <p>
+ * The default implementation adds the RecyclerView's padding to the given bounding box
+ * then caps the value to be within the given measurement specs.
+ * <p>
+ * This method is only called if the LayoutManager opted into the auto measurement API.
+ *
+ * @param childrenBounds The bounding box of all children
+ * @param wSpec The widthMeasureSpec that was passed into the RecyclerView.
+ * @param hSpec The heightMeasureSpec that was passed into the RecyclerView.
+ *
+ * @see #setAutoMeasureEnabled(boolean)
+ */
+ public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
+ int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight();
+ int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom();
+ int width = chooseSize(wSpec, usedWidth, getMinimumWidth());
+ int height = chooseSize(hSpec, usedHeight, getMinimumHeight());
+ setMeasuredDimension(width, height);
+ }
+
+ /**
+ * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView
+ */
+ public void requestLayout() {
+ if (mRecyclerView != null) {
+ mRecyclerView.requestLayout();
+ }
+ }
+
+ /**
+ * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+ * {@link IllegalStateException} if it <b>is not</b>.
+ *
+ * @param message The message for the exception. Can be null.
+ * @see #assertNotInLayoutOrScroll(String)
+ */
+ public void assertInLayoutOrScroll(String message) {
+ if (mRecyclerView != null) {
+ mRecyclerView.assertInLayoutOrScroll(message);
+ }
+ }
+
+ /**
+ * Chooses a size from the given specs and parameters that is closest to the desired size
+ * and also complies with the spec.
+ *
+ * @param spec The measureSpec
+ * @param desired The preferred measurement
+ * @param min The minimum value
+ *
+ * @return A size that fits to the given specs
+ */
+ public static int chooseSize(int spec, int desired, int min) {
+ final int mode = View.MeasureSpec.getMode(spec);
+ final int size = View.MeasureSpec.getSize(spec);
+ switch (mode) {
+ case View.MeasureSpec.EXACTLY:
+ return size;
+ case View.MeasureSpec.AT_MOST:
+ return Math.min(size, Math.max(desired, min));
+ case View.MeasureSpec.UNSPECIFIED:
+ default:
+ return Math.max(desired, min);
+ }
+ }
+
+ /**
+ * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+ * {@link IllegalStateException} if it <b>is</b>.
+ *
+ * @param message The message for the exception. Can be null.
+ * @see #assertInLayoutOrScroll(String)
+ */
+ public void assertNotInLayoutOrScroll(String message) {
+ if (mRecyclerView != null) {
+ mRecyclerView.assertNotInLayoutOrScroll(message);
+ }
+ }
+
+ /**
+ * Defines whether the layout should be measured by the RecyclerView or the LayoutManager
+ * wants to handle the layout measurements itself.
+ * <p>
+ * This method is usually called by the LayoutManager with value {@code true} if it wants
+ * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize
+ * the measurement logic, you can call this method with {@code false} and override
+ * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic.
+ * <p>
+ * AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or
+ * handle various specs provided by the RecyclerView's parent.
+ * It works by calling {@link LayoutManager#onLayoutChildren(Recycler, State)} during an
+ * {@link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based
+ * on children's positions. It does this while supporting all existing animation
+ * capabilities of the RecyclerView.
+ * <p>
+ * AutoMeasure works as follows:
+ * <ol>
+ * <li>LayoutManager should call {@code setAutoMeasureEnabled(true)} to enable it. All of
+ * the framework LayoutManagers use {@code auto-measure}.</li>
+ * <li>When {@link RecyclerView#onMeasure(int, int)} is called, if the provided specs are
+ * exact, RecyclerView will only call LayoutManager's {@code onMeasure} and return without
+ * doing any layout calculation.</li>
+ * <li>If one of the layout specs is not {@code EXACT}, the RecyclerView will start the
+ * layout process in {@code onMeasure} call. It will process all pending Adapter updates and
+ * decide whether to run a predictive layout or not. If it decides to do so, it will first
+ * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to
+ * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still
+ * return the width and height of the RecyclerView as of the last layout calculation.
+ * <p>
+ * After handling the predictive case, RecyclerView will call
+ * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
+ * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can
+ * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()},
+ * {@link #getWidth()} and {@link #getWidthMode()}.</li>
+ * <li>After the layout calculation, RecyclerView sets the measured width & height by
+ * calculating the bounding box for the children (+ RecyclerView's padding). The
+ * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose
+ * different values. For instance, GridLayoutManager overrides this value to handle the case
+ * where if it is vertical and has 3 columns but only 2 items, it should still measure its
+ * width to fit 3 items, not 2.</li>
+ * <li>Any following on measure call to the RecyclerView will run
+ * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
+ * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will
+ * take care of which views are actually added / removed / moved / changed for animations so
+ * that the LayoutManager should not worry about them and handle each
+ * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one.
+ * </li>
+ * <li>When measure is complete and RecyclerView's
+ * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks
+ * whether it already did layout calculations during the measure pass and if so, it re-uses
+ * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)}
+ * if the last measure spec was different from the final dimensions or adapter contents
+ * have changed between the measure call and the layout call.</li>
+ * <li>Finally, animations are calculated and run as usual.</li>
+ * </ol>
+ *
+ * @param enabled <code>True</code> if the Layout should be measured by the
+ * RecyclerView, <code>false</code> if the LayoutManager wants
+ * to measure itself.
+ *
+ * @see #setMeasuredDimension(Rect, int, int)
+ * @see #isAutoMeasureEnabled()
+ */
+ public void setAutoMeasureEnabled(boolean enabled) {
+ mAutoMeasure = enabled;
+ }
+
+ /**
+ * Returns whether the LayoutManager uses the automatic measurement API or not.
+ *
+ * @return <code>True</code> if the LayoutManager is measured by the RecyclerView or
+ * <code>false</code> if it measures itself.
+ *
+ * @see #setAutoMeasureEnabled(boolean)
+ */
+ public boolean isAutoMeasureEnabled() {
+ return mAutoMeasure;
+ }
+
+ /**
+ * Returns whether this LayoutManager supports automatic item animations.
+ * A LayoutManager wishing to support item animations should obey certain
+ * rules as outlined in {@link #onLayoutChildren(Recycler, State)}.
+ * The default return value is <code>false</code>, so subclasses of LayoutManager
+ * will not get predictive item animations by default.
+ *
+ * <p>Whether item animations are enabled in a RecyclerView is determined both
+ * by the return value from this method and the
+ * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the
+ * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this
+ * method returns false, then simple item animations will be enabled, in which
+ * views that are moving onto or off of the screen are simply faded in/out. If
+ * the RecyclerView has a non-null ItemAnimator and this method returns true,
+ * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to
+ * setup up the information needed to more intelligently predict where appearing
+ * and disappearing views should be animated from/to.</p>
+ *
+ * @return true if predictive item animations should be enabled, false otherwise
+ */
+ public boolean supportsPredictiveItemAnimations() {
+ return false;
+ }
+
+ /**
+ * Sets whether the LayoutManager should be queried for views outside of
+ * its viewport while the UI thread is idle between frames.
+ *
+ * <p>If enabled, the LayoutManager will be queried for items to inflate/bind in between
+ * view system traversals on devices running API 21 or greater. Default value is true.</p>
+ *
+ * <p>On platforms API level 21 and higher, the UI thread is idle between passing a frame
+ * to RenderThread and the starting up its next frame at the next VSync pulse. By
+ * prefetching out of window views in this time period, delays from inflation and view
+ * binding are much less likely to cause jank and stuttering during scrolls and flings.</p>
+ *
+ * <p>While prefetch is enabled, it will have the side effect of expanding the effective
+ * size of the View cache to hold prefetched views.</p>
+ *
+ * @param enabled <code>True</code> if items should be prefetched in between traversals.
+ *
+ * @see #isItemPrefetchEnabled()
+ */
+ public final void setItemPrefetchEnabled(boolean enabled) {
+ if (enabled != mItemPrefetchEnabled) {
+ mItemPrefetchEnabled = enabled;
+ mPrefetchMaxCountObserved = 0;
+ if (mRecyclerView != null) {
+ mRecyclerView.mRecycler.updateViewCacheSize();
+ }
+ }
+ }
+
+ /**
+ * Sets whether the LayoutManager should be queried for views outside of
+ * its viewport while the UI thread is idle between frames.
+ *
+ * @see #setItemPrefetchEnabled(boolean)
+ *
+ * @return true if item prefetch is enabled, false otherwise
+ */
+ public final boolean isItemPrefetchEnabled() {
+ return mItemPrefetchEnabled;
+ }
+
+ /**
+ * Gather all positions from the LayoutManager to be prefetched, given specified momentum.
+ *
+ * <p>If item prefetch is enabled, this method is called in between traversals to gather
+ * which positions the LayoutManager will soon need, given upcoming movement in subsequent
+ * traversals.</p>
+ *
+ * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for
+ * each item to be prepared, and these positions will have their ViewHolders created and
+ * bound, if there is sufficient time available, in advance of being needed by a
+ * scroll or layout.</p>
+ *
+ * @param dx X movement component.
+ * @param dy Y movement component.
+ * @param state State of RecyclerView
+ * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into.
+ *
+ * @see #isItemPrefetchEnabled()
+ * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
+ */
+ public void collectAdjacentPrefetchPositions(int dx, int dy, State state,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {}
+
+ /**
+ * Gather all positions from the LayoutManager to be prefetched in preperation for its
+ * RecyclerView to come on screen, due to the movement of another, containing RecyclerView.
+ *
+ * <p>This method is only called when a RecyclerView is nested in another RecyclerView.</p>
+ *
+ * <p>If item prefetch is enabled for this LayoutManager, as well in another containing
+ * LayoutManager, this method is called in between draw traversals to gather
+ * which positions this LayoutManager will first need, once it appears on the screen.</p>
+ *
+ * <p>For example, if this LayoutManager represents a horizontally scrolling list within a
+ * vertically scrolling LayoutManager, this method would be called when the horizontal list
+ * is about to come onscreen.</p>
+ *
+ * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for
+ * each item to be prepared, and these positions will have their ViewHolders created and
+ * bound, if there is sufficient time available, in advance of being needed by a
+ * scroll or layout.</p>
+ *
+ * @param adapterItemCount number of items in the associated adapter.
+ * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into.
+ *
+ * @see #isItemPrefetchEnabled()
+ * @see #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)
+ */
+ public void collectInitialPrefetchPositions(int adapterItemCount,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {}
+
+ void dispatchAttachedToWindow(RecyclerView view) {
+ mIsAttachedToWindow = true;
+ onAttachedToWindow(view);
+ }
+
+ void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) {
+ mIsAttachedToWindow = false;
+ onDetachedFromWindow(view, recycler);
+ }
+
+ /**
+ * Returns whether LayoutManager is currently attached to a RecyclerView which is attached
+ * to a window.
+ *
+ * @return True if this LayoutManager is controlling a RecyclerView and the RecyclerView
+ * is attached to window.
+ */
+ public boolean isAttachedToWindow() {
+ return mIsAttachedToWindow;
+ }
+
+ /**
+ * Causes the Runnable to execute on the next animation time step.
+ * The runnable will be run on the user interface thread.
+ * <p>
+ * Calling this method when LayoutManager is not attached to a RecyclerView has no effect.
+ *
+ * @param action The Runnable that will be executed.
+ *
+ * @see #removeCallbacks
+ */
+ public void postOnAnimation(Runnable action) {
+ if (mRecyclerView != null) {
+ mRecyclerView.postOnAnimation(action);
+ }
+ }
+
+ /**
+ * Removes the specified Runnable from the message queue.
+ * <p>
+ * Calling this method when LayoutManager is not attached to a RecyclerView has no effect.
+ *
+ * @param action The Runnable to remove from the message handling queue
+ *
+ * @return true if RecyclerView could ask the Handler to remove the Runnable,
+ * false otherwise. When the returned value is true, the Runnable
+ * may or may not have been actually removed from the message queue
+ * (for instance, if the Runnable was not in the queue already.)
+ *
+ * @see #postOnAnimation
+ */
+ public boolean removeCallbacks(Runnable action) {
+ if (mRecyclerView != null) {
+ return mRecyclerView.removeCallbacks(action);
+ }
+ return false;
+ }
+ /**
+ * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView
+ * is attached to a window.
+ * <p>
+ * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not
+ * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was
+ * not requested on the RecyclerView while it was detached.
+ * <p>
+ * Subclass implementations should always call through to the superclass implementation.
+ *
+ * @param view The RecyclerView this LayoutManager is bound to
+ *
+ * @see #onDetachedFromWindow(RecyclerView, Recycler)
+ */
+ @CallSuper
+ public void onAttachedToWindow(RecyclerView view) {
+ }
+
+ /**
+ * @deprecated
+ * override {@link #onDetachedFromWindow(RecyclerView, Recycler)}
+ */
+ @Deprecated
+ public void onDetachedFromWindow(RecyclerView view) {
+
+ }
+
+ /**
+ * Called when this LayoutManager is detached from its parent RecyclerView or when
+ * its parent RecyclerView is detached from its window.
+ * <p>
+ * LayoutManager should clear all of its View references as another LayoutManager might be
+ * assigned to the RecyclerView.
+ * <p>
+ * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not
+ * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was
+ * not requested on the RecyclerView while it was detached.
+ * <p>
+ * If your LayoutManager has View references that it cleans in on-detach, it should also
+ * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when
+ * RecyclerView is re-attached.
+ * <p>
+ * Subclass implementations should always call through to the superclass implementation.
+ *
+ * @param view The RecyclerView this LayoutManager is bound to
+ * @param recycler The recycler to use if you prefer to recycle your children instead of
+ * keeping them around.
+ *
+ * @see #onAttachedToWindow(RecyclerView)
+ */
+ @CallSuper
+ public void onDetachedFromWindow(RecyclerView view, Recycler recycler) {
+ onDetachedFromWindow(view);
+ }
+
+ /**
+ * Check if the RecyclerView is configured to clip child views to its padding.
+ *
+ * @return true if this RecyclerView clips children to its padding, false otherwise
+ */
+ public boolean getClipToPadding() {
+ return mRecyclerView != null && mRecyclerView.mClipToPadding;
+ }
+
+ /**
+ * Lay out all relevant child views from the given adapter.
+ *
+ * The LayoutManager is in charge of the behavior of item animations. By default,
+ * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple
+ * item animations are enabled. This means that add/remove operations on the
+ * adapter will result in animations to add new or appearing items, removed or
+ * disappearing items, and moved items. If a LayoutManager returns false from
+ * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a
+ * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the
+ * RecyclerView will have enough information to run those animations in a simple
+ * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will
+ * simply fade views in and out, whether they are actually added/removed or whether
+ * they are moved on or off the screen due to other add/remove operations.
+ *
+ * <p>A LayoutManager wanting a better item animation experience, where items can be
+ * animated onto and off of the screen according to where the items exist when they
+ * are not on screen, then the LayoutManager should return true from
+ * {@link #supportsPredictiveItemAnimations()} and add additional logic to
+ * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations
+ * means that {@link #onLayoutChildren(Recycler, State)} will be called twice;
+ * once as a "pre" layout step to determine where items would have been prior to
+ * a real layout, and again to do the "real" layout. In the pre-layout phase,
+ * items will remember their pre-layout positions to allow them to be laid out
+ * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will
+ * be returned from the scrap to help determine correct placement of other items.
+ * These removed items should not be added to the child list, but should be used
+ * to help calculate correct positioning of other views, including views that
+ * were not previously onscreen (referred to as APPEARING views), but whose
+ * pre-layout offscreen position can be determined given the extra
+ * information about the pre-layout removed views.</p>
+ *
+ * <p>The second layout pass is the real layout in which only non-removed views
+ * will be used. The only additional requirement during this pass is, if
+ * {@link #supportsPredictiveItemAnimations()} returns true, to note which
+ * views exist in the child list prior to layout and which are not there after
+ * layout (referred to as DISAPPEARING views), and to position/layout those views
+ * appropriately, without regard to the actual bounds of the RecyclerView. This allows
+ * the animation system to know the location to which to animate these disappearing
+ * views.</p>
+ *
+ * <p>The default LayoutManager implementations for RecyclerView handle all of these
+ * requirements for animations already. Clients of RecyclerView can either use one
+ * of these layout managers directly or look at their implementations of
+ * onLayoutChildren() to see how they account for the APPEARING and
+ * DISAPPEARING views.</p>
+ *
+ * @param recycler Recycler to use for fetching potentially cached views for a
+ * position
+ * @param state Transient state of RecyclerView
+ */
+ public void onLayoutChildren(Recycler recycler, State state) {
+ Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
+ }
+
+ /**
+ * Called after a full layout calculation is finished. The layout calculation may include
+ * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or
+ * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call.
+ * This method will be called at the end of {@link View#layout(int, int, int, int)} call.
+ * <p>
+ * This is a good place for the LayoutManager to do some cleanup like pending scroll
+ * position, saved state etc.
+ *
+ * @param state Transient state of RecyclerView
+ */
+ public void onLayoutCompleted(State state) {
+ }
+
+ /**
+ * Create a default <code>LayoutParams</code> object for a child of the RecyclerView.
+ *
+ * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type
+ * to store extra information specific to the layout. Client code should subclass
+ * {@link RecyclerView.LayoutParams} for this purpose.</p>
+ *
+ * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+ * you must also override
+ * {@link #checkLayoutParams(LayoutParams)},
+ * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+ * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+ *
+ * @return A new LayoutParams for a child view
+ */
+ public abstract LayoutParams generateDefaultLayoutParams();
+
+ /**
+ * Determines the validity of the supplied LayoutParams object.
+ *
+ * <p>This should check to make sure that the object is of the correct type
+ * and all values are within acceptable ranges. The default implementation
+ * returns <code>true</code> for non-null params.</p>
+ *
+ * @param lp LayoutParams object to check
+ * @return true if this LayoutParams object is valid, false otherwise
+ */
+ public boolean checkLayoutParams(LayoutParams lp) {
+ return lp != null;
+ }
+
+ /**
+ * Create a LayoutParams object suitable for this LayoutManager, copying relevant
+ * values from the supplied LayoutParams object if possible.
+ *
+ * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+ * you must also override
+ * {@link #checkLayoutParams(LayoutParams)},
+ * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+ * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+ *
+ * @param lp Source LayoutParams object to copy values from
+ * @return a new LayoutParams object
+ */
+ public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+ if (lp instanceof LayoutParams) {
+ return new LayoutParams((LayoutParams) lp);
+ } else if (lp instanceof MarginLayoutParams) {
+ return new LayoutParams((MarginLayoutParams) lp);
+ } else {
+ return new LayoutParams(lp);
+ }
+ }
+
+ /**
+ * Create a LayoutParams object suitable for this LayoutManager from
+ * an inflated layout resource.
+ *
+ * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+ * you must also override
+ * {@link #checkLayoutParams(LayoutParams)},
+ * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+ * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+ *
+ * @param c Context for obtaining styled attributes
+ * @param attrs AttributeSet describing the supplied arguments
+ * @return a new LayoutParams object
+ */
+ public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
+ return new LayoutParams(c, attrs);
+ }
+
+ /**
+ * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled.
+ * The default implementation does nothing and returns 0.
+ *
+ * @param dx distance to scroll by in pixels. X increases as scroll position
+ * approaches the right.
+ * @param recycler Recycler to use for fetching potentially cached views for a
+ * position
+ * @param state Transient state of RecyclerView
+ * @return The actual distance scrolled. The return value will be negative if dx was
+ * negative and scrolling proceeeded in that direction.
+ * <code>Math.abs(result)</code> may be less than dx if a boundary was reached.
+ */
+ public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
+ return 0;
+ }
+
+ /**
+ * Scroll vertically by dy pixels in screen coordinates and return the distance traveled.
+ * The default implementation does nothing and returns 0.
+ *
+ * @param dy distance to scroll in pixels. Y increases as scroll position
+ * approaches the bottom.
+ * @param recycler Recycler to use for fetching potentially cached views for a
+ * position
+ * @param state Transient state of RecyclerView
+ * @return The actual distance scrolled. The return value will be negative if dy was
+ * negative and scrolling proceeeded in that direction.
+ * <code>Math.abs(result)</code> may be less than dy if a boundary was reached.
+ */
+ public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
+ return 0;
+ }
+
+ /**
+ * Query if horizontal scrolling is currently supported. The default implementation
+ * returns false.
+ *
+ * @return True if this LayoutManager can scroll the current contents horizontally
+ */
+ public boolean canScrollHorizontally() {
+ return false;
+ }
+
+ /**
+ * Query if vertical scrolling is currently supported. The default implementation
+ * returns false.
+ *
+ * @return True if this LayoutManager can scroll the current contents vertically
+ */
+ public boolean canScrollVertically() {
+ return false;
+ }
+
+ /**
+ * Scroll to the specified adapter position.
+ *
+ * Actual position of the item on the screen depends on the LayoutManager implementation.
+ * @param position Scroll to this adapter position.
+ */
+ public void scrollToPosition(int position) {
+ if (DEBUG) {
+ Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
+ }
+ }
+
+ /**
+ * <p>Smooth scroll to the specified adapter position.</p>
+ * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller}
+ * instance and call {@link #startSmoothScroll(SmoothScroller)}.
+ * </p>
+ * @param recyclerView The RecyclerView to which this layout manager is attached
+ * @param state Current State of RecyclerView
+ * @param position Scroll to this adapter position.
+ */
+ public void smoothScrollToPosition(RecyclerView recyclerView, State state,
+ int position) {
+ Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling");
+ }
+
+ /**
+ * <p>Starts a smooth scroll using the provided SmoothScroller.</p>
+ * <p>Calling this method will cancel any previous smooth scroll request.</p>
+ * @param smoothScroller Instance which defines how smooth scroll should be animated
+ */
+ public void startSmoothScroll(SmoothScroller smoothScroller) {
+ if (mSmoothScroller != null && smoothScroller != mSmoothScroller
+ && mSmoothScroller.isRunning()) {
+ mSmoothScroller.stop();
+ }
+ mSmoothScroller = smoothScroller;
+ mSmoothScroller.start(mRecyclerView, this);
+ }
+
+ /**
+ * @return true if RecycylerView is currently in the state of smooth scrolling.
+ */
+ public boolean isSmoothScrolling() {
+ return mSmoothScroller != null && mSmoothScroller.isRunning();
+ }
+
+
+ /**
+ * Returns the resolved layout direction for this RecyclerView.
+ *
+ * @return {@link android.view.View#LAYOUT_DIRECTION_RTL} if the layout
+ * direction is RTL or returns
+ * {@link android.view.View#LAYOUT_DIRECTION_LTR} if the layout direction
+ * is not RTL.
+ */
+ public int getLayoutDirection() {
+ return mRecyclerView.getLayoutDirection();
+ }
+
+ /**
+ * Ends all animations on the view created by the {@link ItemAnimator}.
+ *
+ * @param view The View for which the animations should be ended.
+ * @see RecyclerView.ItemAnimator#endAnimations()
+ */
+ public void endAnimation(View view) {
+ if (mRecyclerView.mItemAnimator != null) {
+ mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view));
+ }
+ }
+
+ /**
+ * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view
+ * to the layout that is known to be going away, either because it has been
+ * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the
+ * visible portion of the container but is being laid out in order to inform RecyclerView
+ * in how to animate the item out of view.
+ * <p>
+ * Views added via this method are going to be invisible to LayoutManager after the
+ * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)}
+ * or won't be included in {@link #getChildCount()} method.
+ *
+ * @param child View to add and then remove with animation.
+ */
+ public void addDisappearingView(View child) {
+ addDisappearingView(child, -1);
+ }
+
+ /**
+ * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view
+ * to the layout that is known to be going away, either because it has been
+ * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the
+ * visible portion of the container but is being laid out in order to inform RecyclerView
+ * in how to animate the item out of view.
+ * <p>
+ * Views added via this method are going to be invisible to LayoutManager after the
+ * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)}
+ * or won't be included in {@link #getChildCount()} method.
+ *
+ * @param child View to add and then remove with animation.
+ * @param index Index of the view.
+ */
+ public void addDisappearingView(View child, int index) {
+ addViewInt(child, index, true);
+ }
+
+ /**
+ * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
+ * use this method to add views obtained from a {@link Recycler} using
+ * {@link Recycler#getViewForPosition(int)}.
+ *
+ * @param child View to add
+ */
+ public void addView(View child) {
+ addView(child, -1);
+ }
+
+ /**
+ * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
+ * use this method to add views obtained from a {@link Recycler} using
+ * {@link Recycler#getViewForPosition(int)}.
+ *
+ * @param child View to add
+ * @param index Index to add child at
+ */
+ public void addView(View child, int index) {
+ addViewInt(child, index, false);
+ }
+
+ private void addViewInt(View child, int index, boolean disappearing) {
+ final ViewHolder holder = getChildViewHolderInt(child);
+ if (disappearing || holder.isRemoved()) {
+ // these views will be hidden at the end of the layout pass.
+ mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
+ } else {
+ // This may look like unnecessary but may happen if layout manager supports
+ // predictive layouts and adapter removed then re-added the same item.
+ // In this case, added version will be visible in the post layout (because add is
+ // deferred) but RV will still bind it to the same View.
+ // So if a View re-appears in post layout pass, remove it from disappearing list.
+ mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
+ }
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (holder.wasReturnedFromScrap() || holder.isScrap()) {
+ if (holder.isScrap()) {
+ holder.unScrap();
+ } else {
+ holder.clearReturnedFromScrapFlag();
+ }
+ mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
+ if (DISPATCH_TEMP_DETACH) {
+ child.dispatchFinishTemporaryDetach();
+ }
+ } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
+ // ensure in correct position
+ int currentIndex = mChildHelper.indexOfChild(child);
+ if (index == -1) {
+ index = mChildHelper.getChildCount();
+ }
+ if (currentIndex == -1) {
+ throw new IllegalStateException("Added View has RecyclerView as parent but"
+ + " view is not a real child. Unfiltered index:"
+ + mRecyclerView.indexOfChild(child));
+ }
+ if (currentIndex != index) {
+ mRecyclerView.mLayout.moveView(currentIndex, index);
+ }
+ } else {
+ mChildHelper.addView(child, index, false);
+ lp.mInsetsDirty = true;
+ if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
+ mSmoothScroller.onChildAttachedToWindow(child);
+ }
+ }
+ if (lp.mPendingInvalidate) {
+ if (DEBUG) {
+ Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder);
+ }
+ holder.itemView.invalidate();
+ lp.mPendingInvalidate = false;
+ }
+ }
+
+ /**
+ * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
+ * use this method to completely remove a child view that is no longer needed.
+ * LayoutManagers should strongly consider recycling removed views using
+ * {@link Recycler#recycleView(android.view.View)}.
+ *
+ * @param child View to remove
+ */
+ public void removeView(View child) {
+ mChildHelper.removeView(child);
+ }
+
+ /**
+ * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
+ * use this method to completely remove a child view that is no longer needed.
+ * LayoutManagers should strongly consider recycling removed views using
+ * {@link Recycler#recycleView(android.view.View)}.
+ *
+ * @param index Index of the child view to remove
+ */
+ public void removeViewAt(int index) {
+ final View child = getChildAt(index);
+ if (child != null) {
+ mChildHelper.removeViewAt(index);
+ }
+ }
+
+ /**
+ * Remove all views from the currently attached RecyclerView. This will not recycle
+ * any of the affected views; the LayoutManager is responsible for doing so if desired.
+ */
+ public void removeAllViews() {
+ // Only remove non-animating views
+ final int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ mChildHelper.removeViewAt(i);
+ }
+ }
+
+ /**
+ * Returns offset of the RecyclerView's text baseline from the its top boundary.
+ *
+ * @return The offset of the RecyclerView's text baseline from the its top boundary; -1 if
+ * there is no baseline.
+ */
+ public int getBaseline() {
+ return -1;
+ }
+
+ /**
+ * Returns the adapter position of the item represented by the given View. This does not
+ * contain any adapter changes that might have happened after the last layout.
+ *
+ * @param view The view to query
+ * @return The adapter position of the item which is rendered by this View.
+ */
+ public int getPosition(View view) {
+ return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
+ }
+
+ /**
+ * Returns the View type defined by the adapter.
+ *
+ * @param view The view to query
+ * @return The type of the view assigned by the adapter.
+ */
+ public int getItemViewType(View view) {
+ return getChildViewHolderInt(view).getItemViewType();
+ }
+
+ /**
+ * Traverses the ancestors of the given view and returns the item view that contains it
+ * and also a direct child of the LayoutManager.
+ * <p>
+ * Note that this method may return null if the view is a child of the RecyclerView but
+ * not a child of the LayoutManager (e.g. running a disappear animation).
+ *
+ * @param view The view that is a descendant of the LayoutManager.
+ *
+ * @return The direct child of the LayoutManager which contains the given view or null if
+ * the provided view is not a descendant of this LayoutManager.
+ *
+ * @see RecyclerView#getChildViewHolder(View)
+ * @see RecyclerView#findContainingViewHolder(View)
+ */
+ @Nullable
+ public View findContainingItemView(View view) {
+ if (mRecyclerView == null) {
+ return null;
+ }
+ View found = mRecyclerView.findContainingItemView(view);
+ if (found == null) {
+ return null;
+ }
+ if (mChildHelper.isHidden(found)) {
+ return null;
+ }
+ return found;
+ }
+
+ /**
+ * Finds the view which represents the given adapter position.
+ * <p>
+ * This method traverses each child since it has no information about child order.
+ * Override this method to improve performance if your LayoutManager keeps data about
+ * child views.
+ * <p>
+ * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method.
+ *
+ * @param position Position of the item in adapter
+ * @return The child view that represents the given position or null if the position is not
+ * laid out
+ */
+ public View findViewByPosition(int position) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ ViewHolder vh = getChildViewHolderInt(child);
+ if (vh == null) {
+ continue;
+ }
+ if (vh.getLayoutPosition() == position && !vh.shouldIgnore()
+ && (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Temporarily detach a child view.
+ *
+ * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
+ * views currently attached to the RecyclerView. Generally LayoutManager implementations
+ * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
+ * so that the detached view may be rebound and reused.</p>
+ *
+ * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
+ * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
+ * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
+ * before the LayoutManager entry point method called by RecyclerView returns.</p>
+ *
+ * @param child Child to detach
+ */
+ public void detachView(View child) {
+ final int ind = mChildHelper.indexOfChild(child);
+ if (ind >= 0) {
+ detachViewInternal(ind, child);
+ }
+ }
+
+ /**
+ * Temporarily detach a child view.
+ *
+ * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
+ * views currently attached to the RecyclerView. Generally LayoutManager implementations
+ * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
+ * so that the detached view may be rebound and reused.</p>
+ *
+ * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
+ * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
+ * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
+ * before the LayoutManager entry point method called by RecyclerView returns.</p>
+ *
+ * @param index Index of the child to detach
+ */
+ public void detachViewAt(int index) {
+ detachViewInternal(index, getChildAt(index));
+ }
+
+ private void detachViewInternal(int index, View view) {
+ if (DISPATCH_TEMP_DETACH) {
+ view.dispatchStartTemporaryDetach();
+ }
+ mChildHelper.detachViewFromParent(index);
+ }
+
+ /**
+ * Reattach a previously {@link #detachView(android.view.View) detached} view.
+ * This method should not be used to reattach views that were previously
+ * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}.
+ *
+ * @param child Child to reattach
+ * @param index Intended child index for child
+ * @param lp LayoutParams for child
+ */
+ public void attachView(View child, int index, LayoutParams lp) {
+ ViewHolder vh = getChildViewHolderInt(child);
+ if (vh.isRemoved()) {
+ mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh);
+ } else {
+ mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh);
+ }
+ mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved());
+ if (DISPATCH_TEMP_DETACH) {
+ child.dispatchFinishTemporaryDetach();
+ }
+ }
+
+ /**
+ * Reattach a previously {@link #detachView(android.view.View) detached} view.
+ * This method should not be used to reattach views that were previously
+ * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}.
+ *
+ * @param child Child to reattach
+ * @param index Intended child index for child
+ */
+ public void attachView(View child, int index) {
+ attachView(child, index, (LayoutParams) child.getLayoutParams());
+ }
+
+ /**
+ * Reattach a previously {@link #detachView(android.view.View) detached} view.
+ * This method should not be used to reattach views that were previously
+ * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}.
+ *
+ * @param child Child to reattach
+ */
+ public void attachView(View child) {
+ attachView(child, -1);
+ }
+
+ /**
+ * Finish removing a view that was previously temporarily
+ * {@link #detachView(android.view.View) detached}.
+ *
+ * @param child Detached child to remove
+ */
+ public void removeDetachedView(View child) {
+ mRecyclerView.removeDetachedView(child, false);
+ }
+
+ /**
+ * Moves a View from one position to another.
+ *
+ * @param fromIndex The View's initial index
+ * @param toIndex The View's target index
+ */
+ public void moveView(int fromIndex, int toIndex) {
+ View view = getChildAt(fromIndex);
+ if (view == null) {
+ throw new IllegalArgumentException("Cannot move a child from non-existing index:"
+ + fromIndex);
+ }
+ detachViewAt(fromIndex);
+ attachView(view, toIndex);
+ }
+
+ /**
+ * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
+ *
+ * <p>Scrapping a view allows it to be rebound and reused to show updated or
+ * different data.</p>
+ *
+ * @param child Child to detach and scrap
+ * @param recycler Recycler to deposit the new scrap view into
+ */
+ public void detachAndScrapView(View child, Recycler recycler) {
+ int index = mChildHelper.indexOfChild(child);
+ scrapOrRecycleView(recycler, index, child);
+ }
+
+ /**
+ * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
+ *
+ * <p>Scrapping a view allows it to be rebound and reused to show updated or
+ * different data.</p>
+ *
+ * @param index Index of child to detach and scrap
+ * @param recycler Recycler to deposit the new scrap view into
+ */
+ public void detachAndScrapViewAt(int index, Recycler recycler) {
+ final View child = getChildAt(index);
+ scrapOrRecycleView(recycler, index, child);
+ }
+
+ /**
+ * Remove a child view and recycle it using the given Recycler.
+ *
+ * @param child Child to remove and recycle
+ * @param recycler Recycler to use to recycle child
+ */
+ public void removeAndRecycleView(View child, Recycler recycler) {
+ removeView(child);
+ recycler.recycleView(child);
+ }
+
+ /**
+ * Remove a child view and recycle it using the given Recycler.
+ *
+ * @param index Index of child to remove and recycle
+ * @param recycler Recycler to use to recycle child
+ */
+ public void removeAndRecycleViewAt(int index, Recycler recycler) {
+ final View view = getChildAt(index);
+ removeViewAt(index);
+ recycler.recycleView(view);
+ }
+
+ /**
+ * Return the current number of child views attached to the parent RecyclerView.
+ * This does not include child views that were temporarily detached and/or scrapped.
+ *
+ * @return Number of attached children
+ */
+ public int getChildCount() {
+ return mChildHelper != null ? mChildHelper.getChildCount() : 0;
+ }
+
+ /**
+ * Return the child view at the given index
+ * @param index Index of child to return
+ * @return Child view at index
+ */
+ public View getChildAt(int index) {
+ return mChildHelper != null ? mChildHelper.getChildAt(index) : null;
+ }
+
+ /**
+ * Return the width measurement spec mode of the RecyclerView.
+ * <p>
+ * This value is set only if the LayoutManager opts into the auto measure api via
+ * {@link #setAutoMeasureEnabled(boolean)}.
+ * <p>
+ * When RecyclerView is running a layout, this value is always set to
+ * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+ *
+ * @return Width measure spec mode.
+ *
+ * @see View.MeasureSpec#getMode(int)
+ * @see View#onMeasure(int, int)
+ */
+ public int getWidthMode() {
+ return mWidthMode;
+ }
+
+ /**
+ * Return the height measurement spec mode of the RecyclerView.
+ * <p>
+ * This value is set only if the LayoutManager opts into the auto measure api via
+ * {@link #setAutoMeasureEnabled(boolean)}.
+ * <p>
+ * When RecyclerView is running a layout, this value is always set to
+ * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+ *
+ * @return Height measure spec mode.
+ *
+ * @see View.MeasureSpec#getMode(int)
+ * @see View#onMeasure(int, int)
+ */
+ public int getHeightMode() {
+ return mHeightMode;
+ }
+
+ /**
+ * Return the width of the parent RecyclerView
+ *
+ * @return Width in pixels
+ */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Return the height of the parent RecyclerView
+ *
+ * @return Height in pixels
+ */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Return the left padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingLeft() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0;
+ }
+
+ /**
+ * Return the top padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingTop() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0;
+ }
+
+ /**
+ * Return the right padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingRight() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0;
+ }
+
+ /**
+ * Return the bottom padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingBottom() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0;
+ }
+
+ /**
+ * Return the start padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingStart() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingStart() : 0;
+ }
+
+ /**
+ * Return the end padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingEnd() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingEnd() : 0;
+ }
+
+ /**
+ * Returns true if the RecyclerView this LayoutManager is bound to has focus.
+ *
+ * @return True if the RecyclerView has focus, false otherwise.
+ * @see View#isFocused()
+ */
+ public boolean isFocused() {
+ return mRecyclerView != null && mRecyclerView.isFocused();
+ }
+
+ /**
+ * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus.
+ *
+ * @return true if the RecyclerView has or contains focus
+ * @see View#hasFocus()
+ */
+ public boolean hasFocus() {
+ return mRecyclerView != null && mRecyclerView.hasFocus();
+ }
+
+ /**
+ * Returns the item View which has or contains focus.
+ *
+ * @return A direct child of RecyclerView which has focus or contains the focused child.
+ */
+ public View getFocusedChild() {
+ if (mRecyclerView == null) {
+ return null;
+ }
+ final View focused = mRecyclerView.getFocusedChild();
+ if (focused == null || mChildHelper.isHidden(focused)) {
+ return null;
+ }
+ return focused;
+ }
+
+ /**
+ * Returns the number of items in the adapter bound to the parent RecyclerView.
+ * <p>
+ * Note that this number is not necessarily equal to
+ * {@link State#getItemCount() State#getItemCount()}. In methods where {@link State} is
+ * available, you should use {@link State#getItemCount() State#getItemCount()} instead.
+ * For more details, check the documentation for
+ * {@link State#getItemCount() State#getItemCount()}.
+ *
+ * @return The number of items in the bound adapter
+ * @see State#getItemCount()
+ */
+ public int getItemCount() {
+ final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null;
+ return a != null ? a.getItemCount() : 0;
+ }
+
+ /**
+ * Offset all child views attached to the parent RecyclerView by dx pixels along
+ * the horizontal axis.
+ *
+ * @param dx Pixels to offset by
+ */
+ public void offsetChildrenHorizontal(int dx) {
+ if (mRecyclerView != null) {
+ mRecyclerView.offsetChildrenHorizontal(dx);
+ }
+ }
+
+ /**
+ * Offset all child views attached to the parent RecyclerView by dy pixels along
+ * the vertical axis.
+ *
+ * @param dy Pixels to offset by
+ */
+ public void offsetChildrenVertical(int dy) {
+ if (mRecyclerView != null) {
+ mRecyclerView.offsetChildrenVertical(dy);
+ }
+ }
+
+ /**
+ * Flags a view so that it will not be scrapped or recycled.
+ * <p>
+ * Scope of ignoring a child is strictly restricted to position tracking, scrapping and
+ * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child
+ * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not
+ * ignore the child.
+ * <p>
+ * Before this child can be recycled again, you have to call
+ * {@link #stopIgnoringView(View)}.
+ * <p>
+ * You can call this method only if your LayoutManger is in onLayout or onScroll callback.
+ *
+ * @param view View to ignore.
+ * @see #stopIgnoringView(View)
+ */
+ public void ignoreView(View view) {
+ if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) {
+ // checking this because calling this method on a recycled or detached view may
+ // cause loss of state.
+ throw new IllegalArgumentException("View should be fully attached to be ignored");
+ }
+ final ViewHolder vh = getChildViewHolderInt(view);
+ vh.addFlags(ViewHolder.FLAG_IGNORE);
+ mRecyclerView.mViewInfoStore.removeViewHolder(vh);
+ }
+
+ /**
+ * View can be scrapped and recycled again.
+ * <p>
+ * Note that calling this method removes all information in the view holder.
+ * <p>
+ * You can call this method only if your LayoutManger is in onLayout or onScroll callback.
+ *
+ * @param view View to ignore.
+ */
+ public void stopIgnoringView(View view) {
+ final ViewHolder vh = getChildViewHolderInt(view);
+ vh.stopIgnoring();
+ vh.resetInternal();
+ vh.addFlags(ViewHolder.FLAG_INVALID);
+ }
+
+ /**
+ * Temporarily detach and scrap all currently attached child views. Views will be scrapped
+ * into the given Recycler. The Recycler may prefer to reuse scrap views before
+ * other views that were previously recycled.
+ *
+ * @param recycler Recycler to scrap views into
+ */
+ public void detachAndScrapAttachedViews(Recycler recycler) {
+ final int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ final View v = getChildAt(i);
+ scrapOrRecycleView(recycler, i, v);
+ }
+ }
+
+ private void scrapOrRecycleView(Recycler recycler, int index, View view) {
+ final ViewHolder viewHolder = getChildViewHolderInt(view);
+ if (viewHolder.shouldIgnore()) {
+ if (DEBUG) {
+ Log.d(TAG, "ignoring view " + viewHolder);
+ }
+ return;
+ }
+ if (viewHolder.isInvalid() && !viewHolder.isRemoved()
+ && !mRecyclerView.mAdapter.hasStableIds()) {
+ removeViewAt(index);
+ recycler.recycleViewHolderInternal(viewHolder);
+ } else {
+ detachViewAt(index);
+ recycler.scrapView(view);
+ mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
+ }
+ }
+
+ /**
+ * Recycles the scrapped views.
+ * <p>
+ * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is
+ * the expected behavior if scrapped views are used for animations. Otherwise, we need to
+ * call remove and invalidate RecyclerView to ensure UI update.
+ *
+ * @param recycler Recycler
+ */
+ void removeAndRecycleScrapInt(Recycler recycler) {
+ final int scrapCount = recycler.getScrapCount();
+ // Loop backward, recycler might be changed by removeDetachedView()
+ for (int i = scrapCount - 1; i >= 0; i--) {
+ final View scrap = recycler.getScrapViewAt(i);
+ final ViewHolder vh = getChildViewHolderInt(scrap);
+ if (vh.shouldIgnore()) {
+ continue;
+ }
+ // If the scrap view is animating, we need to cancel them first. If we cancel it
+ // here, ItemAnimator callback may recycle it which will cause double recycling.
+ // To avoid this, we mark it as not recycleable before calling the item animator.
+ // Since removeDetachedView calls a user API, a common mistake (ending animations on
+ // the view) may recycle it too, so we guard it before we call user APIs.
+ vh.setIsRecyclable(false);
+ if (vh.isTmpDetached()) {
+ mRecyclerView.removeDetachedView(scrap, false);
+ }
+ if (mRecyclerView.mItemAnimator != null) {
+ mRecyclerView.mItemAnimator.endAnimation(vh);
+ }
+ vh.setIsRecyclable(true);
+ recycler.quickRecycleScrapView(scrap);
+ }
+ recycler.clearScrap();
+ if (scrapCount > 0) {
+ mRecyclerView.invalidate();
+ }
+ }
+
+
+ /**
+ * Measure a child view using standard measurement policy, taking the padding
+ * of the parent RecyclerView and any added item decorations into account.
+ *
+ * <p>If the RecyclerView can be scrolled in either dimension the caller may
+ * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
+ *
+ * @param child Child view to measure
+ * @param widthUsed Width in pixels currently consumed by other views, if relevant
+ * @param heightUsed Height in pixels currently consumed by other views, if relevant
+ */
+ public void measureChild(View child, int widthUsed, int heightUsed) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+ widthUsed += insets.left + insets.right;
+ heightUsed += insets.top + insets.bottom;
+ final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
+ getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
+ canScrollHorizontally());
+ final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
+ getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
+ canScrollVertically());
+ if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
+ child.measure(widthSpec, heightSpec);
+ }
+ }
+
+ /**
+ * RecyclerView internally does its own View measurement caching which should help with
+ * WRAP_CONTENT.
+ * <p>
+ * Use this method if the View is already measured once in this layout pass.
+ */
+ boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
+ return !mMeasurementCacheEnabled
+ || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width)
+ || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height);
+ }
+
+ // we may consider making this public
+ /**
+ * RecyclerView internally does its own View measurement caching which should help with
+ * WRAP_CONTENT.
+ * <p>
+ * Use this method if the View is not yet measured and you need to decide whether to
+ * measure this View or not.
+ */
+ boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
+ return child.isLayoutRequested()
+ || !mMeasurementCacheEnabled
+ || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width)
+ || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height);
+ }
+
+ /**
+ * In addition to the View Framework's measurement cache, RecyclerView uses its own
+ * additional measurement cache for its children to avoid re-measuring them when not
+ * necessary. It is on by default but it can be turned off via
+ * {@link #setMeasurementCacheEnabled(boolean)}.
+ *
+ * @return True if measurement cache is enabled, false otherwise.
+ *
+ * @see #setMeasurementCacheEnabled(boolean)
+ */
+ public boolean isMeasurementCacheEnabled() {
+ return mMeasurementCacheEnabled;
+ }
+
+ /**
+ * Sets whether RecyclerView should use its own measurement cache for the children. This is
+ * a more aggressive cache than the framework uses.
+ *
+ * @param measurementCacheEnabled True to enable the measurement cache, false otherwise.
+ *
+ * @see #isMeasurementCacheEnabled()
+ */
+ public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) {
+ mMeasurementCacheEnabled = measurementCacheEnabled;
+ }
+
+ private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) {
+ final int specMode = MeasureSpec.getMode(spec);
+ final int specSize = MeasureSpec.getSize(spec);
+ if (dimension > 0 && childSize != dimension) {
+ return false;
+ }
+ switch (specMode) {
+ case MeasureSpec.UNSPECIFIED:
+ return true;
+ case MeasureSpec.AT_MOST:
+ return specSize >= childSize;
+ case MeasureSpec.EXACTLY:
+ return specSize == childSize;
+ }
+ return false;
+ }
+
+ /**
+ * Measure a child view using standard measurement policy, taking the padding
+ * of the parent RecyclerView, any added item decorations and the child margins
+ * into account.
+ *
+ * <p>If the RecyclerView can be scrolled in either dimension the caller may
+ * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
+ *
+ * @param child Child view to measure
+ * @param widthUsed Width in pixels currently consumed by other views, if relevant
+ * @param heightUsed Height in pixels currently consumed by other views, if relevant
+ */
+ public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+ widthUsed += insets.left + insets.right;
+ heightUsed += insets.top + insets.bottom;
+
+ final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
+ getPaddingLeft() + getPaddingRight()
+ + lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
+ canScrollHorizontally());
+ final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
+ getPaddingTop() + getPaddingBottom()
+ + lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
+ canScrollVertically());
+ if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
+ child.measure(widthSpec, heightSpec);
+ }
+ }
+
+ /**
+ * Calculate a MeasureSpec value for measuring a child view in one dimension.
+ *
+ * @param parentSize Size of the parent view where the child will be placed
+ * @param padding Total space currently consumed by other elements of the parent
+ * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
+ * Generally obtained from the child view's LayoutParams
+ * @param canScroll true if the parent RecyclerView can scroll in this dimension
+ *
+ * @return a MeasureSpec value for the child view
+ * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)}
+ */
+ @Deprecated
+ public static int getChildMeasureSpec(int parentSize, int padding, int childDimension,
+ boolean canScroll) {
+ int size = Math.max(0, parentSize - padding);
+ int resultSize = 0;
+ int resultMode = 0;
+ if (canScroll) {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else {
+ // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
+ // instead using UNSPECIFIED.
+ resultSize = 0;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ }
+ } else {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
+ resultSize = size;
+ // TODO this should be my spec.
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ resultSize = size;
+ resultMode = MeasureSpec.AT_MOST;
+ }
+ }
+ return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+ }
+
+ /**
+ * Calculate a MeasureSpec value for measuring a child view in one dimension.
+ *
+ * @param parentSize Size of the parent view where the child will be placed
+ * @param parentMode The measurement spec mode of the parent
+ * @param padding Total space currently consumed by other elements of parent
+ * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
+ * Generally obtained from the child view's LayoutParams
+ * @param canScroll true if the parent RecyclerView can scroll in this dimension
+ *
+ * @return a MeasureSpec value for the child view
+ */
+ public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,
+ int childDimension, boolean canScroll) {
+ int size = Math.max(0, parentSize - padding);
+ int resultSize = 0;
+ int resultMode = 0;
+ if (canScroll) {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
+ switch (parentMode) {
+ case MeasureSpec.AT_MOST:
+ case MeasureSpec.EXACTLY:
+ resultSize = size;
+ resultMode = parentMode;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ resultSize = 0;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ break;
+ }
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ resultSize = 0;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ }
+ } else {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
+ resultSize = size;
+ resultMode = parentMode;
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ resultSize = size;
+ if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {
+ resultMode = MeasureSpec.AT_MOST;
+ } else {
+ resultMode = MeasureSpec.UNSPECIFIED;
+ }
+
+ }
+ }
+ //noinspection WrongConstant
+ return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+ }
+
+ /**
+ * Returns the measured width of the given child, plus the additional size of
+ * any insets applied by {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child view to query
+ * @return child's measured width plus <code>ItemDecoration</code> insets
+ *
+ * @see View#getMeasuredWidth()
+ */
+ public int getDecoratedMeasuredWidth(View child) {
+ final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+ return child.getMeasuredWidth() + insets.left + insets.right;
+ }
+
+ /**
+ * Returns the measured height of the given child, plus the additional size of
+ * any insets applied by {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child view to query
+ * @return child's measured height plus <code>ItemDecoration</code> insets
+ *
+ * @see View#getMeasuredHeight()
+ */
+ public int getDecoratedMeasuredHeight(View child) {
+ final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+ return child.getMeasuredHeight() + insets.top + insets.bottom;
+ }
+
+ /**
+ * Lay out the given child view within the RecyclerView using coordinates that
+ * include any current {@link ItemDecoration ItemDecorations}.
+ *
+ * <p>LayoutManagers should prefer working in sizes and coordinates that include
+ * item decoration insets whenever possible. This allows the LayoutManager to effectively
+ * ignore decoration insets within measurement and layout code. See the following
+ * methods:</p>
+ * <ul>
+ * <li>{@link #layoutDecoratedWithMargins(View, int, int, int, int)}</li>
+ * <li>{@link #getDecoratedBoundsWithMargins(View, Rect)}</li>
+ * <li>{@link #measureChild(View, int, int)}</li>
+ * <li>{@link #measureChildWithMargins(View, int, int)}</li>
+ * <li>{@link #getDecoratedLeft(View)}</li>
+ * <li>{@link #getDecoratedTop(View)}</li>
+ * <li>{@link #getDecoratedRight(View)}</li>
+ * <li>{@link #getDecoratedBottom(View)}</li>
+ * <li>{@link #getDecoratedMeasuredWidth(View)}</li>
+ * <li>{@link #getDecoratedMeasuredHeight(View)}</li>
+ * </ul>
+ *
+ * @param child Child to lay out
+ * @param left Left edge, with item decoration insets included
+ * @param top Top edge, with item decoration insets included
+ * @param right Right edge, with item decoration insets included
+ * @param bottom Bottom edge, with item decoration insets included
+ *
+ * @see View#layout(int, int, int, int)
+ * @see #layoutDecoratedWithMargins(View, int, int, int, int)
+ */
+ public void layoutDecorated(View child, int left, int top, int right, int bottom) {
+ final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+ child.layout(left + insets.left, top + insets.top, right - insets.right,
+ bottom - insets.bottom);
+ }
+
+ /**
+ * Lay out the given child view within the RecyclerView using coordinates that
+ * include any current {@link ItemDecoration ItemDecorations} and margins.
+ *
+ * <p>LayoutManagers should prefer working in sizes and coordinates that include
+ * item decoration insets whenever possible. This allows the LayoutManager to effectively
+ * ignore decoration insets within measurement and layout code. See the following
+ * methods:</p>
+ * <ul>
+ * <li>{@link #layoutDecorated(View, int, int, int, int)}</li>
+ * <li>{@link #measureChild(View, int, int)}</li>
+ * <li>{@link #measureChildWithMargins(View, int, int)}</li>
+ * <li>{@link #getDecoratedLeft(View)}</li>
+ * <li>{@link #getDecoratedTop(View)}</li>
+ * <li>{@link #getDecoratedRight(View)}</li>
+ * <li>{@link #getDecoratedBottom(View)}</li>
+ * <li>{@link #getDecoratedMeasuredWidth(View)}</li>
+ * <li>{@link #getDecoratedMeasuredHeight(View)}</li>
+ * </ul>
+ *
+ * @param child Child to lay out
+ * @param left Left edge, with item decoration insets and left margin included
+ * @param top Top edge, with item decoration insets and top margin included
+ * @param right Right edge, with item decoration insets and right margin included
+ * @param bottom Bottom edge, with item decoration insets and bottom margin included
+ *
+ * @see View#layout(int, int, int, int)
+ * @see #layoutDecorated(View, int, int, int, int)
+ */
+ public void layoutDecoratedWithMargins(View child, int left, int top, int right,
+ int bottom) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final Rect insets = lp.mDecorInsets;
+ child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
+ right - insets.right - lp.rightMargin,
+ bottom - insets.bottom - lp.bottomMargin);
+ }
+
+ /**
+ * Calculates the bounding box of the View while taking into account its matrix changes
+ * (translation, scale etc) with respect to the RecyclerView.
+ * <p>
+ * If {@code includeDecorInsets} is {@code true}, they are applied first before applying
+ * the View's matrix so that the decor offsets also go through the same transformation.
+ *
+ * @param child The ItemView whose bounding box should be calculated.
+ * @param includeDecorInsets True if the decor insets should be included in the bounding box
+ * @param out The rectangle into which the output will be written.
+ */
+ public void getTransformedBoundingBox(View child, boolean includeDecorInsets, Rect out) {
+ if (includeDecorInsets) {
+ Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+ out.set(-insets.left, -insets.top,
+ child.getWidth() + insets.right, child.getHeight() + insets.bottom);
+ } else {
+ out.set(0, 0, child.getWidth(), child.getHeight());
+ }
+
+ if (mRecyclerView != null) {
+ final Matrix childMatrix = child.getMatrix();
+ if (childMatrix != null && !childMatrix.isIdentity()) {
+ final RectF tempRectF = mRecyclerView.mTempRectF;
+ tempRectF.set(out);
+ childMatrix.mapRect(tempRectF);
+ out.set(
+ (int) Math.floor(tempRectF.left),
+ (int) Math.floor(tempRectF.top),
+ (int) Math.ceil(tempRectF.right),
+ (int) Math.ceil(tempRectF.bottom)
+ );
+ }
+ }
+ out.offset(child.getLeft(), child.getTop());
+ }
+
+ /**
+ * Returns the bounds of the view including its decoration and margins.
+ *
+ * @param view The view element to check
+ * @param outBounds A rect that will receive the bounds of the element including its
+ * decoration and margins.
+ */
+ public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
+ RecyclerView.getDecoratedBoundsWithMarginsInt(view, outBounds);
+ }
+
+ /**
+ * Returns the left edge of the given child view within its parent, offset by any applied
+ * {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child to query
+ * @return Child left edge with offsets applied
+ * @see #getLeftDecorationWidth(View)
+ */
+ public int getDecoratedLeft(View child) {
+ return child.getLeft() - getLeftDecorationWidth(child);
+ }
+
+ /**
+ * Returns the top edge of the given child view within its parent, offset by any applied
+ * {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child to query
+ * @return Child top edge with offsets applied
+ * @see #getTopDecorationHeight(View)
+ */
+ public int getDecoratedTop(View child) {
+ return child.getTop() - getTopDecorationHeight(child);
+ }
+
+ /**
+ * Returns the right edge of the given child view within its parent, offset by any applied
+ * {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child to query
+ * @return Child right edge with offsets applied
+ * @see #getRightDecorationWidth(View)
+ */
+ public int getDecoratedRight(View child) {
+ return child.getRight() + getRightDecorationWidth(child);
+ }
+
+ /**
+ * Returns the bottom edge of the given child view within its parent, offset by any applied
+ * {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child to query
+ * @return Child bottom edge with offsets applied
+ * @see #getBottomDecorationHeight(View)
+ */
+ public int getDecoratedBottom(View child) {
+ return child.getBottom() + getBottomDecorationHeight(child);
+ }
+
+ /**
+ * Calculates the item decor insets applied to the given child and updates the provided
+ * Rect instance with the inset values.
+ * <ul>
+ * <li>The Rect's left is set to the total width of left decorations.</li>
+ * <li>The Rect's top is set to the total height of top decorations.</li>
+ * <li>The Rect's right is set to the total width of right decorations.</li>
+ * <li>The Rect's bottom is set to total height of bottom decorations.</li>
+ * </ul>
+ * <p>
+ * Note that item decorations are automatically calculated when one of the LayoutManager's
+ * measure child methods is called. If you need to measure the child with custom specs via
+ * {@link View#measure(int, int)}, you can use this method to get decorations.
+ *
+ * @param child The child view whose decorations should be calculated
+ * @param outRect The Rect to hold result values
+ */
+ public void calculateItemDecorationsForChild(View child, Rect outRect) {
+ if (mRecyclerView == null) {
+ outRect.set(0, 0, 0, 0);
+ return;
+ }
+ Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+ outRect.set(insets);
+ }
+
+ /**
+ * Returns the total height of item decorations applied to child's top.
+ * <p>
+ * Note that this value is not updated until the View is measured or
+ * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+ *
+ * @param child Child to query
+ * @return The total height of item decorations applied to the child's top.
+ * @see #getDecoratedTop(View)
+ * @see #calculateItemDecorationsForChild(View, Rect)
+ */
+ public int getTopDecorationHeight(View child) {
+ return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top;
+ }
+
+ /**
+ * Returns the total height of item decorations applied to child's bottom.
+ * <p>
+ * Note that this value is not updated until the View is measured or
+ * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+ *
+ * @param child Child to query
+ * @return The total height of item decorations applied to the child's bottom.
+ * @see #getDecoratedBottom(View)
+ * @see #calculateItemDecorationsForChild(View, Rect)
+ */
+ public int getBottomDecorationHeight(View child) {
+ return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom;
+ }
+
+ /**
+ * Returns the total width of item decorations applied to child's left.
+ * <p>
+ * Note that this value is not updated until the View is measured or
+ * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+ *
+ * @param child Child to query
+ * @return The total width of item decorations applied to the child's left.
+ * @see #getDecoratedLeft(View)
+ * @see #calculateItemDecorationsForChild(View, Rect)
+ */
+ public int getLeftDecorationWidth(View child) {
+ return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left;
+ }
+
+ /**
+ * Returns the total width of item decorations applied to child's right.
+ * <p>
+ * Note that this value is not updated until the View is measured or
+ * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+ *
+ * @param child Child to query
+ * @return The total width of item decorations applied to the child's right.
+ * @see #getDecoratedRight(View)
+ * @see #calculateItemDecorationsForChild(View, Rect)
+ */
+ public int getRightDecorationWidth(View child) {
+ return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right;
+ }
+
+ /**
+ * Called when searching for a focusable view in the given direction has failed
+ * for the current content of the RecyclerView.
+ *
+ * <p>This is the LayoutManager's opportunity to populate views in the given direction
+ * to fulfill the request if it can. The LayoutManager should attach and return
+ * the view to be focused. The default implementation returns null.</p>
+ *
+ * @param focused The currently focused view
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+ * or 0 for not applicable
+ * @param recycler The recycler to use for obtaining views for currently offscreen items
+ * @param state Transient state of RecyclerView
+ * @return The chosen view to be focused
+ */
+ @Nullable
+ public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
+ State state) {
+ return null;
+ }
+
+ /**
+ * This method gives a LayoutManager an opportunity to intercept the initial focus search
+ * before the default behavior of {@link FocusFinder} is used. If this method returns
+ * null FocusFinder will attempt to find a focusable child view. If it fails
+ * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)}
+ * will be called to give the LayoutManager an opportunity to add new views for items
+ * that did not have attached views representing them. The LayoutManager should not add
+ * or remove views from this method.
+ *
+ * @param focused The currently focused view
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+ * @return A descendant view to focus or null to fall back to default behavior.
+ * The default implementation returns null.
+ */
+ public View onInterceptFocusSearch(View focused, int direction) {
+ return null;
+ }
+
+ /**
+ * Called when a child of the RecyclerView wants a particular rectangle to be positioned
+ * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View,
+ * android.graphics.Rect, boolean)} for more details.
+ *
+ * <p>The base implementation will attempt to perform a standard programmatic scroll
+ * to bring the given rect into view, within the padded area of the RecyclerView.</p>
+ *
+ * @param child The direct child making the request.
+ * @param rect The rectangle in the child's coordinates the child
+ * wishes to be on the screen.
+ * @param immediate True to forbid animated or delayed scrolling,
+ * false otherwise
+ * @return Whether the group scrolled to handle the operation
+ */
+ public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect,
+ boolean immediate) {
+ final int parentLeft = getPaddingLeft();
+ final int parentTop = getPaddingTop();
+ final int parentRight = getWidth() - getPaddingRight();
+ final int parentBottom = getHeight() - getPaddingBottom();
+ final int childLeft = child.getLeft() + rect.left - child.getScrollX();
+ final int childTop = child.getTop() + rect.top - child.getScrollY();
+ final int childRight = childLeft + rect.width();
+ final int childBottom = childTop + rect.height();
+
+ final int offScreenLeft = Math.min(0, childLeft - parentLeft);
+ final int offScreenTop = Math.min(0, childTop - parentTop);
+ final int offScreenRight = Math.max(0, childRight - parentRight);
+ final int offScreenBottom = Math.max(0, childBottom - parentBottom);
+
+ // Favor the "start" layout direction over the end when bringing one side or the other
+ // of a large rect into view. If we decide to bring in end because start is already
+ // visible, limit the scroll such that start won't go out of bounds.
+ final int dx;
+ if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ dx = offScreenRight != 0 ? offScreenRight
+ : Math.max(offScreenLeft, childRight - parentRight);
+ } else {
+ dx = offScreenLeft != 0 ? offScreenLeft
+ : Math.min(childLeft - parentLeft, offScreenRight);
+ }
+
+ // Favor bringing the top into view over the bottom. If top is already visible and
+ // we should scroll to make bottom visible, make sure top does not go out of bounds.
+ final int dy = offScreenTop != 0 ? offScreenTop
+ : Math.min(childTop - parentTop, offScreenBottom);
+
+ if (dx != 0 || dy != 0) {
+ if (immediate) {
+ parent.scrollBy(dx, dy);
+ } else {
+ parent.smoothScrollBy(dx, dy);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)}
+ */
+ @Deprecated
+ public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
+ // eat the request if we are in the middle of a scroll or layout
+ return isSmoothScrolling() || parent.isComputingLayout();
+ }
+
+ /**
+ * Called when a descendant view of the RecyclerView requests focus.
+ *
+ * <p>A LayoutManager wishing to keep focused views aligned in a specific
+ * portion of the view may implement that behavior in an override of this method.</p>
+ *
+ * <p>If the LayoutManager executes different behavior that should override the default
+ * behavior of scrolling the focused child on screen instead of running alongside it,
+ * this method should return true.</p>
+ *
+ * @param parent The RecyclerView hosting this LayoutManager
+ * @param state Current state of RecyclerView
+ * @param child Direct child of the RecyclerView containing the newly focused view
+ * @param focused The newly focused view. This may be the same view as child or it may be
+ * null
+ * @return true if the default scroll behavior should be suppressed
+ */
+ public boolean onRequestChildFocus(RecyclerView parent, State state, View child,
+ View focused) {
+ return onRequestChildFocus(parent, child, focused);
+ }
+
+ /**
+ * Called if the RecyclerView this LayoutManager is bound to has a different adapter set.
+ * The LayoutManager may use this opportunity to clear caches and configure state such
+ * that it can relayout appropriately with the new data and potentially new view types.
+ *
+ * <p>The default implementation removes all currently attached views.</p>
+ *
+ * @param oldAdapter The previous adapter instance. Will be null if there was previously no
+ * adapter.
+ * @param newAdapter The new adapter instance. Might be null if
+ * {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}.
+ */
+ public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
+ }
+
+ /**
+ * Called to populate focusable views within the RecyclerView.
+ *
+ * <p>The LayoutManager implementation should return <code>true</code> if the default
+ * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be
+ * suppressed.</p>
+ *
+ * <p>The default implementation returns <code>false</code> to trigger RecyclerView
+ * to fall back to the default ViewGroup behavior.</p>
+ *
+ * @param recyclerView The RecyclerView hosting this LayoutManager
+ * @param views List of output views. This method should add valid focusable views
+ * to this list.
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+ * @param focusableMode The type of focusables to be added.
+ *
+ * @return true to suppress the default behavior, false to add default focusables after
+ * this method returns.
+ *
+ * @see #FOCUSABLES_ALL
+ * @see #FOCUSABLES_TOUCH_MODE
+ */
+ public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views,
+ int direction, int focusableMode) {
+ return false;
+ }
+
+ /**
+ * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving
+ * detailed information on what has actually changed.
+ *
+ * @param recyclerView
+ */
+ public void onItemsChanged(RecyclerView recyclerView) {
+ }
+
+ /**
+ * Called when items have been added to the adapter. The LayoutManager may choose to
+ * requestLayout if the inserted items would require refreshing the currently visible set
+ * of child views. (e.g. currently empty space would be filled by appended items, etc.)
+ *
+ * @param recyclerView
+ * @param positionStart
+ * @param itemCount
+ */
+ public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+ }
+
+ /**
+ * Called when items have been removed from the adapter.
+ *
+ * @param recyclerView
+ * @param positionStart
+ * @param itemCount
+ */
+ public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
+ }
+
+ /**
+ * Called when items have been changed in the adapter.
+ * To receive payload, override {@link #onItemsUpdated(RecyclerView, int, int, Object)}
+ * instead, then this callback will not be invoked.
+ *
+ * @param recyclerView
+ * @param positionStart
+ * @param itemCount
+ */
+ public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
+ }
+
+ /**
+ * Called when items have been changed in the adapter and with optional payload.
+ * Default implementation calls {@link #onItemsUpdated(RecyclerView, int, int)}.
+ *
+ * @param recyclerView
+ * @param positionStart
+ * @param itemCount
+ * @param payload
+ */
+ public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
+ Object payload) {
+ onItemsUpdated(recyclerView, positionStart, itemCount);
+ }
+
+ /**
+ * Called when an item is moved withing the adapter.
+ * <p>
+ * Note that, an item may also change position in response to another ADD/REMOVE/MOVE
+ * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved}
+ * is called.
+ *
+ * @param recyclerView
+ * @param from
+ * @param to
+ * @param itemCount
+ */
+ public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
+
+ }
+
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current state of RecyclerView
+ * @return The horizontal extent of the scrollbar's thumb
+ * @see RecyclerView#computeHorizontalScrollExtent()
+ */
+ public int computeHorizontalScrollExtent(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current State of RecyclerView where you can find total item count
+ * @return The horizontal offset of the scrollbar's thumb
+ * @see RecyclerView#computeHorizontalScrollOffset()
+ */
+ public int computeHorizontalScrollOffset(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current State of RecyclerView where you can find total item count
+ * @return The total horizontal range represented by the vertical scrollbar
+ * @see RecyclerView#computeHorizontalScrollRange()
+ */
+ public int computeHorizontalScrollRange(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current state of RecyclerView
+ * @return The vertical extent of the scrollbar's thumb
+ * @see RecyclerView#computeVerticalScrollExtent()
+ */
+ public int computeVerticalScrollExtent(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current State of RecyclerView where you can find total item count
+ * @return The vertical offset of the scrollbar's thumb
+ * @see RecyclerView#computeVerticalScrollOffset()
+ */
+ public int computeVerticalScrollOffset(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current State of RecyclerView where you can find total item count
+ * @return The total vertical range represented by the vertical scrollbar
+ * @see RecyclerView#computeVerticalScrollRange()
+ */
+ public int computeVerticalScrollRange(State state) {
+ return 0;
+ }
+
+ /**
+ * Measure the attached RecyclerView. Implementations must call
+ * {@link #setMeasuredDimension(int, int)} before returning.
+ *
+ * <p>The default implementation will handle EXACTLY measurements and respect
+ * the minimum width and height properties of the host RecyclerView if measured
+ * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView
+ * will consume all available space.</p>
+ *
+ * @param recycler Recycler
+ * @param state Transient state of RecyclerView
+ * @param widthSpec Width {@link android.view.View.MeasureSpec}
+ * @param heightSpec Height {@link android.view.View.MeasureSpec}
+ */
+ public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
+ mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
+ }
+
+ /**
+ * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the
+ * host RecyclerView.
+ *
+ * @param widthSize Measured width
+ * @param heightSize Measured height
+ */
+ public void setMeasuredDimension(int widthSize, int heightSize) {
+ mRecyclerView.setMeasuredDimension(widthSize, heightSize);
+ }
+
+ /**
+ * @return The host RecyclerView's {@link View#getMinimumWidth()}
+ */
+ public int getMinimumWidth() {
+ return mRecyclerView.getMinimumWidth();
+ }
+
+ /**
+ * @return The host RecyclerView's {@link View#getMinimumHeight()}
+ */
+ public int getMinimumHeight() {
+ return mRecyclerView.getMinimumHeight();
+ }
+ /**
+ * <p>Called when the LayoutManager should save its state. This is a good time to save your
+ * scroll position, configuration and anything else that may be required to restore the same
+ * layout state if the LayoutManager is recreated.</p>
+ * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and
+ * restore. This will let you share information between your LayoutManagers but it is also
+ * your responsibility to make sure they use the same parcelable class.</p>
+ *
+ * @return Necessary information for LayoutManager to be able to restore its state
+ */
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+
+ public void onRestoreInstanceState(Parcelable state) {
+
+ }
+
+ void stopSmoothScroller() {
+ if (mSmoothScroller != null) {
+ mSmoothScroller.stop();
+ }
+ }
+
+ private void onSmoothScrollerStopped(SmoothScroller smoothScroller) {
+ if (mSmoothScroller == smoothScroller) {
+ mSmoothScroller = null;
+ }
+ }
+
+ /**
+ * RecyclerView calls this method to notify LayoutManager that scroll state has changed.
+ *
+ * @param state The new scroll state for RecyclerView
+ */
+ public void onScrollStateChanged(int state) {
+ }
+
+ /**
+ * Removes all views and recycles them using the given recycler.
+ * <p>
+ * If you want to clean cached views as well, you should call {@link Recycler#clear()} too.
+ * <p>
+ * If a View is marked as "ignored", it is not removed nor recycled.
+ *
+ * @param recycler Recycler to use to recycle children
+ * @see #removeAndRecycleView(View, Recycler)
+ * @see #removeAndRecycleViewAt(int, Recycler)
+ * @see #ignoreView(View)
+ */
+ public void removeAndRecycleAllViews(Recycler recycler) {
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final View view = getChildAt(i);
+ if (!getChildViewHolderInt(view).shouldIgnore()) {
+ removeAndRecycleViewAt(i, recycler);
+ }
+ }
+ }
+
+ // called by accessibility delegate
+ void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, info);
+ }
+
+ /**
+ * Called by the AccessibilityDelegate when the information about the current layout should
+ * be populated.
+ * <p>
+ * Default implementation adds a {@link
+ * android.view.accessibility.AccessibilityNodeInfo.CollectionInfo}.
+ * <p>
+ * You should override
+ * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)},
+ * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)},
+ * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and
+ * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for
+ * more accurate accessibility information.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param info The info that should be filled by the LayoutManager
+ * @see View#onInitializeAccessibilityNodeInfo(
+ *android.view.accessibility.AccessibilityNodeInfo)
+ * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)
+ * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)
+ * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)
+ * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)
+ */
+ public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
+ AccessibilityNodeInfo info) {
+ if (mRecyclerView.canScrollVertically(-1)
+ || mRecyclerView.canScrollHorizontally(-1)) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ info.setScrollable(true);
+ }
+ if (mRecyclerView.canScrollVertically(1)
+ || mRecyclerView.canScrollHorizontally(1)) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.setScrollable(true);
+ }
+ final AccessibilityNodeInfo.CollectionInfo collectionInfo =
+ AccessibilityNodeInfo.CollectionInfo
+ .obtain(getRowCountForAccessibility(recycler, state),
+ getColumnCountForAccessibility(recycler, state),
+ isLayoutHierarchical(recycler, state),
+ getSelectionModeForAccessibility(recycler, state));
+ info.setCollectionInfo(collectionInfo);
+ }
+
+ // called by accessibility delegate
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event);
+ }
+
+ /**
+ * Called by the accessibility delegate to initialize an accessibility event.
+ * <p>
+ * Default implementation adds item count and scroll information to the event.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param event The event instance to initialize
+ * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent)
+ */
+ public void onInitializeAccessibilityEvent(Recycler recycler, State state,
+ AccessibilityEvent event) {
+ if (mRecyclerView == null || event == null) {
+ return;
+ }
+ event.setScrollable(mRecyclerView.canScrollVertically(1)
+ || mRecyclerView.canScrollVertically(-1)
+ || mRecyclerView.canScrollHorizontally(-1)
+ || mRecyclerView.canScrollHorizontally(1));
+
+ if (mRecyclerView.mAdapter != null) {
+ event.setItemCount(mRecyclerView.mAdapter.getItemCount());
+ }
+ }
+
+ // called by accessibility delegate
+ void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfo info) {
+ final ViewHolder vh = getChildViewHolderInt(host);
+ // avoid trying to create accessibility node info for removed children
+ if (vh != null && !vh.isRemoved() && !mChildHelper.isHidden(vh.itemView)) {
+ onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler,
+ mRecyclerView.mState, host, info);
+ }
+ }
+
+ /**
+ * Called by the AccessibilityDelegate when the accessibility information for a specific
+ * item should be populated.
+ * <p>
+ * Default implementation adds basic positioning information about the item.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param host The child for which accessibility node info should be populated
+ * @param info The info to fill out about the item
+ * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int,
+ * android.view.accessibility.AccessibilityNodeInfo)
+ */
+ public void onInitializeAccessibilityNodeInfoForItem(Recycler recycler, State state,
+ View host, AccessibilityNodeInfo info) {
+ int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0;
+ int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0;
+ final AccessibilityNodeInfo.CollectionItemInfo itemInfo =
+ AccessibilityNodeInfo.CollectionItemInfo.obtain(rowIndexGuess, 1,
+ columnIndexGuess, 1, false, false);
+ info.setCollectionItemInfo(itemInfo);
+ }
+
+ /**
+ * A LayoutManager can call this method to force RecyclerView to run simple animations in
+ * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data
+ * change).
+ * <p>
+ * Note that, calling this method will not guarantee that RecyclerView will run animations
+ * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will
+ * not run any animations but will still clear this flag after the layout is complete.
+ *
+ */
+ public void requestSimpleAnimationsInNextLayout() {
+ mRequestedSimpleAnimations = true;
+ }
+
+ /**
+ * Returns the selection mode for accessibility. Should be
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE},
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_SINGLE} or
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_MULTIPLE}.
+ * <p>
+ * Default implementation returns
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE}.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @return Selection mode for accessibility. Default implementation returns
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE}.
+ */
+ public int getSelectionModeForAccessibility(Recycler recycler, State state) {
+ return AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE;
+ }
+
+ /**
+ * Returns the number of rows for accessibility.
+ * <p>
+ * Default implementation returns the number of items in the adapter if LayoutManager
+ * supports vertical scrolling or 1 if LayoutManager does not support vertical
+ * scrolling.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @return The number of rows in LayoutManager for accessibility.
+ */
+ public int getRowCountForAccessibility(Recycler recycler, State state) {
+ if (mRecyclerView == null || mRecyclerView.mAdapter == null) {
+ return 1;
+ }
+ return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 1;
+ }
+
+ /**
+ * Returns the number of columns for accessibility.
+ * <p>
+ * Default implementation returns the number of items in the adapter if LayoutManager
+ * supports horizontal scrolling or 1 if LayoutManager does not support horizontal
+ * scrolling.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @return The number of rows in LayoutManager for accessibility.
+ */
+ public int getColumnCountForAccessibility(Recycler recycler, State state) {
+ if (mRecyclerView == null || mRecyclerView.mAdapter == null) {
+ return 1;
+ }
+ return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 1;
+ }
+
+ /**
+ * Returns whether layout is hierarchical or not to be used for accessibility.
+ * <p>
+ * Default implementation returns false.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @return True if layout is hierarchical.
+ */
+ public boolean isLayoutHierarchical(Recycler recycler, State state) {
+ return false;
+ }
+
+ // called by accessibility delegate
+ boolean performAccessibilityAction(int action, Bundle args) {
+ return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState,
+ action, args);
+ }
+
+ /**
+ * Called by AccessibilityDelegate when an action is requested from the RecyclerView.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param action The action to perform
+ * @param args Optional action arguments
+ * @see View#performAccessibilityAction(int, android.os.Bundle)
+ */
+ public boolean performAccessibilityAction(Recycler recycler, State state, int action,
+ Bundle args) {
+ if (mRecyclerView == null) {
+ return false;
+ }
+ int vScroll = 0, hScroll = 0;
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
+ if (mRecyclerView.canScrollVertically(-1)) {
+ vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom());
+ }
+ if (mRecyclerView.canScrollHorizontally(-1)) {
+ hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight());
+ }
+ break;
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
+ if (mRecyclerView.canScrollVertically(1)) {
+ vScroll = getHeight() - getPaddingTop() - getPaddingBottom();
+ }
+ if (mRecyclerView.canScrollHorizontally(1)) {
+ hScroll = getWidth() - getPaddingLeft() - getPaddingRight();
+ }
+ break;
+ }
+ if (vScroll == 0 && hScroll == 0) {
+ return false;
+ }
+ mRecyclerView.scrollBy(hScroll, vScroll);
+ return true;
+ }
+
+ // called by accessibility delegate
+ boolean performAccessibilityActionForItem(View view, int action, Bundle args) {
+ return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState,
+ view, action, args);
+ }
+
+ /**
+ * Called by AccessibilityDelegate when an accessibility action is requested on one of the
+ * children of LayoutManager.
+ * <p>
+ * Default implementation does not do anything.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param view The child view on which the action is performed
+ * @param action The action to perform
+ * @param args Optional action arguments
+ * @return true if action is handled
+ * @see View#performAccessibilityAction(int, android.os.Bundle)
+ */
+ public boolean performAccessibilityActionForItem(Recycler recycler, State state, View view,
+ int action, Bundle args) {
+ return false;
+ }
+
+ /**
+ * Parse the xml attributes to get the most common properties used by layout managers.
+ *
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
+ *
+ * @return an object containing the properties as specified in the attrs.
+ */
+ public static Properties getProperties(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ Properties properties = new Properties();
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
+ defStyleAttr, defStyleRes);
+ properties.orientation = a.getInt(R.styleable.RecyclerView_orientation, VERTICAL);
+ properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1);
+ properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false);
+ properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false);
+ a.recycle();
+ return properties;
+ }
+
+ void setExactMeasureSpecsFrom(RecyclerView recyclerView) {
+ setMeasureSpecs(
+ MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)
+ );
+ }
+
+ /**
+ * Internal API to allow LayoutManagers to be measured twice.
+ * <p>
+ * This is not public because LayoutManagers should be able to handle their layouts in one
+ * pass but it is very convenient to make existing LayoutManagers support wrapping content
+ * when both orientations are undefined.
+ * <p>
+ * This API will be removed after default LayoutManagers properly implement wrap content in
+ * non-scroll orientation.
+ */
+ boolean shouldMeasureTwice() {
+ return false;
+ }
+
+ boolean hasFlexibleChildInBothOrientations() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ if (lp.width < 0 && lp.height < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Some general properties that a LayoutManager may want to use.
+ */
+ public static class Properties {
+ /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */
+ public int orientation;
+ /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */
+ public int spanCount;
+ /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */
+ public boolean reverseLayout;
+ /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */
+ public boolean stackFromEnd;
+ }
+ }
+
+ /**
+ * An ItemDecoration allows the application to add a special drawing and layout offset
+ * to specific item views from the adapter's data set. This can be useful for drawing dividers
+ * between items, highlights, visual grouping boundaries and more.
+ *
+ * <p>All ItemDecorations are drawn in the order they were added, before the item
+ * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
+ * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
+ * RecyclerView.State)}.</p>
+ */
+ public abstract static class ItemDecoration {
+ /**
+ * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
+ * Any content drawn by this method will be drawn before the item views are drawn,
+ * and will thus appear underneath the views.
+ *
+ * @param c Canvas to draw into
+ * @param parent RecyclerView this ItemDecoration is drawing into
+ * @param state The current state of RecyclerView
+ */
+ public void onDraw(Canvas c, RecyclerView parent, State state) {
+ onDraw(c, parent);
+ }
+
+ /**
+ * @deprecated
+ * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
+ */
+ @Deprecated
+ public void onDraw(Canvas c, RecyclerView parent) {
+ }
+
+ /**
+ * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
+ * Any content drawn by this method will be drawn after the item views are drawn
+ * and will thus appear over the views.
+ *
+ * @param c Canvas to draw into
+ * @param parent RecyclerView this ItemDecoration is drawing into
+ * @param state The current state of RecyclerView.
+ */
+ public void onDrawOver(Canvas c, RecyclerView parent, State state) {
+ onDrawOver(c, parent);
+ }
+
+ /**
+ * @deprecated
+ * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
+ */
+ @Deprecated
+ public void onDrawOver(Canvas c, RecyclerView parent) {
+ }
+
+
+ /**
+ * @deprecated
+ * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
+ */
+ @Deprecated
+ public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
+ outRect.set(0, 0, 0, 0);
+ }
+
+ /**
+ * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
+ * the number of pixels that the item view should be inset by, similar to padding or margin.
+ * The default implementation sets the bounds of outRect to 0 and returns.
+ *
+ * <p>
+ * If this ItemDecoration does not affect the positioning of item views, it should set
+ * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
+ * before returning.
+ *
+ * <p>
+ * If you need to access Adapter for additional data, you can call
+ * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
+ * View.
+ *
+ * @param outRect Rect to receive the output.
+ * @param view The child view to decorate
+ * @param parent RecyclerView this ItemDecoration is decorating
+ * @param state The current state of RecyclerView.
+ */
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
+ getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
+ parent);
+ }
+ }
+
+ /**
+ * An OnItemTouchListener allows the application to intercept touch events in progress at the
+ * view hierarchy level of the RecyclerView before those touch events are considered for
+ * RecyclerView's own scrolling behavior.
+ *
+ * <p>This can be useful for applications that wish to implement various forms of gestural
+ * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
+ * a touch interaction already in progress even if the RecyclerView is already handling that
+ * gesture stream itself for the purposes of scrolling.</p>
+ *
+ * @see SimpleOnItemTouchListener
+ */
+ public interface OnItemTouchListener {
+ /**
+ * Silently observe and/or take over touch events sent to the RecyclerView
+ * before they are handled by either the RecyclerView itself or its child views.
+ *
+ * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run
+ * in the order in which each listener was added, before any other touch processing
+ * by the RecyclerView itself or child views occurs.</p>
+ *
+ * @param e MotionEvent describing the touch event. All coordinates are in
+ * the RecyclerView's coordinate system.
+ * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false
+ * to continue with the current behavior and continue observing future events in
+ * the gesture.
+ */
+ boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
+
+ /**
+ * Process a touch event as part of a gesture that was claimed by returning true from
+ * a previous call to {@link #onInterceptTouchEvent}.
+ *
+ * @param e MotionEvent describing the touch event. All coordinates are in
+ * the RecyclerView's coordinate system.
+ */
+ void onTouchEvent(RecyclerView rv, MotionEvent e);
+
+ /**
+ * Called when a child of RecyclerView does not want RecyclerView and its ancestors to
+ * intercept touch events with
+ * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
+ *
+ * @param disallowIntercept True if the child does not want the parent to
+ * intercept touch events.
+ * @see ViewParent#requestDisallowInterceptTouchEvent(boolean)
+ */
+ void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
+ }
+
+ /**
+ * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies
+ * and default return values.
+ * <p>
+ * You may prefer to extend this class if you don't need to override all methods. Another
+ * benefit of using this class is future compatibility. As the interface may change, we'll
+ * always provide a default implementation on this class so that your code won't break when
+ * you update to a new version of the support library.
+ */
+ public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener {
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ }
+ }
+
+
+ /**
+ * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event
+ * has occurred on that RecyclerView.
+ * <p>
+ * @see RecyclerView#addOnScrollListener(OnScrollListener)
+ * @see RecyclerView#clearOnChildAttachStateChangeListeners()
+ *
+ */
+ public abstract static class OnScrollListener {
+ /**
+ * Callback method to be invoked when RecyclerView's scroll state changes.
+ *
+ * @param recyclerView The RecyclerView whose scroll state has changed.
+ * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE},
+ * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
+ */
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
+
+ /**
+ * Callback method to be invoked when the RecyclerView has been scrolled. This will be
+ * called after the scroll has completed.
+ * <p>
+ * This callback will also be called if visible item range changes after a layout
+ * calculation. In that case, dx and dy will be 0.
+ *
+ * @param recyclerView The RecyclerView which scrolled.
+ * @param dx The amount of horizontal scroll.
+ * @param dy The amount of vertical scroll.
+ */
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
+ }
+
+ /**
+ * A RecyclerListener can be set on a RecyclerView to receive messages whenever
+ * a view is recycled.
+ *
+ * @see RecyclerView#setRecyclerListener(RecyclerListener)
+ */
+ public interface RecyclerListener {
+
+ /**
+ * This method is called whenever the view in the ViewHolder is recycled.
+ *
+ * RecyclerView calls this method right before clearing ViewHolder's internal data and
+ * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
+ * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
+ * its adapter position.
+ *
+ * @param holder The ViewHolder containing the view that was recycled
+ */
+ void onViewRecycled(ViewHolder holder);
+ }
+
+ /**
+ * A Listener interface that can be attached to a RecylcerView to get notified
+ * whenever a ViewHolder is attached to or detached from RecyclerView.
+ */
+ public interface OnChildAttachStateChangeListener {
+
+ /**
+ * Called when a view is attached to the RecyclerView.
+ *
+ * @param view The View which is attached to the RecyclerView
+ */
+ void onChildViewAttachedToWindow(View view);
+
+ /**
+ * Called when a view is detached from RecyclerView.
+ *
+ * @param view The View which is being detached from the RecyclerView
+ */
+ void onChildViewDetachedFromWindow(View view);
+ }
+
+ /**
+ * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
+ *
+ * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching
+ * potentially expensive {@link View#findViewById(int)} results.</p>
+ *
+ * <p>While {@link LayoutParams} belong to the {@link LayoutManager},
+ * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
+ * their own custom ViewHolder implementations to store data that makes binding view contents
+ * easier. Implementations should assume that individual item views will hold strong references
+ * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold
+ * strong references to extra off-screen item views for caching purposes</p>
+ */
+ public abstract static class ViewHolder {
+ public final View itemView;
+ WeakReference<RecyclerView> mNestedRecyclerView;
+ int mPosition = NO_POSITION;
+ int mOldPosition = NO_POSITION;
+ long mItemId = NO_ID;
+ int mItemViewType = INVALID_TYPE;
+ int mPreLayoutPosition = NO_POSITION;
+
+ // The item that this holder is shadowing during an item change event/animation
+ ViewHolder mShadowedHolder = null;
+ // The item that is shadowing this holder during an item change event/animation
+ ViewHolder mShadowingHolder = null;
+
+ /**
+ * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
+ * are all valid.
+ */
+ static final int FLAG_BOUND = 1 << 0;
+
+ /**
+ * The data this ViewHolder's view reflects is stale and needs to be rebound
+ * by the adapter. mPosition and mItemId are consistent.
+ */
+ static final int FLAG_UPDATE = 1 << 1;
+
+ /**
+ * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
+ * are not to be trusted and may no longer match the item view type.
+ * This ViewHolder must be fully rebound to different data.
+ */
+ static final int FLAG_INVALID = 1 << 2;
+
+ /**
+ * This ViewHolder points at data that represents an item previously removed from the
+ * data set. Its view may still be used for things like outgoing animations.
+ */
+ static final int FLAG_REMOVED = 1 << 3;
+
+ /**
+ * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
+ * and is intended to keep views around during animations.
+ */
+ static final int FLAG_NOT_RECYCLABLE = 1 << 4;
+
+ /**
+ * This ViewHolder is returned from scrap which means we are expecting an addView call
+ * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until
+ * the end of the layout pass and then recycled by RecyclerView if it is not added back to
+ * the RecyclerView.
+ */
+ static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;
+
+ /**
+ * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove
+ * it unless LayoutManager is replaced.
+ * It is still fully visible to the LayoutManager.
+ */
+ static final int FLAG_IGNORE = 1 << 7;
+
+ /**
+ * When the View is detached form the parent, we set this flag so that we can take correct
+ * action when we need to remove it or add it back.
+ */
+ static final int FLAG_TMP_DETACHED = 1 << 8;
+
+ /**
+ * Set when we can no longer determine the adapter position of this ViewHolder until it is
+ * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is
+ * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon
+ * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is
+ * re-calculated.
+ */
+ static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9;
+
+ /**
+ * Set when a addChangePayload(null) is called
+ */
+ static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10;
+
+ /**
+ * Used by ItemAnimator when a ViewHolder's position changes
+ */
+ static final int FLAG_MOVED = 1 << 11;
+
+ /**
+ * Used by ItemAnimator when a ViewHolder appears in pre-layout
+ */
+ static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12;
+
+ static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1;
+
+ /**
+ * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from
+ * hidden list (as if it was scrap) without being recycled in between.
+ *
+ * When a ViewHolder is hidden, there are 2 paths it can be re-used:
+ * a) Animation ends, view is recycled and used from the recycle pool.
+ * b) LayoutManager asks for the View for that position while the ViewHolder is hidden.
+ *
+ * This flag is used to represent "case b" where the ViewHolder is reused without being
+ * recycled (thus "bounced" from the hidden list). This state requires special handling
+ * because the ViewHolder must be added to pre layout maps for animations as if it was
+ * already there.
+ */
+ static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13;
+
+ private int mFlags;
+
+ private static final List<Object> FULLUPDATE_PAYLOADS = Collections.EMPTY_LIST;
+
+ List<Object> mPayloads = null;
+ List<Object> mUnmodifiedPayloads = null;
+
+ private int mIsRecyclableCount = 0;
+
+ // If non-null, view is currently considered scrap and may be reused for other data by the
+ // scrap container.
+ private Recycler mScrapContainer = null;
+ // Keeps whether this ViewHolder lives in Change scrap or Attached scrap
+ private boolean mInChangeScrap = false;
+
+ // Saves isImportantForAccessibility value for the view item while it's in hidden state and
+ // marked as unimportant for accessibility.
+ private int mWasImportantForAccessibilityBeforeHidden =
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ // set if we defer the accessibility state change of the view holder
+ @VisibleForTesting
+ int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
+
+ /**
+ * Is set when VH is bound from the adapter and cleaned right before it is sent to
+ * {@link RecycledViewPool}.
+ */
+ RecyclerView mOwnerRecyclerView;
+
+ public ViewHolder(View itemView) {
+ if (itemView == null) {
+ throw new IllegalArgumentException("itemView may not be null");
+ }
+ this.itemView = itemView;
+ }
+
+ void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) {
+ addFlags(ViewHolder.FLAG_REMOVED);
+ offsetPosition(offset, applyToPreLayout);
+ mPosition = mNewPosition;
+ }
+
+ void offsetPosition(int offset, boolean applyToPreLayout) {
+ if (mOldPosition == NO_POSITION) {
+ mOldPosition = mPosition;
+ }
+ if (mPreLayoutPosition == NO_POSITION) {
+ mPreLayoutPosition = mPosition;
+ }
+ if (applyToPreLayout) {
+ mPreLayoutPosition += offset;
+ }
+ mPosition += offset;
+ if (itemView.getLayoutParams() != null) {
+ ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true;
+ }
+ }
+
+ void clearOldPosition() {
+ mOldPosition = NO_POSITION;
+ mPreLayoutPosition = NO_POSITION;
+ }
+
+ void saveOldPosition() {
+ if (mOldPosition == NO_POSITION) {
+ mOldPosition = mPosition;
+ }
+ }
+
+ boolean shouldIgnore() {
+ return (mFlags & FLAG_IGNORE) != 0;
+ }
+
+ /**
+ * @deprecated This method is deprecated because its meaning is ambiguous due to the async
+ * handling of adapter updates. Please use {@link #getLayoutPosition()} or
+ * {@link #getAdapterPosition()} depending on your use case.
+ *
+ * @see #getLayoutPosition()
+ * @see #getAdapterPosition()
+ */
+ @Deprecated
+ public final int getPosition() {
+ return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
+ }
+
+ /**
+ * Returns the position of the ViewHolder in terms of the latest layout pass.
+ * <p>
+ * This position is mostly used by RecyclerView components to be consistent while
+ * RecyclerView lazily processes adapter updates.
+ * <p>
+ * For performance and animation reasons, RecyclerView batches all adapter updates until the
+ * next layout pass. This may cause mismatches between the Adapter position of the item and
+ * the position it had in the latest layout calculations.
+ * <p>
+ * LayoutManagers should always call this method while doing calculations based on item
+ * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State},
+ * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position
+ * of the item.
+ * <p>
+ * If LayoutManager needs to call an external method that requires the adapter position of
+ * the item, it can use {@link #getAdapterPosition()} or
+ * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}.
+ *
+ * @return Returns the adapter position of the ViewHolder in the latest layout pass.
+ * @see #getAdapterPosition()
+ */
+ public final int getLayoutPosition() {
+ return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
+ }
+
+ /**
+ * Returns the Adapter position of the item represented by this ViewHolder.
+ * <p>
+ * Note that this might be different than the {@link #getLayoutPosition()} if there are
+ * pending adapter updates but a new layout pass has not happened yet.
+ * <p>
+ * RecyclerView does not handle any adapter updates until the next layout traversal. This
+ * may create temporary inconsistencies between what user sees on the screen and what
+ * adapter contents have. This inconsistency is not important since it will be less than
+ * 16ms but it might be a problem if you want to use ViewHolder position to access the
+ * adapter. Sometimes, you may need to get the exact adapter position to do
+ * some actions in response to user events. In that case, you should use this method which
+ * will calculate the Adapter position of the ViewHolder.
+ * <p>
+ * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
+ * next layout pass, the return value of this method will be {@link #NO_POSITION}.
+ *
+ * @return The adapter position of the item if it still exists in the adapter.
+ * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
+ * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
+ * layout pass or the ViewHolder has already been recycled.
+ */
+ public final int getAdapterPosition() {
+ if (mOwnerRecyclerView == null) {
+ return NO_POSITION;
+ }
+ return mOwnerRecyclerView.getAdapterPositionFor(this);
+ }
+
+ /**
+ * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders
+ * to perform animations.
+ * <p>
+ * If a ViewHolder was laid out in the previous onLayout call, old position will keep its
+ * adapter index in the previous layout.
+ *
+ * @return The previous adapter index of the Item represented by this ViewHolder or
+ * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is
+ * complete).
+ */
+ public final int getOldPosition() {
+ return mOldPosition;
+ }
+
+ /**
+ * Returns The itemId represented by this ViewHolder.
+ *
+ * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID}
+ * otherwise
+ */
+ public final long getItemId() {
+ return mItemId;
+ }
+
+ /**
+ * @return The view type of this ViewHolder.
+ */
+ public final int getItemViewType() {
+ return mItemViewType;
+ }
+
+ boolean isScrap() {
+ return mScrapContainer != null;
+ }
+
+ void unScrap() {
+ mScrapContainer.unscrapView(this);
+ }
+
+ boolean wasReturnedFromScrap() {
+ return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0;
+ }
+
+ void clearReturnedFromScrapFlag() {
+ mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP;
+ }
+
+ void clearTmpDetachFlag() {
+ mFlags = mFlags & ~FLAG_TMP_DETACHED;
+ }
+
+ void stopIgnoring() {
+ mFlags = mFlags & ~FLAG_IGNORE;
+ }
+
+ void setScrapContainer(Recycler recycler, boolean isChangeScrap) {
+ mScrapContainer = recycler;
+ mInChangeScrap = isChangeScrap;
+ }
+
+ boolean isInvalid() {
+ return (mFlags & FLAG_INVALID) != 0;
+ }
+
+ boolean needsUpdate() {
+ return (mFlags & FLAG_UPDATE) != 0;
+ }
+
+ boolean isBound() {
+ return (mFlags & FLAG_BOUND) != 0;
+ }
+
+ boolean isRemoved() {
+ return (mFlags & FLAG_REMOVED) != 0;
+ }
+
+ boolean hasAnyOfTheFlags(int flags) {
+ return (mFlags & flags) != 0;
+ }
+
+ boolean isTmpDetached() {
+ return (mFlags & FLAG_TMP_DETACHED) != 0;
+ }
+
+ boolean isAdapterPositionUnknown() {
+ return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid();
+ }
+
+ void setFlags(int flags, int mask) {
+ mFlags = (mFlags & ~mask) | (flags & mask);
+ }
+
+ void addFlags(int flags) {
+ mFlags |= flags;
+ }
+
+ void addChangePayload(Object payload) {
+ if (payload == null) {
+ addFlags(FLAG_ADAPTER_FULLUPDATE);
+ } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
+ createPayloadsIfNeeded();
+ mPayloads.add(payload);
+ }
+ }
+
+ private void createPayloadsIfNeeded() {
+ if (mPayloads == null) {
+ mPayloads = new ArrayList<Object>();
+ mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads);
+ }
+ }
+
+ void clearPayload() {
+ if (mPayloads != null) {
+ mPayloads.clear();
+ }
+ mFlags = mFlags & ~FLAG_ADAPTER_FULLUPDATE;
+ }
+
+ List<Object> getUnmodifiedPayloads() {
+ if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
+ if (mPayloads == null || mPayloads.size() == 0) {
+ // Initial state, no update being called.
+ return FULLUPDATE_PAYLOADS;
+ }
+ // there are none-null payloads
+ return mUnmodifiedPayloads;
+ } else {
+ // a full update has been called.
+ return FULLUPDATE_PAYLOADS;
+ }
+ }
+
+ void resetInternal() {
+ mFlags = 0;
+ mPosition = NO_POSITION;
+ mOldPosition = NO_POSITION;
+ mItemId = NO_ID;
+ mPreLayoutPosition = NO_POSITION;
+ mIsRecyclableCount = 0;
+ mShadowedHolder = null;
+ mShadowingHolder = null;
+ clearPayload();
+ mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
+ clearNestedRecyclerViewIfNotNested(this);
+ }
+
+ /**
+ * Called when the child view enters the hidden state
+ */
+ private void onEnteredHiddenState(RecyclerView parent) {
+ // While the view item is in hidden state, make it invisible for the accessibility.
+ mWasImportantForAccessibilityBeforeHidden =
+ itemView.getImportantForAccessibility();
+ parent.setChildImportantForAccessibilityInternal(this,
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ }
+
+ /**
+ * Called when the child view leaves the hidden state
+ */
+ private void onLeftHiddenState(RecyclerView parent) {
+ parent.setChildImportantForAccessibilityInternal(this,
+ mWasImportantForAccessibilityBeforeHidden);
+ mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ViewHolder{"
+ + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId
+ + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
+ if (isScrap()) {
+ sb.append(" scrap ")
+ .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]");
+ }
+ if (isInvalid()) sb.append(" invalid");
+ if (!isBound()) sb.append(" unbound");
+ if (needsUpdate()) sb.append(" update");
+ if (isRemoved()) sb.append(" removed");
+ if (shouldIgnore()) sb.append(" ignored");
+ if (isTmpDetached()) sb.append(" tmpDetached");
+ if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
+ if (isAdapterPositionUnknown()) sb.append(" undefined adapter position");
+
+ if (itemView.getParent() == null) sb.append(" no parent");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Informs the recycler whether this item can be recycled. Views which are not
+ * recyclable will not be reused for other items until setIsRecyclable() is
+ * later set to true. Calls to setIsRecyclable() should always be paired (one
+ * call to setIsRecyclabe(false) should always be matched with a later call to
+ * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally
+ * reference-counted.
+ *
+ * @param recyclable Whether this item is available to be recycled. Default value
+ * is true.
+ *
+ * @see #isRecyclable()
+ */
+ public final void setIsRecyclable(boolean recyclable) {
+ mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1;
+ if (mIsRecyclableCount < 0) {
+ mIsRecyclableCount = 0;
+ if (DEBUG) {
+ throw new RuntimeException("isRecyclable decremented below 0: "
+ + "unmatched pair of setIsRecyable() calls for " + this);
+ }
+ Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: "
+ + "unmatched pair of setIsRecyable() calls for " + this);
+ } else if (!recyclable && mIsRecyclableCount == 1) {
+ mFlags |= FLAG_NOT_RECYCLABLE;
+ } else if (recyclable && mIsRecyclableCount == 0) {
+ mFlags &= ~FLAG_NOT_RECYCLABLE;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this);
+ }
+ }
+
+ /**
+ * @return true if this item is available to be recycled, false otherwise.
+ *
+ * @see #setIsRecyclable(boolean)
+ */
+ public final boolean isRecyclable() {
+ return (mFlags & FLAG_NOT_RECYCLABLE) == 0
+ && !itemView.hasTransientState();
+ }
+
+ /**
+ * Returns whether we have animations referring to this view holder or not.
+ * This is similar to isRecyclable flag but does not check transient state.
+ */
+ private boolean shouldBeKeptAsChild() {
+ return (mFlags & FLAG_NOT_RECYCLABLE) != 0;
+ }
+
+ /**
+ * @return True if ViewHolder is not referenced by RecyclerView animations but has
+ * transient state which will prevent it from being recycled.
+ */
+ private boolean doesTransientStatePreventRecycling() {
+ return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && itemView.hasTransientState();
+ }
+
+ boolean isUpdated() {
+ return (mFlags & FLAG_UPDATE) != 0;
+ }
+ }
+
+ /**
+ * This method is here so that we can control the important for a11y changes and test it.
+ */
+ @VisibleForTesting
+ boolean setChildImportantForAccessibilityInternal(ViewHolder viewHolder,
+ int importantForAccessibility) {
+ if (isComputingLayout()) {
+ viewHolder.mPendingAccessibilityState = importantForAccessibility;
+ mPendingAccessibilityImportanceChange.add(viewHolder);
+ return false;
+ }
+ viewHolder.itemView.setImportantForAccessibility(importantForAccessibility);
+ return true;
+ }
+
+ void dispatchPendingImportantForAccessibilityChanges() {
+ for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i--) {
+ ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i);
+ if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) {
+ continue;
+ }
+ int state = viewHolder.mPendingAccessibilityState;
+ if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) {
+ //noinspection WrongConstant
+ viewHolder.itemView.setImportantForAccessibility(state);
+ viewHolder.mPendingAccessibilityState =
+ ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET;
+ }
+ }
+ mPendingAccessibilityImportanceChange.clear();
+ }
+
+ int getAdapterPositionFor(ViewHolder viewHolder) {
+ if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
+ | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)
+ || !viewHolder.isBound()) {
+ return RecyclerView.NO_POSITION;
+ }
+ return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
+ }
+
+ /**
+ * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of
+ * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged
+ * to create their own subclass of this <code>LayoutParams</code> class
+ * to store any additional required per-child view metadata about the layout.
+ */
+ public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ViewHolder mViewHolder;
+ final Rect mDecorInsets = new Rect();
+ boolean mInsetsDirty = true;
+ // Flag is set to true if the view is bound while it is detached from RV.
+ // In this case, we need to manually call invalidate after view is added to guarantee that
+ // invalidation is populated through the View hierarchy
+ boolean mPendingInvalidate = false;
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super((ViewGroup.LayoutParams) source);
+ }
+
+ /**
+ * Returns true if the view this LayoutParams is attached to needs to have its content
+ * updated from the corresponding adapter.
+ *
+ * @return true if the view should have its content updated
+ */
+ public boolean viewNeedsUpdate() {
+ return mViewHolder.needsUpdate();
+ }
+
+ /**
+ * Returns true if the view this LayoutParams is attached to is now representing
+ * potentially invalid data. A LayoutManager should scrap/recycle it.
+ *
+ * @return true if the view is invalid
+ */
+ public boolean isViewInvalid() {
+ return mViewHolder.isInvalid();
+ }
+
+ /**
+ * Returns true if the adapter data item corresponding to the view this LayoutParams
+ * is attached to has been removed from the data set. A LayoutManager may choose to
+ * treat it differently in order to animate its outgoing or disappearing state.
+ *
+ * @return true if the item the view corresponds to was removed from the data set
+ */
+ public boolean isItemRemoved() {
+ return mViewHolder.isRemoved();
+ }
+
+ /**
+ * Returns true if the adapter data item corresponding to the view this LayoutParams
+ * is attached to has been changed in the data set. A LayoutManager may choose to
+ * treat it differently in order to animate its changing state.
+ *
+ * @return true if the item the view corresponds to was changed in the data set
+ */
+ public boolean isItemChanged() {
+ return mViewHolder.isUpdated();
+ }
+
+ /**
+ * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()}
+ */
+ @Deprecated
+ public int getViewPosition() {
+ return mViewHolder.getPosition();
+ }
+
+ /**
+ * Returns the adapter position that the view this LayoutParams is attached to corresponds
+ * to as of latest layout calculation.
+ *
+ * @return the adapter position this view as of latest layout pass
+ */
+ public int getViewLayoutPosition() {
+ return mViewHolder.getLayoutPosition();
+ }
+
+ /**
+ * Returns the up-to-date adapter position that the view this LayoutParams is attached to
+ * corresponds to.
+ *
+ * @return the up-to-date adapter position this view. It may return
+ * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or
+ * its up-to-date position cannot be calculated.
+ */
+ public int getViewAdapterPosition() {
+ return mViewHolder.getAdapterPosition();
+ }
+ }
+
+ /**
+ * Observer base class for watching changes to an {@link Adapter}.
+ * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}.
+ */
+ public abstract static class AdapterDataObserver {
+ public void onChanged() {
+ // Do nothing
+ }
+
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ // do nothing
+ }
+
+ public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ // fallback to onItemRangeChanged(positionStart, itemCount) if app
+ // does not override this method.
+ onItemRangeChanged(positionStart, itemCount);
+ }
+
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ // do nothing
+ }
+
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ // do nothing
+ }
+
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ // do nothing
+ }
+ }
+
+ /**
+ * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and
+ * provides methods to trigger a programmatic scroll.</p>
+ *
+ * @see LinearSmoothScroller
+ */
+ public abstract static class SmoothScroller {
+
+ private int mTargetPosition = RecyclerView.NO_POSITION;
+
+ private RecyclerView mRecyclerView;
+
+ private LayoutManager mLayoutManager;
+
+ private boolean mPendingInitialRun;
+
+ private boolean mRunning;
+
+ private View mTargetView;
+
+ private final Action mRecyclingAction;
+
+ public SmoothScroller() {
+ mRecyclingAction = new Action(0, 0);
+ }
+
+ /**
+ * Starts a smooth scroll for the given target position.
+ * <p>In each animation step, {@link RecyclerView} will check
+ * for the target view and call either
+ * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
+ * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until
+ * SmoothScroller is stopped.</p>
+ *
+ * <p>Note that if RecyclerView finds the target view, it will automatically stop the
+ * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will
+ * stop calling SmoothScroller in each animation step.</p>
+ */
+ void start(RecyclerView recyclerView, LayoutManager layoutManager) {
+ mRecyclerView = recyclerView;
+ mLayoutManager = layoutManager;
+ if (mTargetPosition == RecyclerView.NO_POSITION) {
+ throw new IllegalArgumentException("Invalid target position");
+ }
+ mRecyclerView.mState.mTargetPosition = mTargetPosition;
+ mRunning = true;
+ mPendingInitialRun = true;
+ mTargetView = findViewByPosition(getTargetPosition());
+ onStart();
+ mRecyclerView.mViewFlinger.postOnAnimation();
+ }
+
+ public void setTargetPosition(int targetPosition) {
+ mTargetPosition = targetPosition;
+ }
+
+ /**
+ * @return The LayoutManager to which this SmoothScroller is attached. Will return
+ * <code>null</code> after the SmoothScroller is stopped.
+ */
+ @Nullable
+ public LayoutManager getLayoutManager() {
+ return mLayoutManager;
+ }
+
+ /**
+ * Stops running the SmoothScroller in each animation callback. Note that this does not
+ * cancel any existing {@link Action} updated by
+ * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
+ * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}.
+ */
+ protected final void stop() {
+ if (!mRunning) {
+ return;
+ }
+ onStop();
+ mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;
+ mTargetView = null;
+ mTargetPosition = RecyclerView.NO_POSITION;
+ mPendingInitialRun = false;
+ mRunning = false;
+ // trigger a cleanup
+ mLayoutManager.onSmoothScrollerStopped(this);
+ // clear references to avoid any potential leak by a custom smooth scroller
+ mLayoutManager = null;
+ mRecyclerView = null;
+ }
+
+ /**
+ * Returns true if SmoothScroller has been started but has not received the first
+ * animation
+ * callback yet.
+ *
+ * @return True if this SmoothScroller is waiting to start
+ */
+ public boolean isPendingInitialRun() {
+ return mPendingInitialRun;
+ }
+
+
+ /**
+ * @return True if SmoothScroller is currently active
+ */
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /**
+ * Returns the adapter position of the target item
+ *
+ * @return Adapter position of the target item or
+ * {@link RecyclerView#NO_POSITION} if no target view is set.
+ */
+ public int getTargetPosition() {
+ return mTargetPosition;
+ }
+
+ private void onAnimation(int dx, int dy) {
+ final RecyclerView recyclerView = mRecyclerView;
+ if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) {
+ stop();
+ }
+ mPendingInitialRun = false;
+ if (mTargetView != null) {
+ // verify target position
+ if (getChildPosition(mTargetView) == mTargetPosition) {
+ onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);
+ mRecyclingAction.runIfNecessary(recyclerView);
+ stop();
+ } else {
+ Log.e(TAG, "Passed over target position while smooth scrolling.");
+ mTargetView = null;
+ }
+ }
+ if (mRunning) {
+ onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction);
+ boolean hadJumpTarget = mRecyclingAction.hasJumpTarget();
+ mRecyclingAction.runIfNecessary(recyclerView);
+ if (hadJumpTarget) {
+ // It is not stopped so needs to be restarted
+ if (mRunning) {
+ mPendingInitialRun = true;
+ recyclerView.mViewFlinger.postOnAnimation();
+ } else {
+ stop(); // done
+ }
+ }
+ }
+ }
+
+ /**
+ * @see RecyclerView#getChildLayoutPosition(android.view.View)
+ */
+ public int getChildPosition(View view) {
+ return mRecyclerView.getChildLayoutPosition(view);
+ }
+
+ /**
+ * @see RecyclerView.LayoutManager#getChildCount()
+ */
+ public int getChildCount() {
+ return mRecyclerView.mLayout.getChildCount();
+ }
+
+ /**
+ * @see RecyclerView.LayoutManager#findViewByPosition(int)
+ */
+ public View findViewByPosition(int position) {
+ return mRecyclerView.mLayout.findViewByPosition(position);
+ }
+
+ /**
+ * @see RecyclerView#scrollToPosition(int)
+ * @deprecated Use {@link Action#jumpTo(int)}.
+ */
+ @Deprecated
+ public void instantScrollToPosition(int position) {
+ mRecyclerView.scrollToPosition(position);
+ }
+
+ protected void onChildAttachedToWindow(View child) {
+ if (getChildPosition(child) == getTargetPosition()) {
+ mTargetView = child;
+ if (DEBUG) {
+ Log.d(TAG, "smooth scroll target view has been attached");
+ }
+ }
+ }
+
+ /**
+ * Normalizes the vector.
+ * @param scrollVector The vector that points to the target scroll position
+ */
+ protected void normalize(PointF scrollVector) {
+ final double magnitude = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y
+ * scrollVector.y);
+ scrollVector.x /= magnitude;
+ scrollVector.y /= magnitude;
+ }
+
+ /**
+ * Called when smooth scroll is started. This might be a good time to do setup.
+ */
+ protected abstract void onStart();
+
+ /**
+ * Called when smooth scroller is stopped. This is a good place to cleanup your state etc.
+ * @see #stop()
+ */
+ protected abstract void onStop();
+
+ /**
+ * <p>RecyclerView will call this method each time it scrolls until it can find the target
+ * position in the layout.</p>
+ * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the
+ * provided {@link Action} to define the next scroll.</p>
+ *
+ * @param dx Last scroll amount horizontally
+ * @param dy Last scroll amount vertically
+ * @param state Transient state of RecyclerView
+ * @param action If you want to trigger a new smooth scroll and cancel the previous one,
+ * update this object.
+ */
+ protected abstract void onSeekTargetStep(int dx, int dy, State state, Action action);
+
+ /**
+ * Called when the target position is laid out. This is the last callback SmoothScroller
+ * will receive and it should update the provided {@link Action} to define the scroll
+ * details towards the target view.
+ * @param targetView The view element which render the target position.
+ * @param state Transient state of RecyclerView
+ * @param action Action instance that you should update to define final scroll action
+ * towards the targetView
+ */
+ protected abstract void onTargetFound(View targetView, State state, Action action);
+
+ /**
+ * Holds information about a smooth scroll request by a {@link SmoothScroller}.
+ */
+ public static class Action {
+
+ public static final int UNDEFINED_DURATION = Integer.MIN_VALUE;
+
+ private int mDx;
+
+ private int mDy;
+
+ private int mDuration;
+
+ private int mJumpToPosition = NO_POSITION;
+
+ private Interpolator mInterpolator;
+
+ private boolean mChanged = false;
+
+ // we track this variable to inform custom implementer if they are updating the action
+ // in every animation callback
+ private int mConsecutiveUpdates = 0;
+
+ /**
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ */
+ public Action(int dx, int dy) {
+ this(dx, dy, UNDEFINED_DURATION, null);
+ }
+
+ /**
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ * @param duration Duration of the animation in milliseconds
+ */
+ public Action(int dx, int dy, int duration) {
+ this(dx, dy, duration, null);
+ }
+
+ /**
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ * @param duration Duration of the animation in milliseconds
+ * @param interpolator Interpolator to be used when calculating scroll position in each
+ * animation step
+ */
+ public Action(int dx, int dy, int duration, Interpolator interpolator) {
+ mDx = dx;
+ mDy = dy;
+ mDuration = duration;
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Instead of specifying pixels to scroll, use the target position to jump using
+ * {@link RecyclerView#scrollToPosition(int)}.
+ * <p>
+ * You may prefer using this method if scroll target is really far away and you prefer
+ * to jump to a location and smooth scroll afterwards.
+ * <p>
+ * Note that calling this method takes priority over other update methods such as
+ * {@link #update(int, int, int, Interpolator)}, {@link #setX(float)},
+ * {@link #setY(float)} and #{@link #setInterpolator(Interpolator)}. If you call
+ * {@link #jumpTo(int)}, the other changes will not be considered for this animation
+ * frame.
+ *
+ * @param targetPosition The target item position to scroll to using instant scrolling.
+ */
+ public void jumpTo(int targetPosition) {
+ mJumpToPosition = targetPosition;
+ }
+
+ boolean hasJumpTarget() {
+ return mJumpToPosition >= 0;
+ }
+
+ void runIfNecessary(RecyclerView recyclerView) {
+ if (mJumpToPosition >= 0) {
+ final int position = mJumpToPosition;
+ mJumpToPosition = NO_POSITION;
+ recyclerView.jumpToPositionForSmoothScroller(position);
+ mChanged = false;
+ return;
+ }
+ if (mChanged) {
+ validate();
+ if (mInterpolator == null) {
+ if (mDuration == UNDEFINED_DURATION) {
+ recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy);
+ } else {
+ recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration);
+ }
+ } else {
+ recyclerView.mViewFlinger.smoothScrollBy(
+ mDx, mDy, mDuration, mInterpolator);
+ }
+ mConsecutiveUpdates++;
+ if (mConsecutiveUpdates > 10) {
+ // A new action is being set in every animation step. This looks like a bad
+ // implementation. Inform developer.
+ Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure"
+ + " you are not changing it unless necessary");
+ }
+ mChanged = false;
+ } else {
+ mConsecutiveUpdates = 0;
+ }
+ }
+
+ private void validate() {
+ if (mInterpolator != null && mDuration < 1) {
+ throw new IllegalStateException("If you provide an interpolator, you must"
+ + " set a positive duration");
+ } else if (mDuration < 1) {
+ throw new IllegalStateException("Scroll duration must be a positive number");
+ }
+ }
+
+ public int getDx() {
+ return mDx;
+ }
+
+ public void setDx(int dx) {
+ mChanged = true;
+ mDx = dx;
+ }
+
+ public int getDy() {
+ return mDy;
+ }
+
+ public void setDy(int dy) {
+ mChanged = true;
+ mDy = dy;
+ }
+
+ public int getDuration() {
+ return mDuration;
+ }
+
+ public void setDuration(int duration) {
+ mChanged = true;
+ mDuration = duration;
+ }
+
+ public Interpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * Sets the interpolator to calculate scroll steps
+ * @param interpolator The interpolator to use. If you specify an interpolator, you must
+ * also set the duration.
+ * @see #setDuration(int)
+ */
+ public void setInterpolator(Interpolator interpolator) {
+ mChanged = true;
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Updates the action with given parameters.
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ * @param duration Duration of the animation in milliseconds
+ * @param interpolator Interpolator to be used when calculating scroll position in each
+ * animation step
+ */
+ public void update(int dx, int dy, int duration, Interpolator interpolator) {
+ mDx = dx;
+ mDy = dy;
+ mDuration = duration;
+ mInterpolator = interpolator;
+ mChanged = true;
+ }
+ }
+
+ /**
+ * An interface which is optionally implemented by custom {@link RecyclerView.LayoutManager}
+ * to provide a hint to a {@link SmoothScroller} about the location of the target position.
+ */
+ public interface ScrollVectorProvider {
+ /**
+ * Should calculate the vector that points to the direction where the target position
+ * can be found.
+ * <p>
+ * This method is used by the {@link LinearSmoothScroller} to initiate a scroll towards
+ * the target position.
+ * <p>
+ * The magnitude of the vector is not important. It is always normalized before being
+ * used by the {@link LinearSmoothScroller}.
+ * <p>
+ * LayoutManager should not check whether the position exists in the adapter or not.
+ *
+ * @param targetPosition the target position to which the returned vector should point
+ *
+ * @return the scroll vector for a given position.
+ */
+ PointF computeScrollVectorForPosition(int targetPosition);
+ }
+ }
+
+ static class AdapterDataObservable extends Observable<AdapterDataObserver> {
+ public boolean hasObservers() {
+ return !mObservers.isEmpty();
+ }
+
+ public void notifyChanged() {
+ // since onChanged() is implemented by the app, it could do anything, including
+ // removing itself from {@link mObservers} - and that could cause problems if
+ // an iterator is used on the ArrayList {@link mObservers}.
+ // to avoid such problems, just march thru the list in the reverse order.
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onChanged();
+ }
+ }
+
+ public void notifyItemRangeChanged(int positionStart, int itemCount) {
+ notifyItemRangeChanged(positionStart, itemCount, null);
+ }
+
+ public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ // since onItemRangeChanged() is implemented by the app, it could do anything, including
+ // removing itself from {@link mObservers} - and that could cause problems if
+ // an iterator is used on the ArrayList {@link mObservers}.
+ // to avoid such problems, just march thru the list in the reverse order.
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
+ }
+ }
+
+ public void notifyItemRangeInserted(int positionStart, int itemCount) {
+ // since onItemRangeInserted() is implemented by the app, it could do anything,
+ // including removing itself from {@link mObservers} - and that could cause problems if
+ // an iterator is used on the ArrayList {@link mObservers}.
+ // to avoid such problems, just march thru the list in the reverse order.
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
+ }
+ }
+
+ public void notifyItemRangeRemoved(int positionStart, int itemCount) {
+ // since onItemRangeRemoved() is implemented by the app, it could do anything, including
+ // removing itself from {@link mObservers} - and that could cause problems if
+ // an iterator is used on the ArrayList {@link mObservers}.
+ // to avoid such problems, just march thru the list in the reverse order.
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
+ }
+ }
+
+ public void notifyItemMoved(int fromPosition, int toPosition) {
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
+ }
+ }
+ }
+
+ /**
+ * This is public so that the CREATOR can be access on cold launch.
+ * @hide
+ */
+ public static class SavedState extends AbsSavedState {
+
+ Parcelable mLayoutState;
+
+ /**
+ * called by CREATOR
+ */
+ SavedState(Parcel in) {
+ super(in);
+ mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader());
+ }
+
+ /**
+ * Called by onSaveInstanceState
+ */
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(mLayoutState, 0);
+ }
+
+ void copyFrom(SavedState other) {
+ mLayoutState = other.mLayoutState;
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+ /**
+ * <p>Contains useful information about the current RecyclerView state like target scroll
+ * position or view focus. State object can also keep arbitrary data, identified by resource
+ * ids.</p>
+ * <p>Often times, RecyclerView components will need to pass information between each other.
+ * To provide a well defined data bus between components, RecyclerView passes the same State
+ * object to component callbacks and these components can use it to exchange data.</p>
+ * <p>If you implement custom components, you can use State's put/get/remove methods to pass
+ * data between your components without needing to manage their lifecycles.</p>
+ */
+ public static class State {
+ static final int STEP_START = 1;
+ static final int STEP_LAYOUT = 1 << 1;
+ static final int STEP_ANIMATIONS = 1 << 2;
+
+ void assertLayoutStep(int accepted) {
+ if ((accepted & mLayoutStep) == 0) {
+ throw new IllegalStateException("Layout state should be one of "
+ + Integer.toBinaryString(accepted) + " but it is "
+ + Integer.toBinaryString(mLayoutStep));
+ }
+ }
+
+
+ /** Owned by SmoothScroller */
+ private int mTargetPosition = RecyclerView.NO_POSITION;
+
+ private SparseArray<Object> mData;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Fields below are carried from one layout pass to the next
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Number of items adapter had in the previous layout.
+ */
+ int mPreviousLayoutItemCount = 0;
+
+ /**
+ * Number of items that were NOT laid out but has been deleted from the adapter after the
+ * previous layout.
+ */
+ int mDeletedInvisibleItemCountSincePreviousLayout = 0;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Fields below must be updated or cleared before they are used (generally before a pass)
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ @IntDef(flag = true, value = {
+ STEP_START, STEP_LAYOUT, STEP_ANIMATIONS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface LayoutState {}
+
+ @LayoutState
+ int mLayoutStep = STEP_START;
+
+ /**
+ * Number of items adapter has.
+ */
+ int mItemCount = 0;
+
+ boolean mStructureChanged = false;
+
+ boolean mInPreLayout = false;
+
+ boolean mTrackOldChangeHolders = false;
+
+ boolean mIsMeasuring = false;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Fields below are always reset outside of the pass (or passes) that use them
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ boolean mRunSimpleAnimations = false;
+
+ boolean mRunPredictiveAnimations = false;
+
+ /**
+ * This data is saved before a layout calculation happens. After the layout is finished,
+ * if the previously focused view has been replaced with another view for the same item, we
+ * move the focus to the new item automatically.
+ */
+ int mFocusedItemPosition;
+ long mFocusedItemId;
+ // when a sub child has focus, record its id and see if we can directly request focus on
+ // that one instead
+ int mFocusedSubChildId;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ State reset() {
+ mTargetPosition = RecyclerView.NO_POSITION;
+ if (mData != null) {
+ mData.clear();
+ }
+ mItemCount = 0;
+ mStructureChanged = false;
+ mIsMeasuring = false;
+ return this;
+ }
+
+ /**
+ * Prepare for a prefetch occurring on the RecyclerView in between traversals, potentially
+ * prior to any layout passes.
+ *
+ * <p>Don't touch any state stored between layout passes, only reset per-layout state, so
+ * that Recycler#getViewForPosition() can function safely.</p>
+ */
+ void prepareForNestedPrefetch(Adapter adapter) {
+ mLayoutStep = STEP_START;
+ mItemCount = adapter.getItemCount();
+ mStructureChanged = false;
+ mInPreLayout = false;
+ mTrackOldChangeHolders = false;
+ mIsMeasuring = false;
+ }
+
+ /**
+ * Returns true if the RecyclerView is currently measuring the layout. This value is
+ * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView
+ * has non-exact measurement specs.
+ * <p>
+ * Note that if the LayoutManager supports predictive animations and it is calculating the
+ * pre-layout step, this value will be {@code false} even if the RecyclerView is in
+ * {@code onMeasure} call. This is because pre-layout means the previous state of the
+ * RecyclerView and measurements made for that state cannot change the RecyclerView's size.
+ * LayoutManager is always guaranteed to receive another call to
+ * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens.
+ *
+ * @return True if the RecyclerView is currently calculating its bounds, false otherwise.
+ */
+ public boolean isMeasuring() {
+ return mIsMeasuring;
+ }
+
+ /**
+ * Returns true if
+ * @return
+ */
+ public boolean isPreLayout() {
+ return mInPreLayout;
+ }
+
+ /**
+ * Returns whether RecyclerView will run predictive animations in this layout pass
+ * or not.
+ *
+ * @return true if RecyclerView is calculating predictive animations to be run at the end
+ * of the layout pass.
+ */
+ public boolean willRunPredictiveAnimations() {
+ return mRunPredictiveAnimations;
+ }
+
+ /**
+ * Returns whether RecyclerView will run simple animations in this layout pass
+ * or not.
+ *
+ * @return true if RecyclerView is calculating simple animations to be run at the end of
+ * the layout pass.
+ */
+ public boolean willRunSimpleAnimations() {
+ return mRunSimpleAnimations;
+ }
+
+ /**
+ * Removes the mapping from the specified id, if there was any.
+ * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to
+ * preserve cross functionality and avoid conflicts.
+ */
+ public void remove(int resourceId) {
+ if (mData == null) {
+ return;
+ }
+ mData.remove(resourceId);
+ }
+
+ /**
+ * Gets the Object mapped from the specified id, or <code>null</code>
+ * if no such data exists.
+ *
+ * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.*
+ * to
+ * preserve cross functionality and avoid conflicts.
+ */
+ public <T> T get(int resourceId) {
+ if (mData == null) {
+ return null;
+ }
+ return (T) mData.get(resourceId);
+ }
+
+ /**
+ * Adds a mapping from the specified id to the specified value, replacing the previous
+ * mapping from the specified key if there was one.
+ *
+ * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to
+ * preserve cross functionality and avoid conflicts.
+ * @param data The data you want to associate with the resourceId.
+ */
+ public void put(int resourceId, Object data) {
+ if (mData == null) {
+ mData = new SparseArray<Object>();
+ }
+ mData.put(resourceId, data);
+ }
+
+ /**
+ * If scroll is triggered to make a certain item visible, this value will return the
+ * adapter index of that item.
+ * @return Adapter index of the target item or
+ * {@link RecyclerView#NO_POSITION} if there is no target
+ * position.
+ */
+ public int getTargetScrollPosition() {
+ return mTargetPosition;
+ }
+
+ /**
+ * Returns if current scroll has a target position.
+ * @return true if scroll is being triggered to make a certain position visible
+ * @see #getTargetScrollPosition()
+ */
+ public boolean hasTargetScrollPosition() {
+ return mTargetPosition != RecyclerView.NO_POSITION;
+ }
+
+ /**
+ * @return true if the structure of the data set has changed since the last call to
+ * onLayoutChildren, false otherwise
+ */
+ public boolean didStructureChange() {
+ return mStructureChanged;
+ }
+
+ /**
+ * Returns the total number of items that can be laid out. Note that this number is not
+ * necessarily equal to the number of items in the adapter, so you should always use this
+ * number for your position calculations and never access the adapter directly.
+ * <p>
+ * RecyclerView listens for Adapter's notify events and calculates the effects of adapter
+ * data changes on existing Views. These calculations are used to decide which animations
+ * should be run.
+ * <p>
+ * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to
+ * present the correct state to LayoutManager in pre-layout pass.
+ * <p>
+ * For example, a newly added item is not included in pre-layout item count because
+ * pre-layout reflects the contents of the adapter before the item is added. Behind the
+ * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that
+ * LayoutManager does not know about the new item's existence in pre-layout. The item will
+ * be available in second layout pass and will be included in the item count. Similar
+ * adjustments are made for moved and removed items as well.
+ * <p>
+ * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method.
+ *
+ * @return The number of items currently available
+ * @see LayoutManager#getItemCount()
+ */
+ public int getItemCount() {
+ return mInPreLayout
+ ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout)
+ : mItemCount;
+ }
+
+ @Override
+ public String toString() {
+ return "State{"
+ + "mTargetPosition=" + mTargetPosition
+ + ", mData=" + mData
+ + ", mItemCount=" + mItemCount
+ + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount
+ + ", mDeletedInvisibleItemCountSincePreviousLayout="
+ + mDeletedInvisibleItemCountSincePreviousLayout
+ + ", mStructureChanged=" + mStructureChanged
+ + ", mInPreLayout=" + mInPreLayout
+ + ", mRunSimpleAnimations=" + mRunSimpleAnimations
+ + ", mRunPredictiveAnimations=" + mRunPredictiveAnimations
+ + '}';
+ }
+ }
+
+ /**
+ * This class defines the behavior of fling if the developer wishes to handle it.
+ * <p>
+ * Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior.
+ *
+ * @see #setOnFlingListener(OnFlingListener)
+ */
+ public abstract static class OnFlingListener {
+
+ /**
+ * Override this to handle a fling given the velocities in both x and y directions.
+ * Note that this method will only be called if the associated {@link LayoutManager}
+ * supports scrolling and the fling is not handled by nested scrolls first.
+ *
+ * @param velocityX the fling velocity on the X axis
+ * @param velocityY the fling velocity on the Y axis
+ *
+ * @return true if the fling washandled, false otherwise.
+ */
+ public abstract boolean onFling(int velocityX, int velocityY);
+ }
+
+ /**
+ * Internal listener that manages items after animations finish. This is how items are
+ * retained (not recycled) during animations, but allowed to be recycled afterwards.
+ * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished()
+ * method on the animator's listener when it is done animating any item.
+ */
+ private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {
+
+ ItemAnimatorRestoreListener() {
+ }
+
+ @Override
+ public void onAnimationFinished(ViewHolder item) {
+ item.setIsRecyclable(true);
+ if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh
+ item.mShadowedHolder = null;
+ }
+ // always null this because an OldViewHolder can never become NewViewHolder w/o being
+ // recycled.
+ item.mShadowingHolder = null;
+ if (!item.shouldBeKeptAsChild()) {
+ if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
+ removeDetachedView(item.itemView, false);
+ }
+ }
+ }
+ }
+
+ /**
+ * This class defines the animations that take place on items as changes are made
+ * to the adapter.
+ *
+ * Subclasses of ItemAnimator can be used to implement custom animations for actions on
+ * ViewHolder items. The RecyclerView will manage retaining these items while they
+ * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)}
+ * when a ViewHolder's animation is finished. In other words, there must be a matching
+ * {@link #dispatchAnimationFinished(ViewHolder)} call for each
+ * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()},
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange()}
+ * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()},
+ * and
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance()} call.
+ *
+ * <p>By default, RecyclerView uses {@link DefaultItemAnimator}.</p>
+ *
+ * @see #setItemAnimator(ItemAnimator)
+ */
+ @SuppressWarnings("UnusedParameters")
+ public abstract static class ItemAnimator {
+
+ /**
+ * The Item represented by this ViewHolder is updated.
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE;
+
+ /**
+ * The Item represented by this ViewHolder is removed from the adapter.
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED;
+
+ /**
+ * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content
+ * represented by this ViewHolder is invalid.
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID;
+
+ /**
+ * The position of the Item represented by this ViewHolder has been changed. This flag is
+ * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to
+ * any adapter change that may have a side effect on this item. (e.g. The item before this
+ * one has been removed from the Adapter).
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED;
+
+ /**
+ * This ViewHolder was not laid out but has been added to the layout in pre-layout state
+ * by the {@link LayoutManager}. This means that the item was already in the Adapter but
+ * invisible and it may become visible in the post layout phase. LayoutManagers may prefer
+ * to add new items in pre-layout to specify their virtual location when they are invisible
+ * (e.g. to specify the item should <i>animate in</i> from below the visible area).
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_APPEARED_IN_PRE_LAYOUT =
+ ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT;
+
+ /**
+ * The set of flags that might be passed to
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ */
+ @IntDef(flag = true, value = {
+ FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED,
+ FLAG_APPEARED_IN_PRE_LAYOUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AdapterChanges {}
+ private ItemAnimatorListener mListener = null;
+ private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners =
+ new ArrayList<ItemAnimatorFinishedListener>();
+
+ private long mAddDuration = 120;
+ private long mRemoveDuration = 120;
+ private long mMoveDuration = 250;
+ private long mChangeDuration = 250;
+
+ /**
+ * Gets the current duration for which all move animations will run.
+ *
+ * @return The current move duration
+ */
+ public long getMoveDuration() {
+ return mMoveDuration;
+ }
+
+ /**
+ * Sets the duration for which all move animations will run.
+ *
+ * @param moveDuration The move duration
+ */
+ public void setMoveDuration(long moveDuration) {
+ mMoveDuration = moveDuration;
+ }
+
+ /**
+ * Gets the current duration for which all add animations will run.
+ *
+ * @return The current add duration
+ */
+ public long getAddDuration() {
+ return mAddDuration;
+ }
+
+ /**
+ * Sets the duration for which all add animations will run.
+ *
+ * @param addDuration The add duration
+ */
+ public void setAddDuration(long addDuration) {
+ mAddDuration = addDuration;
+ }
+
+ /**
+ * Gets the current duration for which all remove animations will run.
+ *
+ * @return The current remove duration
+ */
+ public long getRemoveDuration() {
+ return mRemoveDuration;
+ }
+
+ /**
+ * Sets the duration for which all remove animations will run.
+ *
+ * @param removeDuration The remove duration
+ */
+ public void setRemoveDuration(long removeDuration) {
+ mRemoveDuration = removeDuration;
+ }
+
+ /**
+ * Gets the current duration for which all change animations will run.
+ *
+ * @return The current change duration
+ */
+ public long getChangeDuration() {
+ return mChangeDuration;
+ }
+
+ /**
+ * Sets the duration for which all change animations will run.
+ *
+ * @param changeDuration The change duration
+ */
+ public void setChangeDuration(long changeDuration) {
+ mChangeDuration = changeDuration;
+ }
+
+ /**
+ * Internal only:
+ * Sets the listener that must be called when the animator is finished
+ * animating the item (or immediately if no animation happens). This is set
+ * internally and is not intended to be set by external code.
+ *
+ * @param listener The listener that must be called.
+ */
+ void setListener(ItemAnimatorListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Called by the RecyclerView before the layout begins. Item animator should record
+ * necessary information about the View before it is potentially rebound, moved or removed.
+ * <p>
+ * The data returned from this method will be passed to the related <code>animate**</code>
+ * methods.
+ * <p>
+ * Note that this method may be called after pre-layout phase if LayoutManager adds new
+ * Views to the layout in pre-layout pass.
+ * <p>
+ * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of
+ * the View and the adapter change flags.
+ *
+ * @param state The current State of RecyclerView which includes some useful data
+ * about the layout that will be calculated.
+ * @param viewHolder The ViewHolder whose information should be recorded.
+ * @param changeFlags Additional information about what changes happened in the Adapter
+ * about the Item represented by this ViewHolder. For instance, if
+ * item is deleted from the adapter, {@link #FLAG_REMOVED} will be set.
+ * @param payloads The payload list that was previously passed to
+ * {@link Adapter#notifyItemChanged(int, Object)} or
+ * {@link Adapter#notifyItemRangeChanged(int, int, Object)}.
+ *
+ * @return An ItemHolderInfo instance that preserves necessary information about the
+ * ViewHolder. This object will be passed back to related <code>animate**</code> methods
+ * after layout is complete.
+ *
+ * @see #recordPostLayoutInformation(State, ViewHolder)
+ * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ */
+ public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state,
+ @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags,
+ @NonNull List<Object> payloads) {
+ return obtainHolderInfo().setFrom(viewHolder);
+ }
+
+ /**
+ * Called by the RecyclerView after the layout is complete. Item animator should record
+ * necessary information about the View's final state.
+ * <p>
+ * The data returned from this method will be passed to the related <code>animate**</code>
+ * methods.
+ * <p>
+ * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of
+ * the View.
+ *
+ * @param state The current State of RecyclerView which includes some useful data about
+ * the layout that will be calculated.
+ * @param viewHolder The ViewHolder whose information should be recorded.
+ *
+ * @return An ItemHolderInfo that preserves necessary information about the ViewHolder.
+ * This object will be passed back to related <code>animate**</code> methods when
+ * RecyclerView decides how items should be animated.
+ *
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ */
+ public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state,
+ @NonNull ViewHolder viewHolder) {
+ return obtainHolderInfo().setFrom(viewHolder);
+ }
+
+ /**
+ * Called by the RecyclerView when a ViewHolder has disappeared from the layout.
+ * <p>
+ * This means that the View was a child of the LayoutManager when layout started but has
+ * been removed by the LayoutManager. It might have been removed from the adapter or simply
+ * become invisible due to other factors. You can distinguish these two cases by checking
+ * the change flags that were passed to
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * <p>
+ * Note that when a ViewHolder both changes and disappears in the same layout pass, the
+ * animation callback method which will be called by the RecyclerView depends on the
+ * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the
+ * LayoutManager's decision whether to layout the changed version of a disappearing
+ * ViewHolder or not. RecyclerView will call
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator
+ * returns {@code false} from
+ * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the
+ * LayoutManager lays out a new disappearing view that holds the updated information.
+ * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views.
+ * <p>
+ * If LayoutManager supports predictive animations, it might provide a target disappear
+ * location for the View by laying it out in that location. When that happens,
+ * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the
+ * response of that call will be passed to this method as the <code>postLayoutInfo</code>.
+ * <p>
+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
+ * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
+ * decides not to animate the view).
+ *
+ * @param viewHolder The ViewHolder which should be animated
+ * @param preLayoutInfo The information that was returned from
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * @param postLayoutInfo The information that was returned from
+ * {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be
+ * null if the LayoutManager did not layout the item.
+ *
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo);
+
+ /**
+ * Called by the RecyclerView when a ViewHolder is added to the layout.
+ * <p>
+ * In detail, this means that the ViewHolder was <b>not</b> a child when the layout started
+ * but has been added by the LayoutManager. It might be newly added to the adapter or
+ * simply become visible due to other factors.
+ * <p>
+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
+ * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
+ * decides not to animate the view).
+ *
+ * @param viewHolder The ViewHolder which should be animated
+ * @param preLayoutInfo The information that was returned from
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * Might be null if Item was just added to the adapter or
+ * LayoutManager does not support predictive animations or it could
+ * not predict that this ViewHolder will become visible.
+ * @param postLayoutInfo The information that was returned from {@link
+ * #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ *
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
+ @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
+
+ /**
+ * Called by the RecyclerView when a ViewHolder is present in both before and after the
+ * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call
+ * for it or a {@link Adapter#notifyDataSetChanged()} call.
+ * <p>
+ * This ViewHolder still represents the same data that it was representing when the layout
+ * started but its position / size may be changed by the LayoutManager.
+ * <p>
+ * If the Item's layout position didn't change, RecyclerView still calls this method because
+ * it does not track this information (or does not necessarily know that an animation is
+ * not required). Your ItemAnimator should handle this case and if there is nothing to
+ * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return
+ * <code>false</code>.
+ * <p>
+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
+ * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
+ * decides not to animate the view).
+ *
+ * @param viewHolder The ViewHolder which should be animated
+ * @param preLayoutInfo The information that was returned from
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * @param postLayoutInfo The information that was returned from {@link
+ * #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ *
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
+
+ /**
+ * Called by the RecyclerView when an adapter item is present both before and after the
+ * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call
+ * for it. This method may also be called when
+ * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that
+ * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when
+ * {@link Adapter#notifyDataSetChanged()} is called, this method <b>will not</b> be called,
+ * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be
+ * called for the new ViewHolder and the old one will be recycled.
+ * <p>
+ * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is
+ * a good possibility that item contents didn't really change but it is rebound from the
+ * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the
+ * screen didn't change and your animator should handle this case as well and avoid creating
+ * unnecessary animations.
+ * <p>
+ * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the
+ * previous presentation of the item as-is and supply a new ViewHolder for the updated
+ * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}.
+ * This is useful if you don't know the contents of the Item and would like
+ * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique).
+ * <p>
+ * When you are writing a custom item animator for your layout, it might be more performant
+ * and elegant to re-use the same ViewHolder and animate the content changes manually.
+ * <p>
+ * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change.
+ * If the Item's view type has changed or ItemAnimator returned <code>false</code> for
+ * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the
+ * <code>oldHolder</code> and <code>newHolder</code> will be different ViewHolder instances
+ * which represent the same Item. In that case, only the new ViewHolder is visible
+ * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations.
+ * <p>
+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct
+ * ViewHolder when their animation is complete
+ * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to
+ * animate the view).
+ * <p>
+ * If oldHolder and newHolder are the same instance, you should call
+ * {@link #dispatchAnimationFinished(ViewHolder)} <b>only once</b>.
+ * <p>
+ * Note that when a ViewHolder both changes and disappears in the same layout pass, the
+ * animation callback method which will be called by the RecyclerView depends on the
+ * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the
+ * LayoutManager's decision whether to layout the changed version of a disappearing
+ * ViewHolder or not. RecyclerView will call
+ * {@code animateChange} instead of
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance} if and only if the ItemAnimator returns {@code false} from
+ * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the
+ * LayoutManager lays out a new disappearing view that holds the updated information.
+ * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views.
+ *
+ * @param oldHolder The ViewHolder before the layout is started, might be the same
+ * instance with newHolder.
+ * @param newHolder The ViewHolder after the layout is finished, might be the same
+ * instance with oldHolder.
+ * @param preLayoutInfo The information that was returned from
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * @param postLayoutInfo The information that was returned from {@link
+ * #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ *
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateChange(@NonNull ViewHolder oldHolder,
+ @NonNull ViewHolder newHolder,
+ @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
+
+ @AdapterChanges static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) {
+ int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED);
+ if (viewHolder.isInvalid()) {
+ return FLAG_INVALIDATED;
+ }
+ if ((flags & FLAG_INVALIDATED) == 0) {
+ final int oldPos = viewHolder.getOldPosition();
+ final int pos = viewHolder.getAdapterPosition();
+ if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) {
+ flags |= FLAG_MOVED;
+ }
+ }
+ return flags;
+ }
+
+ /**
+ * Called when there are pending animations waiting to be started. This state
+ * is governed by the return values from
+ * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateAppearance()},
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange()}
+ * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animatePersistence()}, and
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be
+ * called later to start the associated animations. runPendingAnimations() will be scheduled
+ * to be run on the next frame.
+ */
+ public abstract void runPendingAnimations();
+
+ /**
+ * Method called when an animation on a view should be ended immediately.
+ * This could happen when other events, like scrolling, occur, so that
+ * animating views can be quickly put into their proper end locations.
+ * Implementations should ensure that any animations running on the item
+ * are canceled and affected properties are set to their end values.
+ * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished
+ * animation since the animations are effectively done when this method is called.
+ *
+ * @param item The item for which an animation should be stopped.
+ */
+ public abstract void endAnimation(ViewHolder item);
+
+ /**
+ * Method called when all item animations should be ended immediately.
+ * This could happen when other events, like scrolling, occur, so that
+ * animating views can be quickly put into their proper end locations.
+ * Implementations should ensure that any animations running on any items
+ * are canceled and affected properties are set to their end values.
+ * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished
+ * animation since the animations are effectively done when this method is called.
+ */
+ public abstract void endAnimations();
+
+ /**
+ * Method which returns whether there are any item animations currently running.
+ * This method can be used to determine whether to delay other actions until
+ * animations end.
+ *
+ * @return true if there are any item animations currently running, false otherwise.
+ */
+ public abstract boolean isRunning();
+
+ /**
+ * Method to be called by subclasses when an animation is finished.
+ * <p>
+ * For each call RecyclerView makes to
+ * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateAppearance()},
+ * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animatePersistence()}, or
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance()}, there
+ * should
+ * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass.
+ * <p>
+ * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange()}, subclass should call this method for both the <code>oldHolder</code>
+ * and <code>newHolder</code> (if they are not the same instance).
+ *
+ * @param viewHolder The ViewHolder whose animation is finished.
+ * @see #onAnimationFinished(ViewHolder)
+ */
+ public final void dispatchAnimationFinished(ViewHolder viewHolder) {
+ onAnimationFinished(viewHolder);
+ if (mListener != null) {
+ mListener.onAnimationFinished(viewHolder);
+ }
+ }
+
+ /**
+ * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the
+ * ItemAnimator.
+ *
+ * @param viewHolder The ViewHolder whose animation is finished. There might still be other
+ * animations running on this ViewHolder.
+ * @see #dispatchAnimationFinished(ViewHolder)
+ */
+ public void onAnimationFinished(ViewHolder viewHolder) {
+ }
+
+ /**
+ * Method to be called by subclasses when an animation is started.
+ * <p>
+ * For each call RecyclerView makes to
+ * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateAppearance()},
+ * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animatePersistence()}, or
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance()}, there should be a matching
+ * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass.
+ * <p>
+ * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange()}, subclass should call this method for both the <code>oldHolder</code>
+ * and <code>newHolder</code> (if they are not the same instance).
+ * <p>
+ * If your ItemAnimator decides not to animate a ViewHolder, it should call
+ * {@link #dispatchAnimationFinished(ViewHolder)} <b>without</b> calling
+ * {@link #dispatchAnimationStarted(ViewHolder)}.
+ *
+ * @param viewHolder The ViewHolder whose animation is starting.
+ * @see #onAnimationStarted(ViewHolder)
+ */
+ public final void dispatchAnimationStarted(ViewHolder viewHolder) {
+ onAnimationStarted(viewHolder);
+ }
+
+ /**
+ * Called when a new animation is started on the given ViewHolder.
+ *
+ * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder
+ * might already be animating and this might be another animation.
+ * @see #dispatchAnimationStarted(ViewHolder)
+ */
+ public void onAnimationStarted(ViewHolder viewHolder) {
+
+ }
+
+ /**
+ * Like {@link #isRunning()}, this method returns whether there are any item
+ * animations currently running. Additionally, the listener passed in will be called
+ * when there are no item animations running, either immediately (before the method
+ * returns) if no animations are currently running, or when the currently running
+ * animations are {@link #dispatchAnimationsFinished() finished}.
+ *
+ * <p>Note that the listener is transient - it is either called immediately and not
+ * stored at all, or stored only until it is called when running animations
+ * are finished sometime later.</p>
+ *
+ * @param listener A listener to be called immediately if no animations are running
+ * or later when currently-running animations have finished. A null listener is
+ * equivalent to calling {@link #isRunning()}.
+ * @return true if there are any item animations currently running, false otherwise.
+ */
+ public final boolean isRunning(ItemAnimatorFinishedListener listener) {
+ boolean running = isRunning();
+ if (listener != null) {
+ if (!running) {
+ listener.onAnimationsFinished();
+ } else {
+ mFinishedListeners.add(listener);
+ }
+ }
+ return running;
+ }
+
+ /**
+ * When an item is changed, ItemAnimator can decide whether it wants to re-use
+ * the same ViewHolder for animations or RecyclerView should create a copy of the
+ * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
+ * <p>
+ * Note that this method will only be called if the {@link ViewHolder} still has the same
+ * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
+ * both {@link ViewHolder}s in the
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
+ * <p>
+ * If your application is using change payloads, you can override
+ * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads.
+ *
+ * @param viewHolder The ViewHolder which represents the changed item's old content.
+ *
+ * @return True if RecyclerView should just rebind to the same ViewHolder or false if
+ * RecyclerView should create a new ViewHolder and pass this ViewHolder to the
+ * ItemAnimator to animate. Default implementation returns <code>true</code>.
+ *
+ * @see #canReuseUpdatedViewHolder(ViewHolder, List)
+ */
+ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) {
+ return true;
+ }
+
+ /**
+ * When an item is changed, ItemAnimator can decide whether it wants to re-use
+ * the same ViewHolder for animations or RecyclerView should create a copy of the
+ * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
+ * <p>
+ * Note that this method will only be called if the {@link ViewHolder} still has the same
+ * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
+ * both {@link ViewHolder}s in the
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
+ *
+ * @param viewHolder The ViewHolder which represents the changed item's old content.
+ * @param payloads A non-null list of merged payloads that were sent with change
+ * notifications. Can be empty if the adapter is invalidated via
+ * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of
+ * payloads will be passed into
+ * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)}
+ * method <b>if</b> this method returns <code>true</code>.
+ *
+ * @return True if RecyclerView should just rebind to the same ViewHolder or false if
+ * RecyclerView should create a new ViewHolder and pass this ViewHolder to the
+ * ItemAnimator to animate. Default implementation calls
+ * {@link #canReuseUpdatedViewHolder(ViewHolder)}.
+ *
+ * @see #canReuseUpdatedViewHolder(ViewHolder)
+ */
+ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
+ @NonNull List<Object> payloads) {
+ return canReuseUpdatedViewHolder(viewHolder);
+ }
+
+ /**
+ * This method should be called by ItemAnimator implementations to notify
+ * any listeners that all pending and active item animations are finished.
+ */
+ public final void dispatchAnimationsFinished() {
+ final int count = mFinishedListeners.size();
+ for (int i = 0; i < count; ++i) {
+ mFinishedListeners.get(i).onAnimationsFinished();
+ }
+ mFinishedListeners.clear();
+ }
+
+ /**
+ * Returns a new {@link ItemHolderInfo} which will be used to store information about the
+ * ViewHolder. This information will later be passed into <code>animate**</code> methods.
+ * <p>
+ * You can override this method if you want to extend {@link ItemHolderInfo} and provide
+ * your own instances.
+ *
+ * @return A new {@link ItemHolderInfo}.
+ */
+ public ItemHolderInfo obtainHolderInfo() {
+ return new ItemHolderInfo();
+ }
+
+ /**
+ * The interface to be implemented by listeners to animation events from this
+ * ItemAnimator. This is used internally and is not intended for developers to
+ * create directly.
+ */
+ interface ItemAnimatorListener {
+ void onAnimationFinished(ViewHolder item);
+ }
+
+ /**
+ * This interface is used to inform listeners when all pending or running animations
+ * in an ItemAnimator are finished. This can be used, for example, to delay an action
+ * in a data set until currently-running animations are complete.
+ *
+ * @see #isRunning(ItemAnimatorFinishedListener)
+ */
+ public interface ItemAnimatorFinishedListener {
+ /**
+ * Notifies when all pending or running animations in an ItemAnimator are finished.
+ */
+ void onAnimationsFinished();
+ }
+
+ /**
+ * A simple data structure that holds information about an item's bounds.
+ * This information is used in calculating item animations. Default implementation of
+ * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and
+ * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data
+ * structure. You can extend this class if you would like to keep more information about
+ * the Views.
+ * <p>
+ * If you want to provide your own implementation but still use `super` methods to record
+ * basic information, you can override {@link #obtainHolderInfo()} to provide your own
+ * instances.
+ */
+ public static class ItemHolderInfo {
+
+ /**
+ * The left edge of the View (excluding decorations)
+ */
+ public int left;
+
+ /**
+ * The top edge of the View (excluding decorations)
+ */
+ public int top;
+
+ /**
+ * The right edge of the View (excluding decorations)
+ */
+ public int right;
+
+ /**
+ * The bottom edge of the View (excluding decorations)
+ */
+ public int bottom;
+
+ /**
+ * The change flags that were passed to
+ * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}.
+ */
+ @AdapterChanges
+ public int changeFlags;
+
+ public ItemHolderInfo() {
+ }
+
+ /**
+ * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from
+ * the given ViewHolder. Clears all {@link #changeFlags}.
+ *
+ * @param holder The ViewHolder whose bounds should be copied.
+ * @return This {@link ItemHolderInfo}
+ */
+ public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder) {
+ return setFrom(holder, 0);
+ }
+
+ /**
+ * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from
+ * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter.
+ *
+ * @param holder The ViewHolder whose bounds should be copied.
+ * @param flags The adapter change flags that were passed into
+ * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int,
+ * List)}.
+ * @return This {@link ItemHolderInfo}
+ */
+ public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder,
+ @AdapterChanges int flags) {
+ final View view = holder.itemView;
+ this.left = view.getLeft();
+ this.top = view.getTop();
+ this.right = view.getRight();
+ this.bottom = view.getBottom();
+ return this;
+ }
+ }
+ }
+
+ @Override
+ protected int getChildDrawingOrder(int childCount, int i) {
+ if (mChildDrawingOrderCallback == null) {
+ return super.getChildDrawingOrder(childCount, i);
+ } else {
+ return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i);
+ }
+ }
+
+ /**
+ * A callback interface that can be used to alter the drawing order of RecyclerView children.
+ * <p>
+ * It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case
+ * that applies to that method also applies to this callback. For example, changing the drawing
+ * order of two views will not have any effect if their elevation values are different since
+ * elevation overrides the result of this callback.
+ */
+ public interface ChildDrawingOrderCallback {
+ /**
+ * Returns the index of the child to draw for this iteration. Override this
+ * if you want to change the drawing order of children. By default, it
+ * returns i.
+ *
+ * @param i The current iteration.
+ * @return The index of the child to draw this iteration.
+ *
+ * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback)
+ */
+ int onGetChildDrawingOrder(int childCount, int i);
+ }
+}
diff --git a/core/java/com/android/internal/widget/RecyclerViewAccessibilityDelegate.java b/core/java/com/android/internal/widget/RecyclerViewAccessibilityDelegate.java
new file mode 100644
index 0000000..282da64
--- /dev/null
+++ b/core/java/com/android/internal/widget/RecyclerViewAccessibilityDelegate.java
@@ -0,0 +1,107 @@
+/*
+ * 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.internal.widget;
+
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * The AccessibilityDelegate used by RecyclerView.
+ * <p>
+ * This class handles basic accessibility actions and delegates them to LayoutManager.
+ */
+public class RecyclerViewAccessibilityDelegate extends AccessibilityDelegate {
+ final RecyclerView mRecyclerView;
+
+
+ public RecyclerViewAccessibilityDelegate(RecyclerView recyclerView) {
+ mRecyclerView = recyclerView;
+ }
+
+ boolean shouldIgnore() {
+ return mRecyclerView.hasPendingAdapterUpdates();
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (super.performAccessibilityAction(host, action, args)) {
+ return true;
+ }
+ if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
+ return mRecyclerView.getLayoutManager().performAccessibilityAction(action, args);
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.setClassName(RecyclerView.class.getName());
+ if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
+ mRecyclerView.getLayoutManager().onInitializeAccessibilityNodeInfo(info);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(host, event);
+ event.setClassName(RecyclerView.class.getName());
+ if (host instanceof RecyclerView && !shouldIgnore()) {
+ RecyclerView rv = (RecyclerView) host;
+ if (rv.getLayoutManager() != null) {
+ rv.getLayoutManager().onInitializeAccessibilityEvent(event);
+ }
+ }
+ }
+
+ /**
+ * Gets the AccessibilityDelegate for an individual item in the RecyclerView.
+ * A basic item delegate is provided by default, but you can override this
+ * method to provide a custom per-item delegate.
+ */
+ public AccessibilityDelegate getItemDelegate() {
+ return mItemDelegate;
+ }
+
+ final AccessibilityDelegate mItemDelegate = new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
+ mRecyclerView.getLayoutManager()
+ .onInitializeAccessibilityNodeInfoForItem(host, info);
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (super.performAccessibilityAction(host, action, args)) {
+ return true;
+ }
+ if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
+ return mRecyclerView.getLayoutManager()
+ .performAccessibilityActionForItem(host, action, args);
+ }
+ return false;
+ }
+ };
+}
+
diff --git a/core/java/com/android/internal/widget/ScrollbarHelper.java b/core/java/com/android/internal/widget/ScrollbarHelper.java
new file mode 100644
index 0000000..ae34e4c
--- /dev/null
+++ b/core/java/com/android/internal/widget/ScrollbarHelper.java
@@ -0,0 +1,99 @@
+/*
+ * 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.internal.widget;
+
+import android.view.View;
+
+/**
+ * A helper class to do scroll offset calculations.
+ */
+class ScrollbarHelper {
+
+ /**
+ * @param startChild View closest to start of the list. (top or left)
+ * @param endChild View closest to end of the list (bottom or right)
+ */
+ static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation,
+ View startChild, View endChild, RecyclerView.LayoutManager lm,
+ boolean smoothScrollbarEnabled, boolean reverseLayout) {
+ if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null
+ || endChild == null) {
+ return 0;
+ }
+ final int minPosition = Math.min(lm.getPosition(startChild),
+ lm.getPosition(endChild));
+ final int maxPosition = Math.max(lm.getPosition(startChild),
+ lm.getPosition(endChild));
+ final int itemsBefore = reverseLayout
+ ? Math.max(0, state.getItemCount() - maxPosition - 1)
+ : Math.max(0, minPosition);
+ if (!smoothScrollbarEnabled) {
+ return itemsBefore;
+ }
+ final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild)
+ - orientation.getDecoratedStart(startChild));
+ final int itemRange = Math.abs(lm.getPosition(startChild)
+ - lm.getPosition(endChild)) + 1;
+ final float avgSizePerRow = (float) laidOutArea / itemRange;
+
+ return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
+ - orientation.getDecoratedStart(startChild)));
+ }
+
+ /**
+ * @param startChild View closest to start of the list. (top or left)
+ * @param endChild View closest to end of the list (bottom or right)
+ */
+ static int computeScrollExtent(RecyclerView.State state, OrientationHelper orientation,
+ View startChild, View endChild, RecyclerView.LayoutManager lm,
+ boolean smoothScrollbarEnabled) {
+ if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null
+ || endChild == null) {
+ return 0;
+ }
+ if (!smoothScrollbarEnabled) {
+ return Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1;
+ }
+ final int extend = orientation.getDecoratedEnd(endChild)
+ - orientation.getDecoratedStart(startChild);
+ return Math.min(orientation.getTotalSpace(), extend);
+ }
+
+ /**
+ * @param startChild View closest to start of the list. (top or left)
+ * @param endChild View closest to end of the list (bottom or right)
+ */
+ static int computeScrollRange(RecyclerView.State state, OrientationHelper orientation,
+ View startChild, View endChild, RecyclerView.LayoutManager lm,
+ boolean smoothScrollbarEnabled) {
+ if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null
+ || endChild == null) {
+ return 0;
+ }
+ if (!smoothScrollbarEnabled) {
+ return state.getItemCount();
+ }
+ // smooth scrollbar enabled. try to estimate better.
+ final int laidOutArea = orientation.getDecoratedEnd(endChild)
+ - orientation.getDecoratedStart(startChild);
+ final int laidOutRange = Math.abs(lm.getPosition(startChild)
+ - lm.getPosition(endChild))
+ + 1;
+ // estimate a size for full list.
+ return (int) ((float) laidOutArea / laidOutRange * state.getItemCount());
+ }
+}
diff --git a/core/java/com/android/internal/widget/ScrollingView.java b/core/java/com/android/internal/widget/ScrollingView.java
new file mode 100644
index 0000000..a0205e7
--- /dev/null
+++ b/core/java/com/android/internal/widget/ScrollingView.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.internal.widget;
+
+/**
+ * An interface that can be implemented by Views to provide scroll related APIs.
+ */
+public interface ScrollingView {
+ /**
+ * <p>Compute the horizontal range that the horizontal scrollbar
+ * represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeHorizontalScrollExtent()} and
+ * {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>The default range is the drawing width of this view.</p>
+ *
+ * @return the total horizontal range represented by the horizontal
+ * scrollbar
+ *
+ * @see #computeHorizontalScrollExtent()
+ * @see #computeHorizontalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeHorizontalScrollRange();
+
+ /**
+ * <p>Compute the horizontal offset of the horizontal scrollbar's thumb
+ * within the horizontal range. This value is used to compute the position
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeHorizontalScrollRange()} and
+ * {@link #computeHorizontalScrollExtent()}.</p>
+ *
+ * <p>The default offset is the scroll offset of this view.</p>
+ *
+ * @return the horizontal offset of the scrollbar's thumb
+ *
+ * @see #computeHorizontalScrollRange()
+ * @see #computeHorizontalScrollExtent()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeHorizontalScrollOffset();
+
+ /**
+ * <p>Compute the horizontal extent of the horizontal scrollbar's thumb
+ * within the horizontal range. This value is used to compute the length
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeHorizontalScrollRange()} and
+ * {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>The default extent is the drawing width of this view.</p>
+ *
+ * @return the horizontal extent of the scrollbar's thumb
+ *
+ * @see #computeHorizontalScrollRange()
+ * @see #computeHorizontalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeHorizontalScrollExtent();
+
+ /**
+ * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeVerticalScrollExtent()} and
+ * {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * @return the total vertical range represented by the vertical scrollbar
+ *
+ * <p>The default range is the drawing height of this view.</p>
+ *
+ * @see #computeVerticalScrollExtent()
+ * @see #computeVerticalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeVerticalScrollRange();
+
+ /**
+ * <p>Compute the vertical offset of the vertical scrollbar's thumb
+ * within the horizontal range. This value is used to compute the position
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeVerticalScrollRange()} and
+ * {@link #computeVerticalScrollExtent()}.</p>
+ *
+ * <p>The default offset is the scroll offset of this view.</p>
+ *
+ * @return the vertical offset of the scrollbar's thumb
+ *
+ * @see #computeVerticalScrollRange()
+ * @see #computeVerticalScrollExtent()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeVerticalScrollOffset();
+
+ /**
+ * <p>Compute the vertical extent of the vertical scrollbar's thumb
+ * within the vertical range. This value is used to compute the length
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeVerticalScrollRange()} and
+ * {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * <p>The default extent is the drawing height of this view.</p>
+ *
+ * @return the vertical extent of the scrollbar's thumb
+ *
+ * @see #computeVerticalScrollRange()
+ * @see #computeVerticalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeVerticalScrollExtent();
+}
diff --git a/core/java/com/android/internal/widget/SimpleItemAnimator.java b/core/java/com/android/internal/widget/SimpleItemAnimator.java
new file mode 100644
index 0000000..f4cc753
--- /dev/null
+++ b/core/java/com/android/internal/widget/SimpleItemAnimator.java
@@ -0,0 +1,457 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.widget.RecyclerView.Adapter;
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+/**
+ * A wrapper class for ItemAnimator that records View bounds and decides whether it should run
+ * move, change, add or remove animations. This class also replicates the original ItemAnimator
+ * API.
+ * <p>
+ * It uses {@link ItemHolderInfo} to track the bounds information of the Views. If you would like
+ * to
+ * extend this class, you can override {@link #obtainHolderInfo()} method to provide your own info
+ * class that extends {@link ItemHolderInfo}.
+ */
+public abstract class SimpleItemAnimator extends RecyclerView.ItemAnimator {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "SimpleItemAnimator";
+
+ boolean mSupportsChangeAnimations = true;
+
+ /**
+ * Returns whether this ItemAnimator supports animations of change events.
+ *
+ * @return true if change animations are supported, false otherwise
+ */
+ @SuppressWarnings("unused")
+ public boolean getSupportsChangeAnimations() {
+ return mSupportsChangeAnimations;
+ }
+
+ /**
+ * Sets whether this ItemAnimator supports animations of item change events.
+ * If you set this property to false, actions on the data set which change the
+ * contents of items will not be animated. What those animations do is left
+ * up to the discretion of the ItemAnimator subclass, in its
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation.
+ * The value of this property is true by default.
+ *
+ * @param supportsChangeAnimations true if change animations are supported by
+ * this ItemAnimator, false otherwise. If the property is false,
+ * the ItemAnimator
+ * will not receive a call to
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int,
+ * int)} when changes occur.
+ * @see Adapter#notifyItemChanged(int)
+ * @see Adapter#notifyItemRangeChanged(int, int)
+ */
+ public void setSupportsChangeAnimations(boolean supportsChangeAnimations) {
+ mSupportsChangeAnimations = supportsChangeAnimations;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if change animations are not supported or the ViewHolder is invalid,
+ * false otherwise.
+ *
+ * @see #setSupportsChangeAnimations(boolean)
+ */
+ @Override
+ public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
+ return !mSupportsChangeAnimations || viewHolder.isInvalid();
+ }
+
+ @Override
+ public boolean animateDisappearance(@NonNull ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
+ int oldLeft = preLayoutInfo.left;
+ int oldTop = preLayoutInfo.top;
+ View disappearingItemView = viewHolder.itemView;
+ int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
+ int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
+ if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
+ disappearingItemView.layout(newLeft, newTop,
+ newLeft + disappearingItemView.getWidth(),
+ newTop + disappearingItemView.getHeight());
+ if (DEBUG) {
+ Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
+ }
+ return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
+ }
+ return animateRemove(viewHolder);
+ }
+ }
+
+ @Override
+ public boolean animateAppearance(@NonNull ViewHolder viewHolder,
+ @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
+ if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left
+ || preLayoutInfo.top != postLayoutInfo.top)) {
+ // slide items in if before/after locations differ
+ if (DEBUG) {
+ Log.d(TAG, "APPEARING: " + viewHolder + " with view " + viewHolder);
+ }
+ return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
+ postLayoutInfo.left, postLayoutInfo.top);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "ADDED: " + viewHolder + " with view " + viewHolder);
+ }
+ return animateAdd(viewHolder);
+ }
+ }
+
+ @Override
+ public boolean animatePersistence(@NonNull ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
+ if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
+ if (DEBUG) {
+ Log.d(TAG, "PERSISTENT: " + viewHolder
+ + " with view " + viewHolder.itemView);
+ }
+ return animateMove(viewHolder,
+ preInfo.left, preInfo.top, postInfo.left, postInfo.top);
+ }
+ dispatchMoveFinished(viewHolder);
+ return false;
+ }
+
+ @Override
+ public boolean animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
+ @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
+ if (DEBUG) {
+ Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView);
+ }
+ final int fromLeft = preInfo.left;
+ final int fromTop = preInfo.top;
+ final int toLeft, toTop;
+ if (newHolder.shouldIgnore()) {
+ toLeft = preInfo.left;
+ toTop = preInfo.top;
+ } else {
+ toLeft = postInfo.left;
+ toTop = postInfo.top;
+ }
+ return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop);
+ }
+
+ /**
+ * Called when an item is removed from the RecyclerView. Implementors can choose
+ * whether and how to animate that change, but must always call
+ * {@link #dispatchRemoveFinished(ViewHolder)} when done, either
+ * immediately (if no animation will occur) or after the animation actually finishes.
+ * The return value indicates whether an animation has been set up and whether the
+ * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+ * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+ * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+ * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+ * {@link #animateRemove(ViewHolder) animateRemove()}, and
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+ * then start the animations together in the later call to {@link #runPendingAnimations()}.
+ *
+ * <p>This method may also be called for disappearing items which continue to exist in the
+ * RecyclerView, but for which the system does not have enough information to animate
+ * them out of view. In that case, the default animation for removing items is run
+ * on those items as well.</p>
+ *
+ * @param holder The item that is being removed.
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateRemove(ViewHolder holder);
+
+ /**
+ * Called when an item is added to the RecyclerView. Implementors can choose
+ * whether and how to animate that change, but must always call
+ * {@link #dispatchAddFinished(ViewHolder)} when done, either
+ * immediately (if no animation will occur) or after the animation actually finishes.
+ * The return value indicates whether an animation has been set up and whether the
+ * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+ * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+ * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+ * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+ * {@link #animateRemove(ViewHolder) animateRemove()}, and
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+ * then start the animations together in the later call to {@link #runPendingAnimations()}.
+ *
+ * <p>This method may also be called for appearing items which were already in the
+ * RecyclerView, but for which the system does not have enough information to animate
+ * them into view. In that case, the default animation for adding items is run
+ * on those items as well.</p>
+ *
+ * @param holder The item that is being added.
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateAdd(ViewHolder holder);
+
+ /**
+ * Called when an item is moved in the RecyclerView. Implementors can choose
+ * whether and how to animate that change, but must always call
+ * {@link #dispatchMoveFinished(ViewHolder)} when done, either
+ * immediately (if no animation will occur) or after the animation actually finishes.
+ * The return value indicates whether an animation has been set up and whether the
+ * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+ * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+ * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+ * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+ * {@link #animateRemove(ViewHolder) animateRemove()}, and
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+ * then start the animations together in the later call to {@link #runPendingAnimations()}.
+ *
+ * @param holder The item that is being moved.
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateMove(ViewHolder holder, int fromX, int fromY,
+ int toX, int toY);
+
+ /**
+ * Called when an item is changed in the RecyclerView, as indicated by a call to
+ * {@link Adapter#notifyItemChanged(int)} or
+ * {@link Adapter#notifyItemRangeChanged(int, int)}.
+ * <p>
+ * Implementers can choose whether and how to animate changes, but must always call
+ * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null distinct ViewHolder,
+ * either immediately (if no animation will occur) or after the animation actually finishes.
+ * If the {@code oldHolder} is the same ViewHolder as the {@code newHolder}, you must call
+ * {@link #dispatchChangeFinished(ViewHolder, boolean)} once and only once. In that case, the
+ * second parameter of {@code dispatchChangeFinished} is ignored.
+ * <p>
+ * The return value indicates whether an animation has been set up and whether the
+ * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+ * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+ * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+ * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+ * {@link #animateRemove(ViewHolder) animateRemove()}, and
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+ * then start the animations together in the later call to {@link #runPendingAnimations()}.
+ *
+ * @param oldHolder The original item that changed.
+ * @param newHolder The new item that was created with the changed content. Might be null
+ * @param fromLeft Left of the old view holder
+ * @param fromTop Top of the old view holder
+ * @param toLeft Left of the new view holder
+ * @param toTop Top of the new view holder
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateChange(ViewHolder oldHolder,
+ ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop);
+
+ /**
+ * Method to be called by subclasses when a remove animation is done.
+ *
+ * @param item The item which has been removed
+ * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo,
+ * ItemHolderInfo)
+ */
+ public final void dispatchRemoveFinished(ViewHolder item) {
+ onRemoveFinished(item);
+ dispatchAnimationFinished(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a move animation is done.
+ *
+ * @param item The item which has been moved
+ * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo,
+ * ItemHolderInfo)
+ * @see RecyclerView.ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see RecyclerView.ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ */
+ public final void dispatchMoveFinished(ViewHolder item) {
+ onMoveFinished(item);
+ dispatchAnimationFinished(item);
+ }
+
+ /**
+ * Method to be called by subclasses when an add animation is done.
+ *
+ * @param item The item which has been added
+ */
+ public final void dispatchAddFinished(ViewHolder item) {
+ onAddFinished(item);
+ dispatchAnimationFinished(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a change animation is done.
+ *
+ * @param item The item which has been changed (this method must be called for
+ * each non-null ViewHolder passed into
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
+ * @param oldItem true if this is the old item that was changed, false if
+ * it is the new item that replaced the old item.
+ * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int)
+ */
+ public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) {
+ onChangeFinished(item, oldItem);
+ dispatchAnimationFinished(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a remove animation is being started.
+ *
+ * @param item The item being removed
+ */
+ public final void dispatchRemoveStarting(ViewHolder item) {
+ onRemoveStarting(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a move animation is being started.
+ *
+ * @param item The item being moved
+ */
+ public final void dispatchMoveStarting(ViewHolder item) {
+ onMoveStarting(item);
+ }
+
+ /**
+ * Method to be called by subclasses when an add animation is being started.
+ *
+ * @param item The item being added
+ */
+ public final void dispatchAddStarting(ViewHolder item) {
+ onAddStarting(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a change animation is being started.
+ *
+ * @param item The item which has been changed (this method must be called for
+ * each non-null ViewHolder passed into
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
+ * @param oldItem true if this is the old item that was changed, false if
+ * it is the new item that replaced the old item.
+ */
+ public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) {
+ onChangeStarting(item, oldItem);
+ }
+
+ /**
+ * Called when a remove animation is being started on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void onRemoveStarting(ViewHolder item) {
+ }
+
+ /**
+ * Called when a remove animation has ended on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ public void onRemoveFinished(ViewHolder item) {
+ }
+
+ /**
+ * Called when an add animation is being started on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void onAddStarting(ViewHolder item) {
+ }
+
+ /**
+ * Called when an add animation has ended on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ public void onAddFinished(ViewHolder item) {
+ }
+
+ /**
+ * Called when a move animation is being started on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void onMoveStarting(ViewHolder item) {
+ }
+
+ /**
+ * Called when a move animation has ended on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ public void onMoveFinished(ViewHolder item) {
+ }
+
+ /**
+ * Called when a change animation is being started on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ * @param oldItem true if this is the old item that was changed, false if
+ * it is the new item that replaced the old item.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void onChangeStarting(ViewHolder item, boolean oldItem) {
+ }
+
+ /**
+ * Called when a change animation has ended on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ * @param oldItem true if this is the old item that was changed, false if
+ * it is the new item that replaced the old item.
+ */
+ public void onChangeFinished(ViewHolder item, boolean oldItem) {
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/ViewInfoStore.java b/core/java/com/android/internal/widget/ViewInfoStore.java
new file mode 100644
index 0000000..6784a85
--- /dev/null
+++ b/core/java/com/android/internal/widget/ViewInfoStore.java
@@ -0,0 +1,330 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import android.util.Pools;
+
+import static com.android.internal.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
+import static com.android.internal.widget.RecyclerView.ViewHolder;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_POST;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * This class abstracts all tracking for Views to run animations.
+ */
+class ViewInfoStore {
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * View data records for pre-layout
+ */
+ @VisibleForTesting
+ final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
+
+ @VisibleForTesting
+ final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();
+
+ /**
+ * Clears the state and all existing tracking data
+ */
+ void clear() {
+ mLayoutHolderMap.clear();
+ mOldChangedHolders.clear();
+ }
+
+ /**
+ * Adds the item information to the prelayout tracking
+ * @param holder The ViewHolder whose information is being saved
+ * @param info The information to save
+ */
+ void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ record = InfoRecord.obtain();
+ mLayoutHolderMap.put(holder, record);
+ }
+ record.preInfo = info;
+ record.flags |= FLAG_PRE;
+ }
+
+ boolean isDisappearing(ViewHolder holder) {
+ final InfoRecord record = mLayoutHolderMap.get(holder);
+ return record != null && ((record.flags & FLAG_DISAPPEARED) != 0);
+ }
+
+ /**
+ * Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it.
+ *
+ * @param vh The ViewHolder whose information is being queried
+ * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
+ */
+ @Nullable
+ ItemHolderInfo popFromPreLayout(ViewHolder vh) {
+ return popFromLayoutStep(vh, FLAG_PRE);
+ }
+
+ /**
+ * Finds the ItemHolderInfo for the given ViewHolder in postLayout list and removes it.
+ *
+ * @param vh The ViewHolder whose information is being queried
+ * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
+ */
+ @Nullable
+ ItemHolderInfo popFromPostLayout(ViewHolder vh) {
+ return popFromLayoutStep(vh, FLAG_POST);
+ }
+
+ private ItemHolderInfo popFromLayoutStep(ViewHolder vh, int flag) {
+ int index = mLayoutHolderMap.indexOfKey(vh);
+ if (index < 0) {
+ return null;
+ }
+ final InfoRecord record = mLayoutHolderMap.valueAt(index);
+ if (record != null && (record.flags & flag) != 0) {
+ record.flags &= ~flag;
+ final ItemHolderInfo info;
+ if (flag == FLAG_PRE) {
+ info = record.preInfo;
+ } else if (flag == FLAG_POST) {
+ info = record.postInfo;
+ } else {
+ throw new IllegalArgumentException("Must provide flag PRE or POST");
+ }
+ // if not pre-post flag is left, clear.
+ if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) {
+ mLayoutHolderMap.removeAt(index);
+ InfoRecord.recycle(record);
+ }
+ return info;
+ }
+ return null;
+ }
+
+ /**
+ * Adds the given ViewHolder to the oldChangeHolders list
+ * @param key The key to identify the ViewHolder.
+ * @param holder The ViewHolder to store
+ */
+ void addToOldChangeHolders(long key, ViewHolder holder) {
+ mOldChangedHolders.put(key, holder);
+ }
+
+ /**
+ * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the
+ * LayoutManager during a pre-layout pass. We distinguish them from other views that were
+ * already in the pre-layout so that ItemAnimator can choose to run a different animation for
+ * them.
+ *
+ * @param holder The ViewHolder to store
+ * @param info The information to save
+ */
+ void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ record = InfoRecord.obtain();
+ mLayoutHolderMap.put(holder, record);
+ }
+ record.flags |= FLAG_APPEAR;
+ record.preInfo = info;
+ }
+
+ /**
+ * Checks whether the given ViewHolder is in preLayout list
+ * @param viewHolder The ViewHolder to query
+ *
+ * @return True if the ViewHolder is present in preLayout, false otherwise
+ */
+ boolean isInPreLayout(ViewHolder viewHolder) {
+ final InfoRecord record = mLayoutHolderMap.get(viewHolder);
+ return record != null && (record.flags & FLAG_PRE) != 0;
+ }
+
+ /**
+ * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns
+ * null.
+ * @param key The key to be used to find the ViewHolder.
+ *
+ * @return A ViewHolder if exists or null if it does not exist.
+ */
+ ViewHolder getFromOldChangeHolders(long key) {
+ return mOldChangedHolders.get(key);
+ }
+
+ /**
+ * Adds the item information to the post layout list
+ * @param holder The ViewHolder whose information is being saved
+ * @param info The information to save
+ */
+ void addToPostLayout(ViewHolder holder, ItemHolderInfo info) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ record = InfoRecord.obtain();
+ mLayoutHolderMap.put(holder, record);
+ }
+ record.postInfo = info;
+ record.flags |= FLAG_POST;
+ }
+
+ /**
+ * A ViewHolder might be added by the LayoutManager just to animate its disappearance.
+ * This list holds such items so that we can animate / recycle these ViewHolders properly.
+ *
+ * @param holder The ViewHolder which disappeared during a layout.
+ */
+ void addToDisappearedInLayout(ViewHolder holder) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ record = InfoRecord.obtain();
+ mLayoutHolderMap.put(holder, record);
+ }
+ record.flags |= FLAG_DISAPPEARED;
+ }
+
+ /**
+ * Removes a ViewHolder from disappearing list.
+ * @param holder The ViewHolder to be removed from the disappearing list.
+ */
+ void removeFromDisappearedInLayout(ViewHolder holder) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ return;
+ }
+ record.flags &= ~FLAG_DISAPPEARED;
+ }
+
+ void process(ProcessCallback callback) {
+ for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
+ final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
+ final InfoRecord record = mLayoutHolderMap.removeAt(index);
+ if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
+ // Appeared then disappeared. Not useful for animations.
+ callback.unused(viewHolder);
+ } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
+ // Set as "disappeared" by the LayoutManager (addDisappearingView)
+ if (record.preInfo == null) {
+ // similar to appear disappear but happened between different layout passes.
+ // this can happen when the layout manager is using auto-measure
+ callback.unused(viewHolder);
+ } else {
+ callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
+ }
+ } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
+ // Appeared in the layout but not in the adapter (e.g. entered the viewport)
+ callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
+ } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
+ // Persistent in both passes. Animate persistence
+ callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
+ } else if ((record.flags & FLAG_PRE) != 0) {
+ // Was in pre-layout, never been added to post layout
+ callback.processDisappeared(viewHolder, record.preInfo, null);
+ } else if ((record.flags & FLAG_POST) != 0) {
+ // Was not in pre-layout, been added to post layout
+ callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
+ } else if ((record.flags & FLAG_APPEAR) != 0) {
+ // Scrap view. RecyclerView will handle removing/recycling this.
+ } else if (DEBUG) {
+ throw new IllegalStateException("record without any reasonable flag combination:/");
+ }
+ InfoRecord.recycle(record);
+ }
+ }
+
+ /**
+ * Removes the ViewHolder from all list
+ * @param holder The ViewHolder which we should stop tracking
+ */
+ void removeViewHolder(ViewHolder holder) {
+ for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) {
+ if (holder == mOldChangedHolders.valueAt(i)) {
+ mOldChangedHolders.removeAt(i);
+ break;
+ }
+ }
+ final InfoRecord info = mLayoutHolderMap.remove(holder);
+ if (info != null) {
+ InfoRecord.recycle(info);
+ }
+ }
+
+ void onDetach() {
+ InfoRecord.drainCache();
+ }
+
+ public void onViewDetached(ViewHolder viewHolder) {
+ removeFromDisappearedInLayout(viewHolder);
+ }
+
+ interface ProcessCallback {
+ void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
+ @Nullable ItemHolderInfo postInfo);
+ void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
+ ItemHolderInfo postInfo);
+ void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
+ @NonNull ItemHolderInfo postInfo);
+ void unused(ViewHolder holder);
+ }
+
+ static class InfoRecord {
+ // disappearing list
+ static final int FLAG_DISAPPEARED = 1;
+ // appear in pre layout list
+ static final int FLAG_APPEAR = 1 << 1;
+ // pre layout, this is necessary to distinguish null item info
+ static final int FLAG_PRE = 1 << 2;
+ // post layout, this is necessary to distinguish null item info
+ static final int FLAG_POST = 1 << 3;
+ static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
+ static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
+ static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
+ int flags;
+ @Nullable ItemHolderInfo preInfo;
+ @Nullable ItemHolderInfo postInfo;
+ static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
+
+ private InfoRecord() {
+ }
+
+ static InfoRecord obtain() {
+ InfoRecord record = sPool.acquire();
+ return record == null ? new InfoRecord() : record;
+ }
+
+ static void recycle(InfoRecord record) {
+ record.flags = 0;
+ record.preInfo = null;
+ record.postInfo = null;
+ sPool.release(record);
+ }
+
+ static void drainCache() {
+ //noinspection StatementWithEmptyBody
+ while (sPool.acquire() != null);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/helper/ItemTouchHelper.java b/core/java/com/android/internal/widget/helper/ItemTouchHelper.java
new file mode 100644
index 0000000..9636ed8
--- /dev/null
+++ b/core/java/com/android/internal/widget/helper/ItemTouchHelper.java
@@ -0,0 +1,2391 @@
+/*
+ * 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.internal.widget.helper;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import android.view.animation.Interpolator;
+
+import com.android.internal.R;
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
+import com.android.internal.widget.RecyclerView.OnItemTouchListener;
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
+ * <p>
+ * It works with a RecyclerView and a Callback class, which configures what type of interactions
+ * are enabled and also receives events when user performs these actions.
+ * <p>
+ * Depending on which functionality you support, you should override
+ * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or
+ * {@link Callback#onSwiped(ViewHolder, int)}.
+ * <p>
+ * This class is designed to work with any LayoutManager but for certain situations, it can be
+ * optimized for your custom LayoutManager by extending methods in the
+ * {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler}
+ * interface in your LayoutManager.
+ * <p>
+ * By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. On
+ * platforms older than Honeycomb, ItemTouchHelper uses canvas translations and View's visibility
+ * property to move items in response to touch events. You can customize these behaviors by
+ * overriding {@link Callback#onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)}
+ * or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)}.
+ * <p/>
+ * Most of the time, you only need to override <code>onChildDraw</code> but due to limitations of
+ * platform prior to Honeycomb, you may need to implement <code>onChildDrawOver</code> as well.
+ */
+public class ItemTouchHelper extends RecyclerView.ItemDecoration
+ implements RecyclerView.OnChildAttachStateChangeListener {
+
+ /**
+ * Up direction, used for swipe & drag control.
+ */
+ public static final int UP = 1;
+
+ /**
+ * Down direction, used for swipe & drag control.
+ */
+ public static final int DOWN = 1 << 1;
+
+ /**
+ * Left direction, used for swipe & drag control.
+ */
+ public static final int LEFT = 1 << 2;
+
+ /**
+ * Right direction, used for swipe & drag control.
+ */
+ public static final int RIGHT = 1 << 3;
+
+ // If you change these relative direction values, update Callback#convertToAbsoluteDirection,
+ // Callback#convertToRelativeDirection.
+ /**
+ * Horizontal start direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
+ * direction. Used for swipe & drag control.
+ */
+ public static final int START = LEFT << 2;
+
+ /**
+ * Horizontal end direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
+ * direction. Used for swipe & drag control.
+ */
+ public static final int END = RIGHT << 2;
+
+ /**
+ * ItemTouchHelper is in idle state. At this state, either there is no related motion event by
+ * the user or latest motion events have not yet triggered a swipe or drag.
+ */
+ public static final int ACTION_STATE_IDLE = 0;
+
+ /**
+ * A View is currently being swiped.
+ */
+ public static final int ACTION_STATE_SWIPE = 1;
+
+ /**
+ * A View is currently being dragged.
+ */
+ public static final int ACTION_STATE_DRAG = 2;
+
+ /**
+ * Animation type for views which are swiped successfully.
+ */
+ public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 1 << 1;
+
+ /**
+ * Animation type for views which are not completely swiped thus will animate back to their
+ * original position.
+ */
+ public static final int ANIMATION_TYPE_SWIPE_CANCEL = 1 << 2;
+
+ /**
+ * Animation type for views that were dragged and now will animate to their final position.
+ */
+ public static final int ANIMATION_TYPE_DRAG = 1 << 3;
+
+ static final String TAG = "ItemTouchHelper";
+
+ static final boolean DEBUG = false;
+
+ static final int ACTIVE_POINTER_ID_NONE = -1;
+
+ static final int DIRECTION_FLAG_COUNT = 8;
+
+ private static final int ACTION_MODE_IDLE_MASK = (1 << DIRECTION_FLAG_COUNT) - 1;
+
+ static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT;
+
+ static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT;
+
+ /**
+ * The unit we are using to track velocity
+ */
+ private static final int PIXELS_PER_SECOND = 1000;
+
+ /**
+ * Views, whose state should be cleared after they are detached from RecyclerView.
+ * This is necessary after swipe dismissing an item. We wait until animator finishes its job
+ * to clean these views.
+ */
+ final List<View> mPendingCleanup = new ArrayList<View>();
+
+ /**
+ * Re-use array to calculate dx dy for a ViewHolder
+ */
+ private final float[] mTmpPosition = new float[2];
+
+ /**
+ * Currently selected view holder
+ */
+ ViewHolder mSelected = null;
+
+ /**
+ * The reference coordinates for the action start. For drag & drop, this is the time long
+ * press is completed vs for swipe, this is the initial touch point.
+ */
+ float mInitialTouchX;
+
+ float mInitialTouchY;
+
+ /**
+ * Set when ItemTouchHelper is assigned to a RecyclerView.
+ */
+ float mSwipeEscapeVelocity;
+
+ /**
+ * Set when ItemTouchHelper is assigned to a RecyclerView.
+ */
+ float mMaxSwipeVelocity;
+
+ /**
+ * The diff between the last event and initial touch.
+ */
+ float mDx;
+
+ float mDy;
+
+ /**
+ * The coordinates of the selected view at the time it is selected. We record these values
+ * when action starts so that we can consistently position it even if LayoutManager moves the
+ * View.
+ */
+ float mSelectedStartX;
+
+ float mSelectedStartY;
+
+ /**
+ * The pointer we are tracking.
+ */
+ int mActivePointerId = ACTIVE_POINTER_ID_NONE;
+
+ /**
+ * Developer callback which controls the behavior of ItemTouchHelper.
+ */
+ Callback mCallback;
+
+ /**
+ * Current mode.
+ */
+ int mActionState = ACTION_STATE_IDLE;
+
+ /**
+ * The direction flags obtained from unmasking
+ * {@link Callback#getAbsoluteMovementFlags(RecyclerView, ViewHolder)} for the current
+ * action state.
+ */
+ int mSelectedFlags;
+
+ /**
+ * When a View is dragged or swiped and needs to go back to where it was, we create a Recover
+ * Animation and animate it to its location using this custom Animator, instead of using
+ * framework Animators.
+ * Using framework animators has the side effect of clashing with ItemAnimator, creating
+ * jumpy UIs.
+ */
+ List<RecoverAnimation> mRecoverAnimations = new ArrayList<RecoverAnimation>();
+
+ private int mSlop;
+
+ RecyclerView mRecyclerView;
+
+ /**
+ * When user drags a view to the edge, we start scrolling the LayoutManager as long as View
+ * is partially out of bounds.
+ */
+ final Runnable mScrollRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mSelected != null && scrollIfNecessary()) {
+ if (mSelected != null) { //it might be lost during scrolling
+ moveIfNecessary(mSelected);
+ }
+ mRecyclerView.removeCallbacks(mScrollRunnable);
+ mRecyclerView.postOnAnimation(this);
+ }
+ }
+ };
+
+ /**
+ * Used for detecting fling swipe
+ */
+ VelocityTracker mVelocityTracker;
+
+ //re-used list for selecting a swap target
+ private List<ViewHolder> mSwapTargets;
+
+ //re used for for sorting swap targets
+ private List<Integer> mDistances;
+
+ /**
+ * If drag & drop is supported, we use child drawing order to bring them to front.
+ */
+ private RecyclerView.ChildDrawingOrderCallback mChildDrawingOrderCallback = null;
+
+ /**
+ * This keeps a reference to the child dragged by the user. Even after user stops dragging,
+ * until view reaches its final position (end of recover animation), we keep a reference so
+ * that it can be drawn above other children.
+ */
+ View mOverdrawChild = null;
+
+ /**
+ * We cache the position of the overdraw child to avoid recalculating it each time child
+ * position callback is called. This value is invalidated whenever a child is attached or
+ * detached.
+ */
+ int mOverdrawChildPosition = -1;
+
+ /**
+ * Used to detect long press.
+ */
+ GestureDetector mGestureDetector;
+
+ private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
+ mGestureDetector.onTouchEvent(event);
+ if (DEBUG) {
+ Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
+ }
+ final int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mActivePointerId = event.getPointerId(0);
+ mInitialTouchX = event.getX();
+ mInitialTouchY = event.getY();
+ obtainVelocityTracker();
+ if (mSelected == null) {
+ final RecoverAnimation animation = findAnimation(event);
+ if (animation != null) {
+ mInitialTouchX -= animation.mX;
+ mInitialTouchY -= animation.mY;
+ endRecoverAnimation(animation.mViewHolder, true);
+ if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
+ mCallback.clearView(mRecyclerView, animation.mViewHolder);
+ }
+ select(animation.mViewHolder, animation.mActionState);
+ updateDxDy(event, mSelectedFlags, 0);
+ }
+ }
+ } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mActivePointerId = ACTIVE_POINTER_ID_NONE;
+ select(null, ACTION_STATE_IDLE);
+ } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
+ // in a non scroll orientation, if distance change is above threshold, we
+ // can select the item
+ final int index = event.findPointerIndex(mActivePointerId);
+ if (DEBUG) {
+ Log.d(TAG, "pointer index " + index);
+ }
+ if (index >= 0) {
+ checkSelectForSwipe(action, event, index);
+ }
+ }
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(event);
+ }
+ return mSelected != null;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) {
+ mGestureDetector.onTouchEvent(event);
+ if (DEBUG) {
+ Log.d(TAG,
+ "on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event);
+ }
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(event);
+ }
+ if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
+ return;
+ }
+ final int action = event.getActionMasked();
+ final int activePointerIndex = event.findPointerIndex(mActivePointerId);
+ if (activePointerIndex >= 0) {
+ checkSelectForSwipe(action, event, activePointerIndex);
+ }
+ ViewHolder viewHolder = mSelected;
+ if (viewHolder == null) {
+ return;
+ }
+ switch (action) {
+ case MotionEvent.ACTION_MOVE: {
+ // Find the index of the active pointer and fetch its position
+ if (activePointerIndex >= 0) {
+ updateDxDy(event, mSelectedFlags, activePointerIndex);
+ moveIfNecessary(viewHolder);
+ mRecyclerView.removeCallbacks(mScrollRunnable);
+ mScrollRunnable.run();
+ mRecyclerView.invalidate();
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ // fall through
+ case MotionEvent.ACTION_UP:
+ select(null, ACTION_STATE_IDLE);
+ mActivePointerId = ACTIVE_POINTER_ID_NONE;
+ break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ final int pointerIndex = event.getActionIndex();
+ final int pointerId = event.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mActivePointerId = event.getPointerId(newPointerIndex);
+ updateDxDy(event, mSelectedFlags, pointerIndex);
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (!disallowIntercept) {
+ return;
+ }
+ select(null, ACTION_STATE_IDLE);
+ }
+ };
+
+ /**
+ * Temporary rect instance that is used when we need to lookup Item decorations.
+ */
+ private Rect mTmpRect;
+
+ /**
+ * When user started to drag scroll. Reset when we don't scroll
+ */
+ private long mDragScrollStartTimeInMs;
+
+ /**
+ * Creates an ItemTouchHelper that will work with the given Callback.
+ * <p>
+ * You can attach ItemTouchHelper to a RecyclerView via
+ * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
+ * an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
+ *
+ * @param callback The Callback which controls the behavior of this touch helper.
+ */
+ public ItemTouchHelper(Callback callback) {
+ mCallback = callback;
+ }
+
+ private static boolean hitTest(View child, float x, float y, float left, float top) {
+ return x >= left
+ && x <= left + child.getWidth()
+ && y >= top
+ && y <= top + child.getHeight();
+ }
+
+ /**
+ * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already
+ * attached to a RecyclerView, it will first detach from the previous one. You can call this
+ * method with {@code null} to detach it from the current RecyclerView.
+ *
+ * @param recyclerView The RecyclerView instance to which you want to add this helper or
+ * {@code null} if you want to remove ItemTouchHelper from the current
+ * RecyclerView.
+ */
+ public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
+ if (mRecyclerView == recyclerView) {
+ return; // nothing to do
+ }
+ if (mRecyclerView != null) {
+ destroyCallbacks();
+ }
+ mRecyclerView = recyclerView;
+ if (mRecyclerView != null) {
+ final Resources resources = recyclerView.getResources();
+ mSwipeEscapeVelocity = resources
+ .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
+ mMaxSwipeVelocity = resources
+ .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
+ setupCallbacks();
+ }
+ }
+
+ private void setupCallbacks() {
+ ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
+ mSlop = vc.getScaledTouchSlop();
+ mRecyclerView.addItemDecoration(this);
+ mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
+ mRecyclerView.addOnChildAttachStateChangeListener(this);
+ initGestureDetector();
+ }
+
+ private void destroyCallbacks() {
+ mRecyclerView.removeItemDecoration(this);
+ mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener);
+ mRecyclerView.removeOnChildAttachStateChangeListener(this);
+ // clean all attached
+ final int recoverAnimSize = mRecoverAnimations.size();
+ for (int i = recoverAnimSize - 1; i >= 0; i--) {
+ final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0);
+ mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder);
+ }
+ mRecoverAnimations.clear();
+ mOverdrawChild = null;
+ mOverdrawChildPosition = -1;
+ releaseVelocityTracker();
+ }
+
+ private void initGestureDetector() {
+ if (mGestureDetector != null) {
+ return;
+ }
+ mGestureDetector = new GestureDetector(mRecyclerView.getContext(),
+ new ItemTouchHelperGestureListener());
+ }
+
+ private void getSelectedDxDy(float[] outPosition) {
+ if ((mSelectedFlags & (LEFT | RIGHT)) != 0) {
+ outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft();
+ } else {
+ outPosition[0] = mSelected.itemView.getTranslationX();
+ }
+ if ((mSelectedFlags & (UP | DOWN)) != 0) {
+ outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop();
+ } else {
+ outPosition[1] = mSelected.itemView.getTranslationY();
+ }
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ float dx = 0, dy = 0;
+ if (mSelected != null) {
+ getSelectedDxDy(mTmpPosition);
+ dx = mTmpPosition[0];
+ dy = mTmpPosition[1];
+ }
+ mCallback.onDrawOver(c, parent, mSelected,
+ mRecoverAnimations, mActionState, dx, dy);
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ // we don't know if RV changed something so we should invalidate this index.
+ mOverdrawChildPosition = -1;
+ float dx = 0, dy = 0;
+ if (mSelected != null) {
+ getSelectedDxDy(mTmpPosition);
+ dx = mTmpPosition[0];
+ dy = mTmpPosition[1];
+ }
+ mCallback.onDraw(c, parent, mSelected,
+ mRecoverAnimations, mActionState, dx, dy);
+ }
+
+ /**
+ * Starts dragging or swiping the given View. Call with null if you want to clear it.
+ *
+ * @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the
+ * current action
+ * @param actionState The type of action
+ */
+ void select(ViewHolder selected, int actionState) {
+ if (selected == mSelected && actionState == mActionState) {
+ return;
+ }
+ mDragScrollStartTimeInMs = Long.MIN_VALUE;
+ final int prevActionState = mActionState;
+ // prevent duplicate animations
+ endRecoverAnimation(selected, true);
+ mActionState = actionState;
+ if (actionState == ACTION_STATE_DRAG) {
+ // we remove after animation is complete. this means we only elevate the last drag
+ // child but that should perform good enough as it is very hard to start dragging a
+ // new child before the previous one settles.
+ mOverdrawChild = selected.itemView;
+ addChildDrawingOrderCallback();
+ }
+ int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))
+ - 1;
+ boolean preventLayout = false;
+
+ if (mSelected != null) {
+ final ViewHolder prevSelected = mSelected;
+ if (prevSelected.itemView.getParent() != null) {
+ final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
+ : swipeIfNecessary(prevSelected);
+ releaseVelocityTracker();
+ // find where we should animate to
+ final float targetTranslateX, targetTranslateY;
+ int animationType;
+ switch (swipeDir) {
+ case LEFT:
+ case RIGHT:
+ case START:
+ case END:
+ targetTranslateY = 0;
+ targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
+ break;
+ case UP:
+ case DOWN:
+ targetTranslateX = 0;
+ targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
+ break;
+ default:
+ targetTranslateX = 0;
+ targetTranslateY = 0;
+ }
+ if (prevActionState == ACTION_STATE_DRAG) {
+ animationType = ANIMATION_TYPE_DRAG;
+ } else if (swipeDir > 0) {
+ animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
+ } else {
+ animationType = ANIMATION_TYPE_SWIPE_CANCEL;
+ }
+ getSelectedDxDy(mTmpPosition);
+ final float currentTranslateX = mTmpPosition[0];
+ final float currentTranslateY = mTmpPosition[1];
+ final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
+ prevActionState, currentTranslateX, currentTranslateY,
+ targetTranslateX, targetTranslateY) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (this.mOverridden) {
+ return;
+ }
+ if (swipeDir <= 0) {
+ // this is a drag or failed swipe. recover immediately
+ mCallback.clearView(mRecyclerView, prevSelected);
+ // full cleanup will happen on onDrawOver
+ } else {
+ // wait until remove animation is complete.
+ mPendingCleanup.add(prevSelected.itemView);
+ mIsPendingCleanup = true;
+ if (swipeDir > 0) {
+ // Animation might be ended by other animators during a layout.
+ // We defer callback to avoid editing adapter during a layout.
+ postDispatchSwipe(this, swipeDir);
+ }
+ }
+ // removed from the list after it is drawn for the last time
+ if (mOverdrawChild == prevSelected.itemView) {
+ removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
+ }
+ }
+ };
+ final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
+ targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
+ rv.setDuration(duration);
+ mRecoverAnimations.add(rv);
+ rv.start();
+ preventLayout = true;
+ } else {
+ removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
+ mCallback.clearView(mRecyclerView, prevSelected);
+ }
+ mSelected = null;
+ }
+ if (selected != null) {
+ mSelectedFlags =
+ (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
+ >> (mActionState * DIRECTION_FLAG_COUNT);
+ mSelectedStartX = selected.itemView.getLeft();
+ mSelectedStartY = selected.itemView.getTop();
+ mSelected = selected;
+
+ if (actionState == ACTION_STATE_DRAG) {
+ mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
+ }
+ final ViewParent rvParent = mRecyclerView.getParent();
+ if (rvParent != null) {
+ rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
+ }
+ if (!preventLayout) {
+ mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
+ }
+ mCallback.onSelectedChanged(mSelected, mActionState);
+ mRecyclerView.invalidate();
+ }
+
+ void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
+ // wait until animations are complete.
+ mRecyclerView.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mRecyclerView != null && mRecyclerView.isAttachedToWindow()
+ && !anim.mOverridden
+ && anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
+ final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
+ // if animator is running or we have other active recover animations, we try
+ // not to call onSwiped because DefaultItemAnimator is not good at merging
+ // animations. Instead, we wait and batch.
+ if ((animator == null || !animator.isRunning(null))
+ && !hasRunningRecoverAnim()) {
+ mCallback.onSwiped(anim.mViewHolder, swipeDir);
+ } else {
+ mRecyclerView.post(this);
+ }
+ }
+ }
+ });
+ }
+
+ boolean hasRunningRecoverAnim() {
+ final int size = mRecoverAnimations.size();
+ for (int i = 0; i < size; i++) {
+ if (!mRecoverAnimations.get(i).mEnded) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If user drags the view to the edge, trigger a scroll if necessary.
+ */
+ boolean scrollIfNecessary() {
+ if (mSelected == null) {
+ mDragScrollStartTimeInMs = Long.MIN_VALUE;
+ return false;
+ }
+ final long now = System.currentTimeMillis();
+ final long scrollDuration = mDragScrollStartTimeInMs
+ == Long.MIN_VALUE ? 0 : now - mDragScrollStartTimeInMs;
+ RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
+ if (mTmpRect == null) {
+ mTmpRect = new Rect();
+ }
+ int scrollX = 0;
+ int scrollY = 0;
+ lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect);
+ if (lm.canScrollHorizontally()) {
+ int curX = (int) (mSelectedStartX + mDx);
+ final int leftDiff = curX - mTmpRect.left - mRecyclerView.getPaddingLeft();
+ if (mDx < 0 && leftDiff < 0) {
+ scrollX = leftDiff;
+ } else if (mDx > 0) {
+ final int rightDiff =
+ curX + mSelected.itemView.getWidth() + mTmpRect.right
+ - (mRecyclerView.getWidth() - mRecyclerView.getPaddingRight());
+ if (rightDiff > 0) {
+ scrollX = rightDiff;
+ }
+ }
+ }
+ if (lm.canScrollVertically()) {
+ int curY = (int) (mSelectedStartY + mDy);
+ final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
+ if (mDy < 0 && topDiff < 0) {
+ scrollY = topDiff;
+ } else if (mDy > 0) {
+ final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom
+ - (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
+ if (bottomDiff > 0) {
+ scrollY = bottomDiff;
+ }
+ }
+ }
+ if (scrollX != 0) {
+ scrollX = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
+ mSelected.itemView.getWidth(), scrollX,
+ mRecyclerView.getWidth(), scrollDuration);
+ }
+ if (scrollY != 0) {
+ scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
+ mSelected.itemView.getHeight(), scrollY,
+ mRecyclerView.getHeight(), scrollDuration);
+ }
+ if (scrollX != 0 || scrollY != 0) {
+ if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
+ mDragScrollStartTimeInMs = now;
+ }
+ mRecyclerView.scrollBy(scrollX, scrollY);
+ return true;
+ }
+ mDragScrollStartTimeInMs = Long.MIN_VALUE;
+ return false;
+ }
+
+ private List<ViewHolder> findSwapTargets(ViewHolder viewHolder) {
+ if (mSwapTargets == null) {
+ mSwapTargets = new ArrayList<ViewHolder>();
+ mDistances = new ArrayList<Integer>();
+ } else {
+ mSwapTargets.clear();
+ mDistances.clear();
+ }
+ final int margin = mCallback.getBoundingBoxMargin();
+ final int left = Math.round(mSelectedStartX + mDx) - margin;
+ final int top = Math.round(mSelectedStartY + mDy) - margin;
+ final int right = left + viewHolder.itemView.getWidth() + 2 * margin;
+ final int bottom = top + viewHolder.itemView.getHeight() + 2 * margin;
+ final int centerX = (left + right) / 2;
+ final int centerY = (top + bottom) / 2;
+ final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
+ final int childCount = lm.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View other = lm.getChildAt(i);
+ if (other == viewHolder.itemView) {
+ continue; //myself!
+ }
+ if (other.getBottom() < top || other.getTop() > bottom
+ || other.getRight() < left || other.getLeft() > right) {
+ continue;
+ }
+ final ViewHolder otherVh = mRecyclerView.getChildViewHolder(other);
+ if (mCallback.canDropOver(mRecyclerView, mSelected, otherVh)) {
+ // find the index to add
+ final int dx = Math.abs(centerX - (other.getLeft() + other.getRight()) / 2);
+ final int dy = Math.abs(centerY - (other.getTop() + other.getBottom()) / 2);
+ final int dist = dx * dx + dy * dy;
+
+ int pos = 0;
+ final int cnt = mSwapTargets.size();
+ for (int j = 0; j < cnt; j++) {
+ if (dist > mDistances.get(j)) {
+ pos++;
+ } else {
+ break;
+ }
+ }
+ mSwapTargets.add(pos, otherVh);
+ mDistances.add(pos, dist);
+ }
+ }
+ return mSwapTargets;
+ }
+
+ /**
+ * Checks if we should swap w/ another view holder.
+ */
+ void moveIfNecessary(ViewHolder viewHolder) {
+ if (mRecyclerView.isLayoutRequested()) {
+ return;
+ }
+ if (mActionState != ACTION_STATE_DRAG) {
+ return;
+ }
+
+ final float threshold = mCallback.getMoveThreshold(viewHolder);
+ final int x = (int) (mSelectedStartX + mDx);
+ final int y = (int) (mSelectedStartY + mDy);
+ if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold
+ && Math.abs(x - viewHolder.itemView.getLeft())
+ < viewHolder.itemView.getWidth() * threshold) {
+ return;
+ }
+ List<ViewHolder> swapTargets = findSwapTargets(viewHolder);
+ if (swapTargets.size() == 0) {
+ return;
+ }
+ // may swap.
+ ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y);
+ if (target == null) {
+ mSwapTargets.clear();
+ mDistances.clear();
+ return;
+ }
+ final int toPosition = target.getAdapterPosition();
+ final int fromPosition = viewHolder.getAdapterPosition();
+ if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
+ // keep target visible
+ mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
+ target, toPosition, x, y);
+ }
+ }
+
+ @Override
+ public void onChildViewAttachedToWindow(View view) {
+ }
+
+ @Override
+ public void onChildViewDetachedFromWindow(View view) {
+ removeChildDrawingOrderCallbackIfNecessary(view);
+ final ViewHolder holder = mRecyclerView.getChildViewHolder(view);
+ if (holder == null) {
+ return;
+ }
+ if (mSelected != null && holder == mSelected) {
+ select(null, ACTION_STATE_IDLE);
+ } else {
+ endRecoverAnimation(holder, false); // this may push it into pending cleanup list.
+ if (mPendingCleanup.remove(holder.itemView)) {
+ mCallback.clearView(mRecyclerView, holder);
+ }
+ }
+ }
+
+ /**
+ * Returns the animation type or 0 if cannot be found.
+ */
+ int endRecoverAnimation(ViewHolder viewHolder, boolean override) {
+ final int recoverAnimSize = mRecoverAnimations.size();
+ for (int i = recoverAnimSize - 1; i >= 0; i--) {
+ final RecoverAnimation anim = mRecoverAnimations.get(i);
+ if (anim.mViewHolder == viewHolder) {
+ anim.mOverridden |= override;
+ if (!anim.mEnded) {
+ anim.cancel();
+ }
+ mRecoverAnimations.remove(i);
+ return anim.mAnimationType;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ outRect.setEmpty();
+ }
+
+ void obtainVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ }
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+
+ private void releaseVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private ViewHolder findSwipedView(MotionEvent motionEvent) {
+ final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
+ if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
+ return null;
+ }
+ final int pointerIndex = motionEvent.findPointerIndex(mActivePointerId);
+ final float dx = motionEvent.getX(pointerIndex) - mInitialTouchX;
+ final float dy = motionEvent.getY(pointerIndex) - mInitialTouchY;
+ final float absDx = Math.abs(dx);
+ final float absDy = Math.abs(dy);
+
+ if (absDx < mSlop && absDy < mSlop) {
+ return null;
+ }
+ if (absDx > absDy && lm.canScrollHorizontally()) {
+ return null;
+ } else if (absDy > absDx && lm.canScrollVertically()) {
+ return null;
+ }
+ View child = findChildView(motionEvent);
+ if (child == null) {
+ return null;
+ }
+ return mRecyclerView.getChildViewHolder(child);
+ }
+
+ /**
+ * Checks whether we should select a View for swiping.
+ */
+ boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
+ if (mSelected != null || action != MotionEvent.ACTION_MOVE
+ || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) {
+ return false;
+ }
+ if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
+ return false;
+ }
+ final ViewHolder vh = findSwipedView(motionEvent);
+ if (vh == null) {
+ return false;
+ }
+ final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh);
+
+ final int swipeFlags = (movementFlags & ACTION_MODE_SWIPE_MASK)
+ >> (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE);
+
+ if (swipeFlags == 0) {
+ return false;
+ }
+
+ // mDx and mDy are only set in allowed directions. We use custom x/y here instead of
+ // updateDxDy to avoid swiping if user moves more in the other direction
+ final float x = motionEvent.getX(pointerIndex);
+ final float y = motionEvent.getY(pointerIndex);
+
+ // Calculate the distance moved
+ final float dx = x - mInitialTouchX;
+ final float dy = y - mInitialTouchY;
+ // swipe target is chose w/o applying flags so it does not really check if swiping in that
+ // direction is allowed. This why here, we use mDx mDy to check slope value again.
+ final float absDx = Math.abs(dx);
+ final float absDy = Math.abs(dy);
+
+ if (absDx < mSlop && absDy < mSlop) {
+ return false;
+ }
+ if (absDx > absDy) {
+ if (dx < 0 && (swipeFlags & LEFT) == 0) {
+ return false;
+ }
+ if (dx > 0 && (swipeFlags & RIGHT) == 0) {
+ return false;
+ }
+ } else {
+ if (dy < 0 && (swipeFlags & UP) == 0) {
+ return false;
+ }
+ if (dy > 0 && (swipeFlags & DOWN) == 0) {
+ return false;
+ }
+ }
+ mDx = mDy = 0f;
+ mActivePointerId = motionEvent.getPointerId(0);
+ select(vh, ACTION_STATE_SWIPE);
+ return true;
+ }
+
+ View findChildView(MotionEvent event) {
+ // first check elevated views, if none, then call RV
+ final float x = event.getX();
+ final float y = event.getY();
+ if (mSelected != null) {
+ final View selectedView = mSelected.itemView;
+ if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {
+ return selectedView;
+ }
+ }
+ for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
+ final RecoverAnimation anim = mRecoverAnimations.get(i);
+ final View view = anim.mViewHolder.itemView;
+ if (hitTest(view, x, y, anim.mX, anim.mY)) {
+ return view;
+ }
+ }
+ return mRecyclerView.findChildViewUnder(x, y);
+ }
+
+ /**
+ * Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a
+ * View is long pressed. You can disable that behavior by overriding
+ * {@link ItemTouchHelper.Callback#isLongPressDragEnabled()}.
+ * <p>
+ * For this method to work:
+ * <ul>
+ * <li>The provided ViewHolder must be a child of the RecyclerView to which this
+ * ItemTouchHelper
+ * is attached.</li>
+ * <li>{@link ItemTouchHelper.Callback} must have dragging enabled.</li>
+ * <li>There must be a previous touch event that was reported to the ItemTouchHelper
+ * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
+ * grabs previous events, this should work as expected.</li>
+ * </ul>
+ *
+ * For example, if you would like to let your user to be able to drag an Item by touching one
+ * of its descendants, you may implement it as follows:
+ * <pre>
+ * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
+ * public boolean onTouch(View v, MotionEvent event) {
+ * if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ * mItemTouchHelper.startDrag(viewHolder);
+ * }
+ * return false;
+ * }
+ * });
+ * </pre>
+ * <p>
+ *
+ * @param viewHolder The ViewHolder to start dragging. It must be a direct child of
+ * RecyclerView.
+ * @see ItemTouchHelper.Callback#isItemViewSwipeEnabled()
+ */
+ public void startDrag(ViewHolder viewHolder) {
+ if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) {
+ Log.e(TAG, "Start drag has been called but dragging is not enabled");
+ return;
+ }
+ if (viewHolder.itemView.getParent() != mRecyclerView) {
+ Log.e(TAG, "Start drag has been called with a view holder which is not a child of "
+ + "the RecyclerView which is controlled by this ItemTouchHelper.");
+ return;
+ }
+ obtainVelocityTracker();
+ mDx = mDy = 0f;
+ select(viewHolder, ACTION_STATE_DRAG);
+ }
+
+ /**
+ * Starts swiping the provided ViewHolder. By default, ItemTouchHelper starts swiping a View
+ * when user swipes their finger (or mouse pointer) over the View. You can disable this
+ * behavior
+ * by overriding {@link ItemTouchHelper.Callback}
+ * <p>
+ * For this method to work:
+ * <ul>
+ * <li>The provided ViewHolder must be a child of the RecyclerView to which this
+ * ItemTouchHelper is attached.</li>
+ * <li>{@link ItemTouchHelper.Callback} must have swiping enabled.</li>
+ * <li>There must be a previous touch event that was reported to the ItemTouchHelper
+ * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
+ * grabs previous events, this should work as expected.</li>
+ * </ul>
+ *
+ * For example, if you would like to let your user to be able to swipe an Item by touching one
+ * of its descendants, you may implement it as follows:
+ * <pre>
+ * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
+ * public boolean onTouch(View v, MotionEvent event) {
+ * if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ * mItemTouchHelper.startSwipe(viewHolder);
+ * }
+ * return false;
+ * }
+ * });
+ * </pre>
+ *
+ * @param viewHolder The ViewHolder to start swiping. It must be a direct child of
+ * RecyclerView.
+ */
+ public void startSwipe(ViewHolder viewHolder) {
+ if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) {
+ Log.e(TAG, "Start swipe has been called but swiping is not enabled");
+ return;
+ }
+ if (viewHolder.itemView.getParent() != mRecyclerView) {
+ Log.e(TAG, "Start swipe has been called with a view holder which is not a child of "
+ + "the RecyclerView controlled by this ItemTouchHelper.");
+ return;
+ }
+ obtainVelocityTracker();
+ mDx = mDy = 0f;
+ select(viewHolder, ACTION_STATE_SWIPE);
+ }
+
+ RecoverAnimation findAnimation(MotionEvent event) {
+ if (mRecoverAnimations.isEmpty()) {
+ return null;
+ }
+ View target = findChildView(event);
+ for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
+ final RecoverAnimation anim = mRecoverAnimations.get(i);
+ if (anim.mViewHolder.itemView == target) {
+ return anim;
+ }
+ }
+ return null;
+ }
+
+ void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
+
+ // Calculate the distance moved
+ mDx = x - mInitialTouchX;
+ mDy = y - mInitialTouchY;
+ if ((directionFlags & LEFT) == 0) {
+ mDx = Math.max(0, mDx);
+ }
+ if ((directionFlags & RIGHT) == 0) {
+ mDx = Math.min(0, mDx);
+ }
+ if ((directionFlags & UP) == 0) {
+ mDy = Math.max(0, mDy);
+ }
+ if ((directionFlags & DOWN) == 0) {
+ mDy = Math.min(0, mDy);
+ }
+ }
+
+ private int swipeIfNecessary(ViewHolder viewHolder) {
+ if (mActionState == ACTION_STATE_DRAG) {
+ return 0;
+ }
+ final int originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder);
+ final int absoluteMovementFlags = mCallback.convertToAbsoluteDirection(
+ originalMovementFlags,
+ mRecyclerView.getLayoutDirection());
+ final int flags = (absoluteMovementFlags
+ & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
+ if (flags == 0) {
+ return 0;
+ }
+ final int originalFlags = (originalMovementFlags
+ & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
+ int swipeDir;
+ if (Math.abs(mDx) > Math.abs(mDy)) {
+ if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
+ // if swipe dir is not in original flags, it should be the relative direction
+ if ((originalFlags & swipeDir) == 0) {
+ // convert to relative
+ return Callback.convertToRelativeDirection(swipeDir,
+ mRecyclerView.getLayoutDirection());
+ }
+ return swipeDir;
+ }
+ if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
+ return swipeDir;
+ }
+ } else {
+ if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
+ return swipeDir;
+ }
+ if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
+ // if swipe dir is not in original flags, it should be the relative direction
+ if ((originalFlags & swipeDir) == 0) {
+ // convert to relative
+ return Callback.convertToRelativeDirection(swipeDir,
+ mRecyclerView.getLayoutDirection());
+ }
+ return swipeDir;
+ }
+ }
+ return 0;
+ }
+
+ private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) {
+ if ((flags & (LEFT | RIGHT)) != 0) {
+ final int dirFlag = mDx > 0 ? RIGHT : LEFT;
+ if (mVelocityTracker != null && mActivePointerId > -1) {
+ mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
+ mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
+ final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId);
+ final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId);
+ final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT;
+ final float absXVelocity = Math.abs(xVelocity);
+ if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag
+ && absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity)
+ && absXVelocity > Math.abs(yVelocity)) {
+ return velDirFlag;
+ }
+ }
+
+ final float threshold = mRecyclerView.getWidth() * mCallback
+ .getSwipeThreshold(viewHolder);
+
+ if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) {
+ return dirFlag;
+ }
+ }
+ return 0;
+ }
+
+ private int checkVerticalSwipe(ViewHolder viewHolder, int flags) {
+ if ((flags & (UP | DOWN)) != 0) {
+ final int dirFlag = mDy > 0 ? DOWN : UP;
+ if (mVelocityTracker != null && mActivePointerId > -1) {
+ mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
+ mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
+ final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId);
+ final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId);
+ final int velDirFlag = yVelocity > 0f ? DOWN : UP;
+ final float absYVelocity = Math.abs(yVelocity);
+ if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag
+ && absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity)
+ && absYVelocity > Math.abs(xVelocity)) {
+ return velDirFlag;
+ }
+ }
+
+ final float threshold = mRecyclerView.getHeight() * mCallback
+ .getSwipeThreshold(viewHolder);
+ if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) {
+ return dirFlag;
+ }
+ }
+ return 0;
+ }
+
+ private void addChildDrawingOrderCallback() {
+ if (Build.VERSION.SDK_INT >= 21) {
+ return; // we use elevation on Lollipop
+ }
+ if (mChildDrawingOrderCallback == null) {
+ mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() {
+ @Override
+ public int onGetChildDrawingOrder(int childCount, int i) {
+ if (mOverdrawChild == null) {
+ return i;
+ }
+ int childPosition = mOverdrawChildPosition;
+ if (childPosition == -1) {
+ childPosition = mRecyclerView.indexOfChild(mOverdrawChild);
+ mOverdrawChildPosition = childPosition;
+ }
+ if (i == childCount - 1) {
+ return childPosition;
+ }
+ return i < childPosition ? i : i + 1;
+ }
+ };
+ }
+ mRecyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback);
+ }
+
+ void removeChildDrawingOrderCallbackIfNecessary(View view) {
+ if (view == mOverdrawChild) {
+ mOverdrawChild = null;
+ // only remove if we've added
+ if (mChildDrawingOrderCallback != null) {
+ mRecyclerView.setChildDrawingOrderCallback(null);
+ }
+ }
+ }
+
+ /**
+ * An interface which can be implemented by LayoutManager for better integration with
+ * {@link ItemTouchHelper}.
+ */
+ public interface ViewDropHandler {
+
+ /**
+ * Called by the {@link ItemTouchHelper} after a View is dropped over another View.
+ * <p>
+ * A LayoutManager should implement this interface to get ready for the upcoming move
+ * operation.
+ * <p>
+ * For example, LinearLayoutManager sets up a "scrollToPositionWithOffset" calls so that
+ * the View under drag will be used as an anchor View while calculating the next layout,
+ * making layout stay consistent.
+ *
+ * @param view The View which is being dragged. It is very likely that user is still
+ * dragging this View so there might be other
+ * {@link #prepareForDrop(View, View, int, int)} after this one.
+ * @param target The target view which is being dropped on.
+ * @param x The <code>left</code> offset of the View that is being dragged. This value
+ * includes the movement caused by the user.
+ * @param y The <code>top</code> offset of the View that is being dragged. This value
+ * includes the movement caused by the user.
+ */
+ void prepareForDrop(View view, View target, int x, int y);
+ }
+
+ /**
+ * This class is the contract between ItemTouchHelper and your application. It lets you control
+ * which touch behaviors are enabled per each ViewHolder and also receive callbacks when user
+ * performs these actions.
+ * <p>
+ * To control which actions user can take on each view, you should override
+ * {@link #getMovementFlags(RecyclerView, ViewHolder)} and return appropriate set
+ * of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END},
+ * {@link #UP}, {@link #DOWN}). You can use
+ * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use
+ * {@link SimpleCallback}.
+ * <p>
+ * If user drags an item, ItemTouchHelper will call
+ * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)
+ * onMove(recyclerView, dragged, target)}.
+ * Upon receiving this callback, you should move the item from the old position
+ * ({@code dragged.getAdapterPosition()}) to new position ({@code target.getAdapterPosition()})
+ * in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}.
+ * To control where a View can be dropped, you can override
+ * {@link #canDropOver(RecyclerView, ViewHolder, ViewHolder)}. When a
+ * dragging View overlaps multiple other views, Callback chooses the closest View with which
+ * dragged View might have changed positions. Although this approach works for many use cases,
+ * if you have a custom LayoutManager, you can override
+ * {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)} to select a
+ * custom drop target.
+ * <p>
+ * When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls
+ * {@link #onSwiped(ViewHolder, int)}. At this point, you should update your
+ * adapter (e.g. remove the item) and call related Adapter#notify event.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public abstract static class Callback {
+
+ public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200;
+
+ public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250;
+
+ static final int RELATIVE_DIR_FLAGS = START | END
+ | ((START | END) << DIRECTION_FLAG_COUNT)
+ | ((START | END) << (2 * DIRECTION_FLAG_COUNT));
+
+ private static final ItemTouchUIUtil sUICallback = new ItemTouchUIUtilImpl();
+
+ private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT
+ | ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT)
+ | ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT));
+
+ private static final Interpolator sDragScrollInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ return t * t * t * t * t;
+ }
+ };
+
+ private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
+ }
+ };
+
+ /**
+ * Drag scroll speed keeps accelerating until this many milliseconds before being capped.
+ */
+ private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
+
+ private int mCachedMaxScrollSpeed = -1;
+
+ /**
+ * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for
+ * visual
+ * changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different
+ * implementations for different platform versions.
+ * <p>
+ * By default, {@link Callback} applies these changes on
+ * {@link RecyclerView.ViewHolder#itemView}.
+ * <p>
+ * For example, if you have a use case where you only want the text to move when user
+ * swipes over the view, you can do the following:
+ * <pre>
+ * public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
+ * getDefaultUIUtil().clearView(((ItemTouchViewHolder) viewHolder).textView);
+ * }
+ * public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
+ * if (viewHolder != null){
+ * getDefaultUIUtil().onSelected(((ItemTouchViewHolder) viewHolder).textView);
+ * }
+ * }
+ * public void onChildDraw(Canvas c, RecyclerView recyclerView,
+ * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
+ * boolean isCurrentlyActive) {
+ * getDefaultUIUtil().onDraw(c, recyclerView,
+ * ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
+ * actionState, isCurrentlyActive);
+ * return true;
+ * }
+ * public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
+ * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
+ * boolean isCurrentlyActive) {
+ * getDefaultUIUtil().onDrawOver(c, recyclerView,
+ * ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
+ * actionState, isCurrentlyActive);
+ * return true;
+ * }
+ * </pre>
+ *
+ * @return The {@link ItemTouchUIUtil} instance that is used by the {@link Callback}
+ */
+ public static ItemTouchUIUtil getDefaultUIUtil() {
+ return sUICallback;
+ }
+
+ /**
+ * Replaces a movement direction with its relative version by taking layout direction into
+ * account.
+ *
+ * @param flags The flag value that include any number of movement flags.
+ * @param layoutDirection The layout direction of the View. Can be obtained from
+ * {@link View#getLayoutDirection()}.
+ * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead
+ * of {@link #LEFT}, {@link #RIGHT}.
+ * @see #convertToAbsoluteDirection(int, int)
+ */
+ public static int convertToRelativeDirection(int flags, int layoutDirection) {
+ int masked = flags & ABS_HORIZONTAL_DIR_FLAGS;
+ if (masked == 0) {
+ return flags; // does not have any abs flags, good.
+ }
+ flags &= ~masked; //remove left / right.
+ if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ // no change. just OR with 2 bits shifted mask and return
+ flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
+ return flags;
+ } else {
+ // add RIGHT flag as START
+ flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS);
+ // first clean RIGHT bit then add LEFT flag as END
+ flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2;
+ }
+ return flags;
+ }
+
+ /**
+ * Convenience method to create movement flags.
+ * <p>
+ * For instance, if you want to let your items be drag & dropped vertically and swiped
+ * left to be dismissed, you can call this method with:
+ * <code>makeMovementFlags(UP | DOWN, LEFT);</code>
+ *
+ * @param dragFlags The directions in which the item can be dragged.
+ * @param swipeFlags The directions in which the item can be swiped.
+ * @return Returns an integer composed of the given drag and swipe flags.
+ */
+ public static int makeMovementFlags(int dragFlags, int swipeFlags) {
+ return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags)
+ | makeFlag(ACTION_STATE_SWIPE, swipeFlags)
+ | makeFlag(ACTION_STATE_DRAG, dragFlags);
+ }
+
+ /**
+ * Shifts the given direction flags to the offset of the given action state.
+ *
+ * @param actionState The action state you want to get flags in. Should be one of
+ * {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or
+ * {@link #ACTION_STATE_DRAG}.
+ * @param directions The direction flags. Can be composed from {@link #UP}, {@link #DOWN},
+ * {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}.
+ * @return And integer that represents the given directions in the provided actionState.
+ */
+ public static int makeFlag(int actionState, int directions) {
+ return directions << (actionState * DIRECTION_FLAG_COUNT);
+ }
+
+ /**
+ * Should return a composite flag which defines the enabled move directions in each state
+ * (idle, swiping, dragging).
+ * <p>
+ * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int,
+ * int)}
+ * or {@link #makeFlag(int, int)}.
+ * <p>
+ * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next
+ * 8 bits are for SWIPE state and third 8 bits are for DRAG state.
+ * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in
+ * {@link ItemTouchHelper}.
+ * <p>
+ * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to
+ * swipe by swiping RIGHT, you can return:
+ * <pre>
+ * makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
+ * </pre>
+ * This means, allow right movement while IDLE and allow right and left movement while
+ * swiping.
+ *
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached.
+ * @param viewHolder The ViewHolder for which the movement information is necessary.
+ * @return flags specifying which movements are allowed on this ViewHolder.
+ * @see #makeMovementFlags(int, int)
+ * @see #makeFlag(int, int)
+ */
+ public abstract int getMovementFlags(RecyclerView recyclerView,
+ ViewHolder viewHolder);
+
+ /**
+ * Converts a given set of flags to absolution direction which means {@link #START} and
+ * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout
+ * direction.
+ *
+ * @param flags The flag value that include any number of movement flags.
+ * @param layoutDirection The layout direction of the RecyclerView.
+ * @return Updated flags which includes only absolute direction values.
+ */
+ public int convertToAbsoluteDirection(int flags, int layoutDirection) {
+ int masked = flags & RELATIVE_DIR_FLAGS;
+ if (masked == 0) {
+ return flags; // does not have any relative flags, good.
+ }
+ flags &= ~masked; //remove start / end
+ if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ // no change. just OR with 2 bits shifted mask and return
+ flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
+ return flags;
+ } else {
+ // add START flag as RIGHT
+ flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS);
+ // first clean start bit then add END flag as LEFT
+ flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2;
+ }
+ return flags;
+ }
+
+ final int getAbsoluteMovementFlags(RecyclerView recyclerView,
+ ViewHolder viewHolder) {
+ final int flags = getMovementFlags(recyclerView, viewHolder);
+ return convertToAbsoluteDirection(flags, recyclerView.getLayoutDirection());
+ }
+
+ boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) {
+ final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
+ return (flags & ACTION_MODE_DRAG_MASK) != 0;
+ }
+
+ boolean hasSwipeFlag(RecyclerView recyclerView,
+ ViewHolder viewHolder) {
+ final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
+ return (flags & ACTION_MODE_SWIPE_MASK) != 0;
+ }
+
+ /**
+ * Return true if the current ViewHolder can be dropped over the the target ViewHolder.
+ * <p>
+ * This method is used when selecting drop target for the dragged View. After Views are
+ * eliminated either via bounds check or via this method, resulting set of views will be
+ * passed to {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)}.
+ * <p>
+ * Default implementation returns true.
+ *
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
+ * @param current The ViewHolder that user is dragging.
+ * @param target The ViewHolder which is below the dragged ViewHolder.
+ * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false
+ * otherwise.
+ */
+ public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
+ ViewHolder target) {
+ return true;
+ }
+
+ /**
+ * Called when ItemTouchHelper wants to move the dragged item from its old position to
+ * the new position.
+ * <p>
+ * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
+ * to the adapter position of {@code target} ViewHolder
+ * ({@link ViewHolder#getAdapterPosition()
+ * ViewHolder#getAdapterPosition()}).
+ * <p>
+ * If you don't support drag & drop, this method will never be called.
+ *
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
+ * @param viewHolder The ViewHolder which is being dragged by the user.
+ * @param target The ViewHolder over which the currently active item is being
+ * dragged.
+ * @return True if the {@code viewHolder} has been moved to the adapter position of
+ * {@code target}.
+ * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
+ */
+ public abstract boolean onMove(RecyclerView recyclerView,
+ ViewHolder viewHolder, ViewHolder target);
+
+ /**
+ * Returns whether ItemTouchHelper should start a drag and drop operation if an item is
+ * long pressed.
+ * <p>
+ * Default value returns true but you may want to disable this if you want to start
+ * dragging on a custom view touch using {@link #startDrag(ViewHolder)}.
+ *
+ * @return True if ItemTouchHelper should start dragging an item when it is long pressed,
+ * false otherwise. Default value is <code>true</code>.
+ * @see #startDrag(ViewHolder)
+ */
+ public boolean isLongPressDragEnabled() {
+ return true;
+ }
+
+ /**
+ * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped
+ * over the View.
+ * <p>
+ * Default value returns true but you may want to disable this if you want to start
+ * swiping on a custom view touch using {@link #startSwipe(ViewHolder)}.
+ *
+ * @return True if ItemTouchHelper should start swiping an item when user swipes a pointer
+ * over the View, false otherwise. Default value is <code>true</code>.
+ * @see #startSwipe(ViewHolder)
+ */
+ public boolean isItemViewSwipeEnabled() {
+ return true;
+ }
+
+ /**
+ * When finding views under a dragged view, by default, ItemTouchHelper searches for views
+ * that overlap with the dragged View. By overriding this method, you can extend or shrink
+ * the search box.
+ *
+ * @return The extra margin to be added to the hit box of the dragged View.
+ */
+ public int getBoundingBoxMargin() {
+ return 0;
+ }
+
+ /**
+ * Returns the fraction that the user should move the View to be considered as swiped.
+ * The fraction is calculated with respect to RecyclerView's bounds.
+ * <p>
+ * Default value is .5f, which means, to swipe a View, user must move the View at least
+ * half of RecyclerView's width or height, depending on the swipe direction.
+ *
+ * @param viewHolder The ViewHolder that is being dragged.
+ * @return A float value that denotes the fraction of the View size. Default value
+ * is .5f .
+ */
+ public float getSwipeThreshold(ViewHolder viewHolder) {
+ return .5f;
+ }
+
+ /**
+ * Returns the fraction that the user should move the View to be considered as it is
+ * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
+ * below it for a possible drop.
+ *
+ * @param viewHolder The ViewHolder that is being dragged.
+ * @return A float value that denotes the fraction of the View size. Default value is
+ * .5f .
+ */
+ public float getMoveThreshold(ViewHolder viewHolder) {
+ return .5f;
+ }
+
+ /**
+ * Defines the minimum velocity which will be considered as a swipe action by the user.
+ * <p>
+ * You can increase this value to make it harder to swipe or decrease it to make it easier.
+ * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure
+ * current direction velocity is larger then the perpendicular one. Otherwise, user's
+ * movement is ambiguous. You can change the threshold by overriding
+ * {@link #getSwipeVelocityThreshold(float)}.
+ * <p>
+ * The velocity is calculated in pixels per second.
+ * <p>
+ * The default framework value is passed as a parameter so that you can modify it with a
+ * multiplier.
+ *
+ * @param defaultValue The default value (in pixels per second) used by the
+ * ItemTouchHelper.
+ * @return The minimum swipe velocity. The default implementation returns the
+ * <code>defaultValue</code> parameter.
+ * @see #getSwipeVelocityThreshold(float)
+ * @see #getSwipeThreshold(ViewHolder)
+ */
+ public float getSwipeEscapeVelocity(float defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements.
+ * <p>
+ * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the
+ * perpendicular movement. If both directions reach to the max threshold, none of them will
+ * be considered as a swipe because it is usually an indication that user rather tried to
+ * scroll then swipe.
+ * <p>
+ * The velocity is calculated in pixels per second.
+ * <p>
+ * You can customize this behavior by changing this method. If you increase the value, it
+ * will be easier for the user to swipe diagonally and if you decrease the value, user will
+ * need to make a rather straight finger movement to trigger a swipe.
+ *
+ * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper.
+ * @return The velocity cap for pointer movements. The default implementation returns the
+ * <code>defaultValue</code> parameter.
+ * @see #getSwipeEscapeVelocity(float)
+ */
+ public float getSwipeVelocityThreshold(float defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that
+ * are under the dragged View.
+ * <p>
+ * Default implementation filters the View with which dragged item have changed position
+ * in the drag direction. For instance, if the view is dragged UP, it compares the
+ * <code>view.getTop()</code> of the two views before and after drag started. If that value
+ * is different, the target view passes the filter.
+ * <p>
+ * Among these Views which pass the test, the one closest to the dragged view is chosen.
+ * <p>
+ * This method is called on the main thread every time user moves the View. If you want to
+ * override it, make sure it does not do any expensive operations.
+ *
+ * @param selected The ViewHolder being dragged by the user.
+ * @param dropTargets The list of ViewHolder that are under the dragged View and
+ * candidate as a drop.
+ * @param curX The updated left value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ * @param curY The updated top value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ * @return A ViewHolder to whose position the dragged ViewHolder should be
+ * moved to.
+ */
+ public ViewHolder chooseDropTarget(ViewHolder selected,
+ List<ViewHolder> dropTargets, int curX, int curY) {
+ int right = curX + selected.itemView.getWidth();
+ int bottom = curY + selected.itemView.getHeight();
+ ViewHolder winner = null;
+ int winnerScore = -1;
+ final int dx = curX - selected.itemView.getLeft();
+ final int dy = curY - selected.itemView.getTop();
+ final int targetsSize = dropTargets.size();
+ for (int i = 0; i < targetsSize; i++) {
+ final ViewHolder target = dropTargets.get(i);
+ if (dx > 0) {
+ int diff = target.itemView.getRight() - right;
+ if (diff < 0 && target.itemView.getRight() > selected.itemView.getRight()) {
+ final int score = Math.abs(diff);
+ if (score > winnerScore) {
+ winnerScore = score;
+ winner = target;
+ }
+ }
+ }
+ if (dx < 0) {
+ int diff = target.itemView.getLeft() - curX;
+ if (diff > 0 && target.itemView.getLeft() < selected.itemView.getLeft()) {
+ final int score = Math.abs(diff);
+ if (score > winnerScore) {
+ winnerScore = score;
+ winner = target;
+ }
+ }
+ }
+ if (dy < 0) {
+ int diff = target.itemView.getTop() - curY;
+ if (diff > 0 && target.itemView.getTop() < selected.itemView.getTop()) {
+ final int score = Math.abs(diff);
+ if (score > winnerScore) {
+ winnerScore = score;
+ winner = target;
+ }
+ }
+ }
+
+ if (dy > 0) {
+ int diff = target.itemView.getBottom() - bottom;
+ if (diff < 0 && target.itemView.getBottom() > selected.itemView.getBottom()) {
+ final int score = Math.abs(diff);
+ if (score > winnerScore) {
+ winnerScore = score;
+ winner = target;
+ }
+ }
+ }
+ }
+ return winner;
+ }
+
+ /**
+ * Called when a ViewHolder is swiped by the user.
+ * <p>
+ * If you are returning relative directions ({@link #START} , {@link #END}) from the
+ * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method
+ * will also use relative directions. Otherwise, it will use absolute directions.
+ * <p>
+ * If you don't support swiping, this method will never be called.
+ * <p>
+ * ItemTouchHelper will keep a reference to the View until it is detached from
+ * RecyclerView.
+ * As soon as it is detached, ItemTouchHelper will call
+ * {@link #clearView(RecyclerView, ViewHolder)}.
+ *
+ * @param viewHolder The ViewHolder which has been swiped by the user.
+ * @param direction The direction to which the ViewHolder is swiped. It is one of
+ * {@link #UP}, {@link #DOWN},
+ * {@link #LEFT} or {@link #RIGHT}. If your
+ * {@link #getMovementFlags(RecyclerView, ViewHolder)}
+ * method
+ * returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
+ * `direction` will be relative as well. ({@link #START} or {@link
+ * #END}).
+ */
+ public abstract void onSwiped(ViewHolder viewHolder, int direction);
+
+ /**
+ * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed.
+ * <p/>
+ * If you override this method, you should call super.
+ *
+ * @param viewHolder The new ViewHolder that is being swiped or dragged. Might be null if
+ * it is cleared.
+ * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE},
+ * {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
+ * {@link ItemTouchHelper#ACTION_STATE_DRAG}.
+ * @see #clearView(RecyclerView, RecyclerView.ViewHolder)
+ */
+ public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
+ if (viewHolder != null) {
+ sUICallback.onSelected(viewHolder.itemView);
+ }
+ }
+
+ private int getMaxDragScroll(RecyclerView recyclerView) {
+ if (mCachedMaxScrollSpeed == -1) {
+ mCachedMaxScrollSpeed = recyclerView.getResources().getDimensionPixelSize(
+ R.dimen.item_touch_helper_max_drag_scroll_per_frame);
+ }
+ return mCachedMaxScrollSpeed;
+ }
+
+ /**
+ * Called when {@link #onMove(RecyclerView, ViewHolder, ViewHolder)} returns true.
+ * <p>
+ * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it
+ * modifies the existing View. Because of this reason, it is important that the View is
+ * still part of the layout after it is moved. This may not work as intended when swapped
+ * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views
+ * which were not eligible for dropping over).
+ * <p>
+ * This method is responsible to give necessary hint to the LayoutManager so that it will
+ * keep the View in visible area. For example, for LinearLayoutManager, this is as simple
+ * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}.
+ *
+ * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's
+ * new position is likely to be out of bounds.
+ * <p>
+ * It is important to ensure the ViewHolder will stay visible as otherwise, it might be
+ * removed by the LayoutManager if the move causes the View to go out of bounds. In that
+ * case, drag will end prematurely.
+ *
+ * @param recyclerView The RecyclerView controlled by the ItemTouchHelper.
+ * @param viewHolder The ViewHolder under user's control.
+ * @param fromPos The previous adapter position of the dragged item (before it was
+ * moved).
+ * @param target The ViewHolder on which the currently active item has been dropped.
+ * @param toPos The new adapter position of the dragged item.
+ * @param x The updated left value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ * @param y The updated top value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ */
+ public void onMoved(final RecyclerView recyclerView,
+ final ViewHolder viewHolder, int fromPos, final ViewHolder target, int toPos, int x,
+ int y) {
+ final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+ if (layoutManager instanceof ViewDropHandler) {
+ ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView,
+ target.itemView, x, y);
+ return;
+ }
+
+ // if layout manager cannot handle it, do some guesswork
+ if (layoutManager.canScrollHorizontally()) {
+ final int minLeft = layoutManager.getDecoratedLeft(target.itemView);
+ if (minLeft <= recyclerView.getPaddingLeft()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ final int maxRight = layoutManager.getDecoratedRight(target.itemView);
+ if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ }
+
+ if (layoutManager.canScrollVertically()) {
+ final int minTop = layoutManager.getDecoratedTop(target.itemView);
+ if (minTop <= recyclerView.getPaddingTop()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ final int maxBottom = layoutManager.getDecoratedBottom(target.itemView);
+ if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ }
+ }
+
+ void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
+ List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
+ int actionState, float dX, float dY) {
+ final int recoverAnimSize = recoverAnimationList.size();
+ for (int i = 0; i < recoverAnimSize; i++) {
+ final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
+ anim.update();
+ final int count = c.save();
+ onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
+ false);
+ c.restoreToCount(count);
+ }
+ if (selected != null) {
+ final int count = c.save();
+ onChildDraw(c, parent, selected, dX, dY, actionState, true);
+ c.restoreToCount(count);
+ }
+ }
+
+ void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected,
+ List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
+ int actionState, float dX, float dY) {
+ final int recoverAnimSize = recoverAnimationList.size();
+ for (int i = 0; i < recoverAnimSize; i++) {
+ final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
+ final int count = c.save();
+ onChildDrawOver(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
+ false);
+ c.restoreToCount(count);
+ }
+ if (selected != null) {
+ final int count = c.save();
+ onChildDrawOver(c, parent, selected, dX, dY, actionState, true);
+ c.restoreToCount(count);
+ }
+ boolean hasRunningAnimation = false;
+ for (int i = recoverAnimSize - 1; i >= 0; i--) {
+ final RecoverAnimation anim = recoverAnimationList.get(i);
+ if (anim.mEnded && !anim.mIsPendingCleanup) {
+ recoverAnimationList.remove(i);
+ } else if (!anim.mEnded) {
+ hasRunningAnimation = true;
+ }
+ }
+ if (hasRunningAnimation) {
+ parent.invalidate();
+ }
+ }
+
+ /**
+ * Called by the ItemTouchHelper when the user interaction with an element is over and it
+ * also completed its animation.
+ * <p>
+ * This is a good place to clear all changes on the View that was done in
+ * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)},
+ * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)} or
+ * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}.
+ *
+ * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper.
+ * @param viewHolder The View that was interacted by the user.
+ */
+ public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
+ sUICallback.clearView(viewHolder.itemView);
+ }
+
+ /**
+ * Called by ItemTouchHelper on RecyclerView's onDraw callback.
+ * <p>
+ * If you would like to customize how your View's respond to user interactions, this is
+ * a good place to override.
+ * <p>
+ * Default implementation translates the child by the given <code>dX</code>,
+ * <code>dY</code>.
+ * ItemTouchHelper also takes care of drawing the child after other children if it is being
+ * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
+ * is
+ * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
+ * and after, it changes View's elevation value to be greater than all other children.)
+ *
+ * @param c The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was
+ * interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either {@link
+ * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or
+ * false it is simply animating back to its original state.
+ * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)
+ */
+ public void onChildDraw(Canvas c, RecyclerView recyclerView,
+ ViewHolder viewHolder,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
+ isCurrentlyActive);
+ }
+
+ /**
+ * Called by ItemTouchHelper on RecyclerView's onDraw callback.
+ * <p>
+ * If you would like to customize how your View's respond to user interactions, this is
+ * a good place to override.
+ * <p>
+ * Default implementation translates the child by the given <code>dX</code>,
+ * <code>dY</code>.
+ * ItemTouchHelper also takes care of drawing the child after other children if it is being
+ * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
+ * is
+ * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
+ * and after, it changes View's elevation value to be greater than all other children.)
+ *
+ * @param c The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was
+ * interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either {@link
+ * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or
+ * false it is simply animating back to its original state.
+ * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)
+ */
+ public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
+ ViewHolder viewHolder,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
+ isCurrentlyActive);
+ }
+
+ /**
+ * Called by the ItemTouchHelper when user action finished on a ViewHolder and now the View
+ * will be animated to its final position.
+ * <p>
+ * Default implementation uses ItemAnimator's duration values. If
+ * <code>animationType</code> is {@link #ANIMATION_TYPE_DRAG}, it returns
+ * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns
+ * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have
+ * any {@link RecyclerView.ItemAnimator} attached, this method returns
+ * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION}
+ * depending on the animation type.
+ *
+ * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
+ * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG},
+ * {@link #ANIMATION_TYPE_SWIPE_CANCEL} or
+ * {@link #ANIMATION_TYPE_SWIPE_SUCCESS}.
+ * @param animateDx The horizontal distance that the animation will offset
+ * @param animateDy The vertical distance that the animation will offset
+ * @return The duration for the animation
+ */
+ public long getAnimationDuration(RecyclerView recyclerView, int animationType,
+ float animateDx, float animateDy) {
+ final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
+ if (itemAnimator == null) {
+ return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
+ : DEFAULT_SWIPE_ANIMATION_DURATION;
+ } else {
+ return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration()
+ : itemAnimator.getRemoveDuration();
+ }
+ }
+
+ /**
+ * Called by the ItemTouchHelper when user is dragging a view out of bounds.
+ * <p>
+ * You can override this method to decide how much RecyclerView should scroll in response
+ * to this action. Default implementation calculates a value based on the amount of View
+ * out of bounds and the time it spent there. The longer user keeps the View out of bounds,
+ * the faster the list will scroll. Similarly, the larger portion of the View is out of
+ * bounds, the faster the RecyclerView will scroll.
+ *
+ * @param recyclerView The RecyclerView instance to which ItemTouchHelper is
+ * attached to.
+ * @param viewSize The total size of the View in scroll direction, excluding
+ * item decorations.
+ * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
+ * is negative if the View is dragged towards left or top edge.
+ * @param totalSize The total size of RecyclerView in the scroll direction.
+ * @param msSinceStartScroll The time passed since View is kept out of bounds.
+ * @return The amount that RecyclerView should scroll. Keep in mind that this value will
+ * be passed to {@link RecyclerView#scrollBy(int, int)} method.
+ */
+ public int interpolateOutOfBoundsScroll(RecyclerView recyclerView,
+ int viewSize, int viewSizeOutOfBounds,
+ int totalSize, long msSinceStartScroll) {
+ final int maxScroll = getMaxDragScroll(recyclerView);
+ final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
+ final int direction = (int) Math.signum(viewSizeOutOfBounds);
+ // might be negative if other direction
+ float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
+ final int cappedScroll = (int) (direction * maxScroll
+ * sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
+ final float timeRatio;
+ if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
+ timeRatio = 1f;
+ } else {
+ timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
+ }
+ final int value = (int) (cappedScroll * sDragScrollInterpolator
+ .getInterpolation(timeRatio));
+ if (value == 0) {
+ return viewSizeOutOfBounds > 0 ? 1 : -1;
+ }
+ return value;
+ }
+ }
+
+ /**
+ * A simple wrapper to the default Callback which you can construct with drag and swipe
+ * directions and this class will handle the flag callbacks. You should still override onMove
+ * or
+ * onSwiped depending on your use case.
+ *
+ * <pre>
+ * ItemTouchHelper mIth = new ItemTouchHelper(
+ * new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
+ * ItemTouchHelper.LEFT) {
+ * public abstract boolean onMove(RecyclerView recyclerView,
+ * ViewHolder viewHolder, ViewHolder target) {
+ * final int fromPos = viewHolder.getAdapterPosition();
+ * final int toPos = target.getAdapterPosition();
+ * // move item in `fromPos` to `toPos` in adapter.
+ * return true;// true if moved, false otherwise
+ * }
+ * public void onSwiped(ViewHolder viewHolder, int direction) {
+ * // remove from adapter
+ * }
+ * });
+ * </pre>
+ */
+ public abstract static class SimpleCallback extends Callback {
+
+ private int mDefaultSwipeDirs;
+
+ private int mDefaultDragDirs;
+
+ /**
+ * Creates a Callback for the given drag and swipe allowance. These values serve as
+ * defaults
+ * and if you want to customize behavior per ViewHolder, you can override
+ * {@link #getSwipeDirs(RecyclerView, ViewHolder)}
+ * and / or {@link #getDragDirs(RecyclerView, ViewHolder)}.
+ *
+ * @param dragDirs Binary OR of direction flags in which the Views can be dragged. Must be
+ * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
+ * #END},
+ * {@link #UP} and {@link #DOWN}.
+ * @param swipeDirs Binary OR of direction flags in which the Views can be swiped. Must be
+ * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
+ * #END},
+ * {@link #UP} and {@link #DOWN}.
+ */
+ public SimpleCallback(int dragDirs, int swipeDirs) {
+ mDefaultSwipeDirs = swipeDirs;
+ mDefaultDragDirs = dragDirs;
+ }
+
+ /**
+ * Updates the default swipe directions. For example, you can use this method to toggle
+ * certain directions depending on your use case.
+ *
+ * @param defaultSwipeDirs Binary OR of directions in which the ViewHolders can be swiped.
+ */
+ public void setDefaultSwipeDirs(int defaultSwipeDirs) {
+ mDefaultSwipeDirs = defaultSwipeDirs;
+ }
+
+ /**
+ * Updates the default drag directions. For example, you can use this method to toggle
+ * certain directions depending on your use case.
+ *
+ * @param defaultDragDirs Binary OR of directions in which the ViewHolders can be dragged.
+ */
+ public void setDefaultDragDirs(int defaultDragDirs) {
+ mDefaultDragDirs = defaultDragDirs;
+ }
+
+ /**
+ * Returns the swipe directions for the provided ViewHolder.
+ * Default implementation returns the swipe directions that was set via constructor or
+ * {@link #setDefaultSwipeDirs(int)}.
+ *
+ * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
+ * @param viewHolder The RecyclerView for which the swipe direction is queried.
+ * @return A binary OR of direction flags.
+ */
+ public int getSwipeDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
+ return mDefaultSwipeDirs;
+ }
+
+ /**
+ * Returns the drag directions for the provided ViewHolder.
+ * Default implementation returns the drag directions that was set via constructor or
+ * {@link #setDefaultDragDirs(int)}.
+ *
+ * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
+ * @param viewHolder The RecyclerView for which the swipe direction is queried.
+ * @return A binary OR of direction flags.
+ */
+ public int getDragDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
+ return mDefaultDragDirs;
+ }
+
+ @Override
+ public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
+ return makeMovementFlags(getDragDirs(recyclerView, viewHolder),
+ getSwipeDirs(recyclerView, viewHolder));
+ }
+ }
+
+ private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
+
+ ItemTouchHelperGestureListener() {
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ View child = findChildView(e);
+ if (child != null) {
+ ViewHolder vh = mRecyclerView.getChildViewHolder(child);
+ if (vh != null) {
+ if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
+ return;
+ }
+ int pointerId = e.getPointerId(0);
+ // Long press is deferred.
+ // Check w/ active pointer id to avoid selecting after motion
+ // event is canceled.
+ if (pointerId == mActivePointerId) {
+ final int index = e.findPointerIndex(mActivePointerId);
+ final float x = e.getX(index);
+ final float y = e.getY(index);
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ mDx = mDy = 0f;
+ if (DEBUG) {
+ Log.d(TAG,
+ "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
+ }
+ if (mCallback.isLongPressDragEnabled()) {
+ select(vh, ACTION_STATE_DRAG);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private class RecoverAnimation implements Animator.AnimatorListener {
+
+ final float mStartDx;
+
+ final float mStartDy;
+
+ final float mTargetX;
+
+ final float mTargetY;
+
+ final ViewHolder mViewHolder;
+
+ final int mActionState;
+
+ private final ValueAnimator mValueAnimator;
+
+ final int mAnimationType;
+
+ public boolean mIsPendingCleanup;
+
+ float mX;
+
+ float mY;
+
+ // if user starts touching a recovering view, we put it into interaction mode again,
+ // instantly.
+ boolean mOverridden = false;
+
+ boolean mEnded = false;
+
+ private float mFraction;
+
+ RecoverAnimation(ViewHolder viewHolder, int animationType,
+ int actionState, float startDx, float startDy, float targetX, float targetY) {
+ mActionState = actionState;
+ mAnimationType = animationType;
+ mViewHolder = viewHolder;
+ mStartDx = startDx;
+ mStartDy = startDy;
+ mTargetX = targetX;
+ mTargetY = targetY;
+ mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
+ mValueAnimator.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setFraction(animation.getAnimatedFraction());
+ }
+ });
+ mValueAnimator.setTarget(viewHolder.itemView);
+ mValueAnimator.addListener(this);
+ setFraction(0f);
+ }
+
+ public void setDuration(long duration) {
+ mValueAnimator.setDuration(duration);
+ }
+
+ public void start() {
+ mViewHolder.setIsRecyclable(false);
+ mValueAnimator.start();
+ }
+
+ public void cancel() {
+ mValueAnimator.cancel();
+ }
+
+ public void setFraction(float fraction) {
+ mFraction = fraction;
+ }
+
+ /**
+ * We run updates on onDraw method but use the fraction from animator callback.
+ * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
+ */
+ public void update() {
+ if (mStartDx == mTargetX) {
+ mX = mViewHolder.itemView.getTranslationX();
+ } else {
+ mX = mStartDx + mFraction * (mTargetX - mStartDx);
+ }
+ if (mStartDy == mTargetY) {
+ mY = mViewHolder.itemView.getTranslationY();
+ } else {
+ mY = mStartDy + mFraction * (mTargetY - mStartDy);
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mEnded) {
+ mViewHolder.setIsRecyclable(true);
+ }
+ mEnded = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ setFraction(1f); //make sure we recover the view's state.
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/helper/ItemTouchUIUtil.java b/core/java/com/android/internal/widget/helper/ItemTouchUIUtil.java
new file mode 100644
index 0000000..e368a6d
--- /dev/null
+++ b/core/java/com/android/internal/widget/helper/ItemTouchUIUtil.java
@@ -0,0 +1,65 @@
+/*
+ * 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.internal.widget.helper;
+
+import android.graphics.Canvas;
+import android.view.View;
+
+import com.android.internal.widget.RecyclerView;
+
+/**
+ * Utility class for {@link ItemTouchHelper} which handles item transformations for different
+ * API versions.
+ * <p/>
+ * This class has methods that map to {@link ItemTouchHelper.Callback}'s drawing methods. Default
+ * implementations in {@link ItemTouchHelper.Callback} call these methods with
+ * {@link RecyclerView.ViewHolder#itemView} and {@link ItemTouchUIUtil} makes necessary changes
+ * on the View depending on the API level. You can access the instance of {@link ItemTouchUIUtil}
+ * via {@link ItemTouchHelper.Callback#getDefaultUIUtil()} and call its methods with the children
+ * of ViewHolder that you want to apply default effects.
+ *
+ * @see ItemTouchHelper.Callback#getDefaultUIUtil()
+ */
+public interface ItemTouchUIUtil {
+
+ /**
+ * The default implementation for {@link ItemTouchHelper.Callback#onChildDraw(Canvas,
+ * RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)}
+ */
+ void onDraw(Canvas c, RecyclerView recyclerView, View view,
+ float dX, float dY, int actionState, boolean isCurrentlyActive);
+
+ /**
+ * The default implementation for {@link ItemTouchHelper.Callback#onChildDrawOver(Canvas,
+ * RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)}
+ */
+ void onDrawOver(Canvas c, RecyclerView recyclerView, View view,
+ float dX, float dY, int actionState, boolean isCurrentlyActive);
+
+ /**
+ * The default implementation for {@link ItemTouchHelper.Callback#clearView(RecyclerView,
+ * RecyclerView.ViewHolder)}
+ */
+ void clearView(View view);
+
+ /**
+ * The default implementation for {@link ItemTouchHelper.Callback#onSelectedChanged(
+ * RecyclerView.ViewHolder, int)}
+ */
+ void onSelected(View view);
+}
+
diff --git a/core/java/com/android/internal/widget/helper/ItemTouchUIUtilImpl.java b/core/java/com/android/internal/widget/helper/ItemTouchUIUtilImpl.java
new file mode 100644
index 0000000..0de240b
--- /dev/null
+++ b/core/java/com/android/internal/widget/helper/ItemTouchUIUtilImpl.java
@@ -0,0 +1,84 @@
+/*
+ * 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.internal.widget.helper;
+
+import android.graphics.Canvas;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.widget.RecyclerView;
+
+/**
+ * Package private class to keep implementations. Putting them inside ItemTouchUIUtil makes them
+ * public API, which is not desired in this case.
+ */
+class ItemTouchUIUtilImpl implements ItemTouchUIUtil {
+ @Override
+ public void onDraw(Canvas c, RecyclerView recyclerView, View view,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ if (isCurrentlyActive) {
+ Object originalElevation = view.getTag(
+ R.id.item_touch_helper_previous_elevation);
+ if (originalElevation == null) {
+ originalElevation = view.getElevation();
+ float newElevation = 1f + findMaxElevation(recyclerView, view);
+ view.setElevation(newElevation);
+ view.setTag(R.id.item_touch_helper_previous_elevation,
+ originalElevation);
+ }
+ }
+ view.setTranslationX(dX);
+ view.setTranslationY(dY);
+ }
+
+ private float findMaxElevation(RecyclerView recyclerView, View itemView) {
+ final int childCount = recyclerView.getChildCount();
+ float max = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = recyclerView.getChildAt(i);
+ if (child == itemView) {
+ continue;
+ }
+ final float elevation = child.getElevation();
+ if (elevation > max) {
+ max = elevation;
+ }
+ }
+ return max;
+ }
+
+ @Override
+ public void clearView(View view) {
+ final Object tag = view.getTag(
+ R.id.item_touch_helper_previous_elevation);
+ if (tag != null && tag instanceof Float) {
+ view.setElevation((Float) tag);
+ }
+ view.setTag(R.id.item_touch_helper_previous_elevation, null);
+ view.setTranslationX(0f);
+ view.setTranslationY(0f);
+ }
+
+ @Override
+ public void onSelected(View view) {
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView recyclerView,
+ View view, float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 0d3ccdc..327f142 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -158,6 +158,7 @@
android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
android_hardware_camera2_legacy_PerfMeasurement.cpp \
android_hardware_camera2_DngCreator.cpp \
+ android_hardware_HardwareBuffer.cpp \
android_hardware_Radio.cpp \
android_hardware_SensorManager.cpp \
android_hardware_SerialPort.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index fb5d037..340f2ee 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -87,6 +87,7 @@
extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env);
extern int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv *env);
extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
+extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
extern int register_android_hardware_Radio(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
extern int register_android_hardware_SerialPort(JNIEnv *env);
@@ -1373,6 +1374,7 @@
REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
REG_JNI(register_android_hardware_camera2_DngCreator),
+ REG_JNI(register_android_hardware_HardwareBuffer),
REG_JNI(register_android_hardware_Radio),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_hardware_SerialPort),
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 2504e54..0c863fd 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -39,27 +39,60 @@
namespace android {
-static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
- if (lang == NULL) {
- return (jlong)new minikin::FontFamily(variant);
+struct NativeFamilyBuilder {
+ uint32_t langId;
+ int variant;
+ std::vector<minikin::Font> fonts;
+};
+
+static jlong FontFamily_initBuilder(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
+ NativeFamilyBuilder* builder = new NativeFamilyBuilder();
+ if (lang != nullptr) {
+ ScopedUtfChars str(env, lang);
+ builder->langId = minikin::FontStyle::registerLanguageList(str.c_str());
+ } else {
+ builder->langId = minikin::FontStyle::registerLanguageList("");
}
- ScopedUtfChars str(env, lang);
- uint32_t langId = minikin::FontStyle::registerLanguageList(str.c_str());
- return (jlong)new minikin::FontFamily(langId, variant);
+ builder->variant = variant;
+ return reinterpret_cast<jlong>(builder);
}
-static void FontFamily_unref(JNIEnv* env, jobject clazz, jlong familyPtr) {
+static jlong FontFamily_create(jlong builderPtr) {
+ if (builderPtr == 0) {
+ return 0;
+ }
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+ minikin::FontFamily* family = new minikin::FontFamily(
+ builder->langId, builder->variant, std::move(builder->fonts));
+ delete builder;
+ return reinterpret_cast<jlong>(family);
+}
+
+static void FontFamily_abort(jlong builderPtr) {
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+ minikin::Font::clearElementsWithLock(&builder->fonts);
+ delete builder;
+}
+
+static void FontFamily_unref(jlong familyPtr) {
minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
fontFamily->Unref();
}
-static jboolean addSkTypeface(minikin::FontFamily* family, sk_sp<SkTypeface> face,
- const void* fontData, size_t fontSize, int ttcIndex) {
+static void addSkTypeface(jlong builderPtr, sk_sp<SkTypeface> face, const void* fontData,
+ size_t fontSize, int ttcIndex) {
minikin::MinikinFont* minikinFont =
new MinikinFontSkia(std::move(face), fontData, fontSize, ttcIndex);
- bool result = family->addFont(minikinFont);
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+ int weight;
+ bool italic;
+ if (!minikin::FontFamily::analyzeStyle(minikinFont, &weight, &italic)) {
+ ALOGE("analyzeStyle failed. Using default style");
+ weight = 400;
+ italic = false;
+ }
+ builder->fonts.push_back(minikin::Font(minikinFont, minikin::FontStyle(weight / 100, italic)));
minikinFont->Unref();
- return result;
}
static void release_global_ref(const void* /*data*/, void* context) {
@@ -85,7 +118,7 @@
}
}
-static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, jobject bytebuf,
+static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, jobject bytebuf,
jint ttcIndex) {
NPE_CHECK_RETURN_ZERO(env, bytebuf);
const void* fontPtr = env->GetDirectBufferAddress(bytebuf);
@@ -112,8 +145,8 @@
ALOGE("addFont failed to create font");
return false;
}
- minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
- return addSkTypeface(fontFamily, std::move(face), fontPtr, (size_t)fontSize, ttcIndex);
+ addSkTypeface(builderPtr, std::move(face), fontPtr, (size_t)fontSize, ttcIndex);
+ return true;
}
static struct {
@@ -126,7 +159,7 @@
jfieldID mStyleValue;
} gAxisClassInfo;
-static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong familyPtr,
+static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr,
jobject font, jint ttcIndex, jobject listOfAxis, jint weight, jboolean isItalic) {
NPE_CHECK_RETURN_ZERO(env, font);
@@ -178,10 +211,11 @@
ALOGE("addFont failed to create font, invalid request");
return false;
}
- minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
minikin::MinikinFont* minikinFont =
- new MinikinFontSkia(std::move(face), fontPtr, (size_t)fontSize, ttcIndex);
- fontFamily->addFont(minikinFont, minikin::FontStyle(weight / 100, isItalic));
+ new MinikinFontSkia(std::move(face), fontPtr, fontSize, ttcIndex);
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+ builder->fonts.push_back(minikin::Font(minikinFont,
+ minikin::FontStyle(weight / 100, isItalic)));
minikinFont->Unref();
return true;
}
@@ -190,7 +224,7 @@
delete static_cast<Asset*>(context);
}
-static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong familyPtr,
+static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong builderPtr,
jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset) {
NPE_CHECK_RETURN_ZERO(env, jassetMgr);
NPE_CHECK_RETURN_ZERO(env, jpath);
@@ -233,14 +267,17 @@
ALOGE("addFontFromAsset failed to create font %s", str.c_str());
return false;
}
- minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
- return addSkTypeface(fontFamily, std::move(face), buf, bufSize, /* ttcIndex */ 0);
+
+ addSkTypeface(builderPtr, std::move(face), buf, bufSize, 0 /* ttc index */);
+ return true;
}
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gFontFamilyMethods[] = {
- { "nCreateFamily", "(Ljava/lang/String;I)J", (void*)FontFamily_create },
+ { "nInitBuilder", "(Ljava/lang/String;I)J", (void*)FontFamily_initBuilder },
+ { "nCreateFamily", "(J)J", (void*)FontFamily_create },
+ { "nAbort", "(J)V", (void*)FontFamily_abort },
{ "nUnrefFamily", "(J)V", (void*)FontFamily_unref },
{ "nAddFont", "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont },
{ "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;ILjava/util/List;IZ)Z",
@@ -258,9 +295,9 @@
gListClassInfo.mGet = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;");
gListClassInfo.mSize = GetMethodIDOrDie(env, listClass, "size", "()I");
- jclass axisClass = FindClassOrDie(env, "android/graphics/FontListParser$Axis");
- gAxisClassInfo.mTag = GetFieldIDOrDie(env, axisClass, "tag", "I");
- gAxisClassInfo.mStyleValue = GetFieldIDOrDie(env, axisClass, "styleValue", "F");
+ jclass axisClass = FindClassOrDie(env, "android/text/FontConfig$Axis");
+ gAxisClassInfo.mTag = GetFieldIDOrDie(env, axisClass, "mTag", "I");
+ gAxisClassInfo.mStyleValue = GetFieldIDOrDie(env, axisClass, "mStyleValue", "F");
return err;
}
diff --git a/core/jni/android/graphics/FontUtils.cpp b/core/jni/android/graphics/FontUtils.cpp
new file mode 100644
index 0000000..91fec2a
--- /dev/null
+++ b/core/jni/android/graphics/FontUtils.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FontUtils.h"
+
+#include "JNIHelp.h"
+#include <core_jni_helpers.h>
+
+namespace android {
+namespace {
+
+static struct {
+ jmethodID mGet;
+ jmethodID mSize;
+} gListClassInfo;
+
+static struct {
+ jfieldID mTag;
+ jfieldID mStyleValue;
+} gAxisClassInfo;
+
+} // namespace
+
+jint ListHelper::size() const {
+ return mEnv->CallIntMethod(mList, gListClassInfo.mSize);
+}
+
+jobject ListHelper::get(jint index) const {
+ return mEnv->CallObjectMethod(mList, gListClassInfo.mGet, index);
+}
+
+jint AxisHelper::getTag() const {
+ return mEnv->GetIntField(mAxis, gAxisClassInfo.mTag);
+}
+
+jfloat AxisHelper::getStyleValue() const {
+ return mEnv->GetFloatField(mAxis, gAxisClassInfo.mStyleValue);
+}
+
+void init_FontUtils(JNIEnv* env) {
+ jclass listClass = FindClassOrDie(env, "java/util/List");
+ gListClassInfo.mGet = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;");
+ gListClassInfo.mSize = GetMethodIDOrDie(env, listClass, "size", "()I");
+
+ jclass axisClass = FindClassOrDie(env, "android/text/FontConfig$Axis");
+ gAxisClassInfo.mTag = GetFieldIDOrDie(env, axisClass, "mTag", "I");
+ gAxisClassInfo.mStyleValue = GetFieldIDOrDie(env, axisClass, "mStyleValue", "F");
+}
+
+} // namespace android
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 9ce5670..c49287c 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -63,97 +63,87 @@
get_canvas(canvasHandle)->setBitmap(bitmap);
}
-static jboolean isOpaque(JNIEnv*, jobject, jlong canvasHandle) {
+static jboolean isOpaque(jlong canvasHandle) {
return get_canvas(canvasHandle)->isOpaque() ? JNI_TRUE : JNI_FALSE;
}
-static jint getWidth(JNIEnv*, jobject, jlong canvasHandle) {
+static jint getWidth(jlong canvasHandle) {
return static_cast<jint>(get_canvas(canvasHandle)->width());
}
-static jint getHeight(JNIEnv*, jobject, jlong canvasHandle) {
+static jint getHeight(jlong canvasHandle) {
return static_cast<jint>(get_canvas(canvasHandle)->height());
}
-static void setHighContrastText(JNIEnv*, jobject, jlong canvasHandle, jboolean highContrastText) {
+static void setHighContrastText(jlong canvasHandle, jboolean highContrastText) {
Canvas* canvas = get_canvas(canvasHandle);
canvas->setHighContrastText(highContrastText);
}
-static jint getSaveCount(JNIEnv*, jobject, jlong canvasHandle) {
- return static_cast<jint>(get_canvas(canvasHandle)->getSaveCount());
-}
-
-static jint save(JNIEnv*, jobject, jlong canvasHandle, jint flagsHandle) {
+static jint save(jlong canvasHandle, jint flagsHandle) {
SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle);
return static_cast<jint>(get_canvas(canvasHandle)->save(flags));
}
-static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
+static jint saveLayer(jlong canvasHandle, jfloat l, jfloat t,
jfloat r, jfloat b, jlong paintHandle, jint flagsHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle);
return static_cast<jint>(get_canvas(canvasHandle)->saveLayer(l, t, r, b, paint, flags));
}
-static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
+static jint saveLayerAlpha(jlong canvasHandle, jfloat l, jfloat t,
jfloat r, jfloat b, jint alpha, jint flagsHandle) {
SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle);
return static_cast<jint>(get_canvas(canvasHandle)->saveLayerAlpha(l, t, r, b, alpha, flags));
}
-static void restore(JNIEnv* env, jobject, jlong canvasHandle, jboolean throwOnUnderflow) {
+static bool restore(jlong canvasHandle) {
Canvas* canvas = get_canvas(canvasHandle);
- if (canvas->getSaveCount() <= 1) { // cannot restore anymore
- if (throwOnUnderflow) {
- doThrowISE(env, "Underflow in restore - more restores than saves");
- }
- return; // compat behavior - return without throwing
+ if (canvas->getSaveCount() <= 1) {
+ return false; // cannot restore anymore
}
canvas->restore();
+ return true; // success
}
-static void restoreToCount(JNIEnv* env, jobject, jlong canvasHandle, jint restoreCount,
- jboolean throwOnUnderflow) {
+static void restoreToCount(jlong canvasHandle, jint saveCount) {
Canvas* canvas = get_canvas(canvasHandle);
- if (restoreCount < 1 || restoreCount > canvas->getSaveCount()) {
- if (throwOnUnderflow) {
- doThrowIAE(env, "Underflow in restoreToCount - more restores than saves");
- return;
- }
- restoreCount = 1; // compat behavior - restore as far as possible
- }
- canvas->restoreToCount(restoreCount);
+ canvas->restoreToCount(saveCount);
}
-static void getCTM(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+static jint getSaveCount(jlong canvasHandle) {
+ return static_cast<jint>(get_canvas(canvasHandle)->getSaveCount());
+}
+
+static void getMatrix(jlong canvasHandle, jlong matrixHandle) {
SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
get_canvas(canvasHandle)->getMatrix(matrix);
}
-static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+static void setMatrix(jlong canvasHandle, jlong matrixHandle) {
const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
get_canvas(canvasHandle)->setMatrix(matrix ? *matrix : SkMatrix::I());
}
-static void concat(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+static void concat(jlong canvasHandle, jlong matrixHandle) {
const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
get_canvas(canvasHandle)->concat(*matrix);
}
-static void rotate(JNIEnv*, jobject, jlong canvasHandle, jfloat degrees) {
+static void rotate(jlong canvasHandle, jfloat degrees) {
get_canvas(canvasHandle)->rotate(degrees);
}
-static void scale(JNIEnv*, jobject, jlong canvasHandle, jfloat sx, jfloat sy) {
+static void scale(jlong canvasHandle, jfloat sx, jfloat sy) {
get_canvas(canvasHandle)->scale(sx, sy);
}
-static void skew(JNIEnv*, jobject, jlong canvasHandle, jfloat sx, jfloat sy) {
+static void skew(jlong canvasHandle, jfloat sx, jfloat sy) {
get_canvas(canvasHandle)->skew(sx, sy);
}
-static void translate(JNIEnv*, jobject, jlong canvasHandle, jfloat dx, jfloat dy) {
+static void translate(jlong canvasHandle, jfloat dx, jfloat dy) {
get_canvas(canvasHandle)->translate(dx, dy);
}
@@ -171,13 +161,13 @@
return result ? JNI_TRUE : JNI_FALSE;
}
-static jboolean quickRejectRect(JNIEnv* env, jobject, jlong canvasHandle,
+static jboolean quickRejectRect(jlong canvasHandle,
jfloat left, jfloat top, jfloat right, jfloat bottom) {
bool result = get_canvas(canvasHandle)->quickRejectRect(left, top, right, bottom);
return result ? JNI_TRUE : JNI_FALSE;
}
-static jboolean quickRejectPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle) {
+static jboolean quickRejectPath(jlong canvasHandle, jlong pathHandle) {
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
bool result = get_canvas(canvasHandle)->quickRejectPath(*path);
return result ? JNI_TRUE : JNI_FALSE;
@@ -205,14 +195,14 @@
return static_cast<SkClipOp>(rgnOp);
}
-static jboolean clipRect(JNIEnv*, jobject, jlong canvasHandle, jfloat l, jfloat t,
+static jboolean clipRect(jlong canvasHandle, jfloat l, jfloat t,
jfloat r, jfloat b, jint opHandle) {
bool nonEmptyClip = get_canvas(canvasHandle)->clipRect(l, t, r, b,
opHandleToClipOp(opHandle));
return nonEmptyClip ? JNI_TRUE : JNI_FALSE;
}
-static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle,
+static jboolean clipPath(jlong canvasHandle, jlong pathHandle,
jint opHandle) {
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
bool nonEmptyClip = get_canvas(canvasHandle)->clipPath(path, opHandleToClipOp(opHandle));
@@ -565,7 +555,7 @@
env->ReleaseStringChars(text, jchars);
}
-static void setDrawFilter(JNIEnv* env, jobject, jlong canvasHandle, jlong filterHandle) {
+static void setDrawFilter(jlong canvasHandle, jlong filterHandle) {
get_canvas(canvasHandle)->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
}
@@ -587,6 +577,9 @@
// ------------ @FastNative ----------------
{"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*) CanvasJNI::setBitmap},
+ {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+
+ // ------------ @CriticalNative ----------------
{"nIsOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
{"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},
{"nGetHeight","(J)I", (void*) CanvasJNI::getHeight},
@@ -595,16 +588,15 @@
{"nSaveLayer","(JFFFFJI)I", (void*) CanvasJNI::saveLayer},
{"nSaveLayerAlpha","(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha},
{"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
- {"nRestore","(JZ)V", (void*) CanvasJNI::restore},
- {"nRestoreToCount","(JIZ)V", (void*) CanvasJNI::restoreToCount},
- {"nGetCTM", "(JJ)V", (void*)CanvasJNI::getCTM},
+ {"nRestore","(J)Z", (void*) CanvasJNI::restore},
+ {"nRestoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount},
+ {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
{"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
{"nConcat","(JJ)V", (void*) CanvasJNI::concat},
{"nRotate","(JF)V", (void*) CanvasJNI::rotate},
{"nScale","(JFF)V", (void*) CanvasJNI::scale},
{"nSkew","(JFF)V", (void*) CanvasJNI::skew},
{"nTranslate","(JFF)V", (void*) CanvasJNI::translate},
- {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
{"nQuickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
{"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
{"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
new file mode 100644
index 0000000..74527d9
--- /dev/null
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "HardwareBuffer"
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include "android_os_Parcel.h"
+#include "android/graphics/GraphicsJNI.h"
+
+#include <android/hardware_buffer.h>
+#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+
+#include <binder/Parcel.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/ISurfaceComposer.h>
+#include <ui/GraphicBuffer.h>
+
+#include <private/gui/ComposerService.h>
+
+#include "core_jni_helpers.h"
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+// Defines
+// ----------------------------------------------------------------------------
+
+// Debug
+static const bool kDebugGraphicBuffer = false;
+
+// ----------------------------------------------------------------------------
+// Types
+// ----------------------------------------------------------------------------
+
+static struct {
+ jclass clazz;
+ jfieldID mNativeObject;
+ jmethodID ctor;
+} gHardwareBufferClassInfo;
+
+class GraphicBufferWrapper {
+public:
+ explicit GraphicBufferWrapper(const sp<GraphicBuffer>& buffer)
+ : buffer(buffer) {}
+
+ sp<GraphicBuffer> buffer;
+};
+
+
+// ----------------------------------------------------------------------------
+// Helper functions
+// ----------------------------------------------------------------------------
+
+static inline bool containsBits(uint64_t mask, uint64_t bitsToCheck) {
+ return (mask & bitsToCheck) == bitsToCheck;
+}
+
+// ----------------------------------------------------------------------------
+// HardwareBuffer lifecycle
+// ----------------------------------------------------------------------------
+
+static jlong android_hardware_HardwareBuffer_create(JNIEnv* env, jobject clazz,
+ jint width, jint height, jint format, jint layers, jlong usage) {
+
+ sp<ISurfaceComposer> composer(ComposerService::getComposerService());
+ sp<IGraphicBufferAlloc> alloc(composer->createGraphicBufferAlloc());
+ if (alloc == NULL) {
+ if (kDebugGraphicBuffer) {
+ ALOGW("createGraphicBufferAlloc() failed in HardwareBuffer.create()");
+ }
+ return NULL;
+ }
+
+ // TODO: update createGraphicBuffer to take two 64-bit values.
+ int pixelFormat = android_hardware_HardwareBuffer_convertToPixelFormat(format);
+ if (pixelFormat == 0) {
+ if (kDebugGraphicBuffer) {
+ ALOGW("createGraphicBufferAlloc() invalid pixel format in HardwareBuffer.create()");
+ }
+ return NULL;
+ }
+ uint32_t grallocUsage = android_hardware_HardwareBuffer_convertToGrallocUsageBits(usage, 0);
+ status_t error;
+ sp<GraphicBuffer> buffer(alloc->createGraphicBuffer(width, height, pixelFormat,
+ layers, grallocUsage, &error));
+ if (buffer == NULL) {
+ if (kDebugGraphicBuffer) {
+ ALOGW("createGraphicBuffer() failed in HardwareBuffer.create()");
+ }
+ return NULL;
+ }
+
+ GraphicBufferWrapper* wrapper = new GraphicBufferWrapper(buffer);
+ return reinterpret_cast<jlong>(wrapper);
+}
+
+static void destroyWrapper(GraphicBufferWrapper* wrapper) {
+ delete wrapper;
+}
+
+static jlong android_hardware_HardwareBuffer_getNativeFinalizer(JNIEnv* env,
+ jobject clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyWrapper));
+}
+
+//----------------------------------------------------------------------------
+// Accessors
+// ----------------------------------------------------------------------------
+
+static inline GraphicBuffer* GraphicBufferWrapper_to_GraphicBuffer(
+ jlong nativeObject) {
+ return reinterpret_cast<GraphicBufferWrapper*>(nativeObject)->buffer.get();
+}
+
+static jint android_hardware_HardwareBuffer_getWidth(JNIEnv* env, jobject clazz,
+ jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jint>(buffer->getWidth());
+}
+
+static jint android_hardware_HardwareBuffer_getHeight(JNIEnv* env,
+ jobject clazz, jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jint>(buffer->getHeight());
+}
+
+static jint android_hardware_HardwareBuffer_getFormat(JNIEnv* env,
+ jobject clazz, jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jint>(android_hardware_HardwareBuffer_convertFromPixelFormat(
+ buffer->getPixelFormat()));
+}
+
+static jint android_hardware_HardwareBuffer_getLayers(JNIEnv* env,
+ jobject clazz, jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jint>(buffer->getLayerCount());
+}
+
+static jlong android_hardware_HardwareBuffer_getUsage(JNIEnv* env,
+ jobject clazz, jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return android_hardware_HardwareBuffer_convertFromGrallocUsageBits(
+ buffer->getUsage());
+}
+
+// ----------------------------------------------------------------------------
+// Serialization
+// ----------------------------------------------------------------------------
+
+static void android_hardware_HardwareBuffer_write(JNIEnv* env, jobject clazz,
+ jlong nativeObject, jobject dest) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ Parcel* parcel = parcelForJavaObject(env, dest);
+ if (parcel) {
+ parcel->write(*buffer);
+ }
+}
+
+static jlong android_hardware_HardwareBuffer_read(JNIEnv* env, jobject clazz,
+ jobject in) {
+ Parcel* parcel = parcelForJavaObject(env, in);
+ if (parcel) {
+ sp<GraphicBuffer> buffer = new GraphicBuffer();
+ parcel->read(*buffer);
+ return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+ }
+
+ return NULL;
+}
+
+// ----------------------------------------------------------------------------
+// Public functions
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
+ JNIEnv* env, jobject hardwareBufferObj) {
+ if (env->IsInstanceOf(hardwareBufferObj, gHardwareBufferClassInfo.clazz)) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(
+ env->GetLongField(hardwareBufferObj, gHardwareBufferClassInfo.mNativeObject));
+ return reinterpret_cast<AHardwareBuffer*>(buffer);
+ } else {
+ return nullptr;
+ }
+}
+
+jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
+ JNIEnv* env, AHardwareBuffer* hardwareBuffer) {
+ GraphicBuffer* buffer = reinterpret_cast<GraphicBuffer*>(hardwareBuffer);
+ GraphicBufferWrapper* wrapper = new GraphicBufferWrapper(buffer);
+ jobject hardwareBufferObj = env->NewObject(gHardwareBufferClassInfo.clazz,
+ gHardwareBufferClassInfo.ctor, reinterpret_cast<jlong>(wrapper));
+ if (hardwareBufferObj == NULL) {
+ delete wrapper;
+ if (env->ExceptionCheck()) {
+ ALOGE("Could not create instance of HardwareBuffer from AHardwareBuffer.");
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+ return nullptr;
+ }
+ return hardwareBufferObj;
+}
+
+uint32_t android_hardware_HardwareBuffer_convertFromPixelFormat(uint32_t format) {
+ switch (format) {
+ case PIXEL_FORMAT_RGBA_8888:
+ return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+ case PIXEL_FORMAT_RGBX_8888:
+ return AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM;
+ case PIXEL_FORMAT_RGB_565:
+ return AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM;
+ case PIXEL_FORMAT_RGB_888:
+ return AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
+ case PIXEL_FORMAT_RGBA_FP16:
+ return AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT;
+ default:
+ ALOGE("Unknown pixel format %u", format);
+ return 0;
+ }
+}
+
+uint32_t android_hardware_HardwareBuffer_convertToPixelFormat(uint32_t format) {
+ switch (format) {
+ case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
+ return PIXEL_FORMAT_RGBA_8888;
+ case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
+ return PIXEL_FORMAT_RGBX_8888;
+ case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
+ return PIXEL_FORMAT_RGB_565;
+ case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
+ return PIXEL_FORMAT_RGB_888;
+ case AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT:
+ return PIXEL_FORMAT_RGBA_FP16;
+ default:
+ ALOGE("Unknown AHardwareBuffer format %u", format);
+ return 0;
+ }
+}
+
+uint32_t android_hardware_HardwareBuffer_convertToGrallocUsageBits(uint64_t usage0,
+ uint64_t usage1) {
+ uint32_t bits = 0;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_CPU_READ))
+ bits |= GRALLOC_USAGE_SW_READ_RARELY;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_CPU_READ_OFTEN))
+ bits |= GRALLOC_USAGE_SW_READ_OFTEN;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_CPU_WRITE))
+ bits |= GRALLOC_USAGE_SW_WRITE_RARELY;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_CPU_WRITE_OFTEN))
+ bits |= GRALLOC_USAGE_SW_WRITE_OFTEN;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_GPU_SAMPLED_IMAGE))
+ bits |= GRALLOC_USAGE_HW_TEXTURE;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_GPU_COLOR_OUTPUT))
+ bits |= GRALLOC_USAGE_HW_RENDER;
+ // Not sure what this should be.
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_GPU_CUBEMAP)) bits |= 0;
+ //if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_GPU_DATA_BUFFER) bits |= 0;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_VIDEO_ENCODE))
+ bits |= GRALLOC_USAGE_HW_VIDEO_ENCODER;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_PROTECTED_CONTENT))
+ bits |= GRALLOC_USAGE_PROTECTED;
+
+ (void)usage1;
+
+ return bits;
+}
+
+uint64_t android_hardware_HardwareBuffer_convertFromGrallocUsageBits(uint64_t usage0) {
+ uint64_t bits = 0;
+ if (containsBits(usage0, GRALLOC_USAGE_SW_READ_RARELY))
+ bits |= AHARDWAREBUFFER_USAGE0_CPU_READ;
+ if (containsBits(usage0, GRALLOC_USAGE_SW_READ_OFTEN))
+ bits |= AHARDWAREBUFFER_USAGE0_CPU_READ_OFTEN;
+ if (containsBits(usage0, GRALLOC_USAGE_SW_WRITE_RARELY))
+ bits |= AHARDWAREBUFFER_USAGE0_CPU_WRITE;
+ if (containsBits(usage0, GRALLOC_USAGE_SW_WRITE_OFTEN))
+ bits |= AHARDWAREBUFFER_USAGE0_CPU_WRITE_OFTEN;
+ if (containsBits(usage0, GRALLOC_USAGE_HW_TEXTURE))
+ bits |= AHARDWAREBUFFER_USAGE0_GPU_SAMPLED_IMAGE;
+ if (containsBits(usage0, GRALLOC_USAGE_HW_RENDER))
+ bits |= AHARDWAREBUFFER_USAGE0_GPU_COLOR_OUTPUT;
+ if (containsBits(usage0, GRALLOC_USAGE_HW_VIDEO_ENCODER))
+ bits |= AHARDWAREBUFFER_USAGE0_VIDEO_ENCODE;
+ if (containsBits(usage0, GRALLOC_USAGE_PROTECTED))
+ bits |= AHARDWAREBUFFER_USAGE0_PROTECTED_CONTENT;
+
+ return bits;
+}
+
+} // namespace android
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/hardware/HardwareBuffer";
+
+static const JNINativeMethod gMethods[] = {
+ { "nCreateHardwareBuffer", "(IIIIJ)J", (void*) android_hardware_HardwareBuffer_create },
+ { "nGetNativeFinalizer", "()J", (void*) android_hardware_HardwareBuffer_getNativeFinalizer },
+ { "nWriteHardwareBufferToParcel", "(JLandroid/os/Parcel;)V",
+ (void*) android_hardware_HardwareBuffer_write },
+ { "nReadHardwareBufferFromParcel", "(Landroid/os/Parcel;)J",
+ (void*) android_hardware_HardwareBuffer_read },
+
+ // --------------- @FastNative ----------------------
+ { "nGetWidth", "(J)I", (void*) android_hardware_HardwareBuffer_getWidth },
+ { "nGetHeight", "(J)I", (void*) android_hardware_HardwareBuffer_getHeight },
+ { "nGetFormat", "(J)I", (void*) android_hardware_HardwareBuffer_getFormat },
+ { "nGetLayers", "(J)I", (void*) android_hardware_HardwareBuffer_getLayers },
+ { "nGetUsage", "(J)J", (void*) android_hardware_HardwareBuffer_getUsage },
+};
+
+int register_android_hardware_HardwareBuffer(JNIEnv* env) {
+ int err = RegisterMethodsOrDie(env, kClassPathName, gMethods,
+ NELEM(gMethods));
+
+ jclass clazz = FindClassOrDie(env, "android/hardware/HardwareBuffer");
+ gHardwareBufferClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gHardwareBufferClassInfo.mNativeObject = GetFieldIDOrDie(env,
+ gHardwareBufferClassInfo.clazz, "mNativeObject", "J");
+ gHardwareBufferClassInfo.ctor = GetMethodIDOrDie(env,
+ gHardwareBufferClassInfo.clazz, "<init>", "(J)V");
+
+ return err;
+}
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 271f24b..bc2fc1c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -26,6 +26,7 @@
#include <gui/Sensor.h>
#include <gui/SensorEventQueue.h>
#include <gui/SensorManager.h>
+#include <cutils/native_handle.h>
#include <utils/Log.h>
#include <utils/Looper.h>
#include <utils/Vector.h>
@@ -243,6 +244,54 @@
return mgr->isDataInjectionEnabled();
}
+static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
+ jlong size, jint channelType, jlongArray channelData) {
+ jint ret = -1;
+ jsize len = _env->GetArrayLength(channelData);
+ if (len > 2) {
+ jlong *data = _env->GetLongArrayElements(channelData, NULL);
+ if (data != nullptr) {
+ // construct native handle from jlong*
+ jlong numFd = data[0];
+ jlong numInt = data[1];
+ if ((numFd + numInt + 2) == len) {
+ native_handle_t *nativeHandle = native_handle_create(numFd, numInt);
+ if (nativeHandle != nullptr) {
+ const jlong *readPointer = data + 2;
+ int *writePointer = nativeHandle->data;
+ size_t n = static_cast<size_t>(numFd + numInt);
+ while (n--) {
+ // native type of data is int, jlong is just to ensure Java does not
+ // truncate data on 64-bit system. The cast here is safe.
+ *writePointer++ = static_cast<int>(*readPointer++);
+ }
+
+ SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+ ret = mgr->createDirectChannel(size, channelType, nativeHandle);
+
+ // do not native_handle_close() here as handle is owned by java
+ native_handle_delete(nativeHandle);
+ }
+ }
+ // unidirectional parameter passing, thus JNI_ABORT
+ _env->ReleaseLongArrayElements(channelData, data, JNI_ABORT);
+ }
+ }
+ return ret;
+}
+
+static void nativeDestroyDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
+ jint channelHandle) {
+ SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+ mgr->destroyDirectChannel(channelHandle);
+}
+
+static jint nativeConfigDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
+ jint channelHandle, jint sensorHandle, jint rate) {
+ SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+ return mgr->configureDirectChannel(channelHandle, sensorHandle, rate);
+}
+
//----------------------------------------------------------------------------
class Receiver : public LooperCallback {
@@ -447,7 +496,19 @@
{"nativeIsDataInjectionEnabled",
"(J)Z",
- (void*)nativeIsDataInjectionEnabled},
+ (void*)nativeIsDataInjectionEnabled },
+
+ {"nativeCreateDirectChannel",
+ "(JJI[J)I",
+ (void*)nativeCreateDirectChannel },
+
+ {"nativeDestroyDirectChannel",
+ "(JI)V",
+ (void*)nativeDestroyDirectChannel },
+
+ {"nativeConfigDirectChannel",
+ "(JIII)I",
+ (void*)nativeConfigDirectChannel },
};
static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp
index 0b4fbcc..8a7600b 100644
--- a/core/jni/android_util_EventLog.cpp
+++ b/core/jni/android_util_EventLog.cpp
@@ -144,26 +144,22 @@
return ctx.write();
}
-/*
- * In class android.util.EventLog:
- * static native void readEvents(int[] tags, Collection<Event> output)
- *
- * Reads events from the event log
- */
-static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz UNUSED,
- jintArray tags,
- jobject out) {
-
- if (tags == NULL || out == NULL) {
- jniThrowNullPointerException(env, NULL);
+static void readEvents(JNIEnv* env, int loggerMode, jintArray tags, jlong startTime, jobject out) {
+ struct logger_list *logger_list;
+ if (startTime) {
+ logger_list = android_logger_list_alloc_time(loggerMode,
+ log_time(startTime / NS_PER_SEC, startTime % NS_PER_SEC), 0);
+ } else {
+ logger_list = android_logger_list_alloc(loggerMode, 0, 0);
+ }
+ if (!logger_list) {
+ jniThrowIOException(env, errno);
return;
}
- struct logger_list *logger_list = android_logger_list_open(
- LOG_ID_EVENTS, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, 0);
-
- if (!logger_list) {
+ if (!android_logger_open(logger_list, LOG_ID_EVENTS)) {
jniThrowIOException(env, errno);
+ android_logger_list_free(logger_list);
return;
}
@@ -228,6 +224,41 @@
}
/*
+ * In class android.util.EventLog:
+ * static native void readEvents(int[] tags, Collection<Event> output)
+ *
+ * Reads events from the event log
+ */
+static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz UNUSED,
+ jintArray tags,
+ jobject out) {
+
+ if (tags == NULL || out == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+
+ readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, tags, 0, out);
+ }
+/*
+ * In class android.util.EventLog:
+ * static native void readEventsOnWrapping(int[] tags, long timestamp, Collection<Event> output)
+ *
+ * Reads events from the event log, blocking until events after timestamp are to be overwritten.
+ */
+static void android_util_EventLog_readEventsOnWrapping(JNIEnv* env, jobject clazz UNUSED,
+ jintArray tags,
+ jlong timestamp,
+ jobject out) {
+ if (tags == NULL || out == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+ readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK | ANDROID_LOG_WRAP,
+ tags, timestamp, out);
+}
+
+/*
* JNI registration.
*/
static const JNINativeMethod gRegisterMethods[] = {
@@ -247,6 +278,10 @@
"([ILjava/util/Collection;)V",
(void*) android_util_EventLog_readEvents
},
+ { "readEventsOnWrapping",
+ "([IJLjava/util/Collection;)V",
+ (void*) android_util_EventLog_readEventsOnWrapping
+ },
};
static struct { const char *name; jclass *clazz; } gClasses[] = {
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 3eccc42..d75d5c1 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -88,7 +88,6 @@
return;
}
- node->setStagingDisplayList(nullptr, nullptr);
// Update the valid field, since native has already removed
// the staging DisplayList
env->SetBooleanField(jnode, gRenderNode_validFieldID, false);
diff --git a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
new file mode 100644
index 0000000..60e065c
--- /dev/null
+++ b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_HARDWARE_HARDWAREBUFFER_H
+#define _ANDROID_HARDWARE_HARDWAREBUFFER_H
+
+#include <android/hardware_buffer.h>
+
+#include "jni.h"
+
+namespace android {
+
+/* Gets the underlying AHardwareBuffer for a HardwareBuffer. */
+extern AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
+ JNIEnv* env, jobject hardwareBufferObj);
+
+/* Returns a HardwareBuffer wrapper for the underlying AHardwareBuffer. */
+extern jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
+ JNIEnv* env, AHardwareBuffer* hardwareBuffer);
+
+/* Convert from HAL_PIXEL_FORMAT values to AHARDWAREBUFFER_FORMAT values. */
+extern uint32_t android_hardware_HardwareBuffer_convertFromPixelFormat(
+ uint32_t format);
+
+/* Convert from AHARDWAREBUFFER_FORMAT values to HAL_PIXEL_FORMAT values. */
+extern uint32_t android_hardware_HardwareBuffer_convertToPixelFormat(
+ uint32_t format);
+
+/* Convert from AHARDWAREBUFFER_USAGE* flags to to gralloc usage flags. */
+extern uint32_t android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+ uint64_t usage0, uint64_t usage1);
+
+/* Convert from gralloc usage flags to to AHARDWAREBUFFER_USAGE0* flags. */
+extern uint64_t android_hardware_HardwareBuffer_convertFromGrallocUsageBits(
+ uint64_t usage0);
+
+} // namespace android
+
+#endif // _ANDROID_HARDWARE_HARDWAREBUFFER_H
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index c3b0ff1..ac9ebe0 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -22,6 +22,7 @@
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/service/fingerprint.proto";
import "frameworks/base/core/proto/android/service/netstats.proto";
+import "frameworks/base/core/proto/android/providers/settings.proto";
package android.os;
@@ -51,4 +52,5 @@
// System Services
android.service.fingerprint.FingerprintServiceDumpProto fingerprint = 3000;
android.service.NetworkStatsServiceDumpProto netstats = 3001;
+ android.providers.settings.SettingsServiceDumpProto settings = 3002;
}
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
new file mode 100644
index 0000000..7674d7a
--- /dev/null
+++ b/core/proto/android/providers/settings.proto
@@ -0,0 +1,605 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package android.providers.settings;
+
+option java_multiple_files = true;
+option java_outer_classname = "SettingsServiceProto";
+
+message SettingsServiceDumpProto {
+ // Per user settings
+ repeated UserSettingsProto user_settings = 1;
+
+ // Global settings
+ GlobalSettingsProto global_settings = 2;
+}
+
+message UserSettingsProto {
+ // Should be 0, 10, 11, 12, etc. where 0 is the owner.
+ int32 user_id = 1;
+
+ // The secure settings for this user
+ SecureSettingsProto secure_settings = 2;
+
+ // The system settings for this user
+ SystemSettingsProto system_settings = 3;
+}
+
+message GlobalSettingsProto {
+ // Historical operations
+ repeated SettingsOperationProto historical_op = 1;
+
+ SettingProto add_users_when_locked = 2;
+ SettingProto enable_accessibility_global_gesture_enabled = 3;
+ SettingProto airplane_mode_on = 4;
+ SettingProto theater_mode_on = 5;
+ SettingProto radio_bluetooth = 6;
+ SettingProto radio_wifi = 7;
+ SettingProto radio_wimax = 8;
+ SettingProto radio_cell = 9;
+ SettingProto radio_nfc = 10;
+ SettingProto airplane_mode_radios = 11;
+ SettingProto airplane_mode_toggleable_radios = 12;
+ SettingProto bluetooth_disabled_profiles = 13;
+ SettingProto bluetooth_interoperability_list = 14;
+ SettingProto wifi_sleep_policy = 15;
+ SettingProto auto_time = 16;
+ SettingProto auto_time_zone = 17;
+ SettingProto car_dock_sound = 18;
+ SettingProto car_undock_sound = 19;
+ SettingProto desk_dock_sound = 20;
+ SettingProto desk_undock_sound = 21;
+ SettingProto dock_sounds_enabled = 22;
+ SettingProto dock_sounds_enabled_when_accessibility = 23;
+ SettingProto lock_sound = 24;
+ SettingProto unlock_sound = 25;
+ SettingProto trusted_sound = 26;
+ SettingProto low_battery_sound = 27;
+ SettingProto power_sounds_enabled = 28;
+ SettingProto wireless_charging_started_sound = 29;
+ SettingProto charging_sounds_enabled = 30;
+ SettingProto stay_on_while_plugged_in = 31;
+ SettingProto bugreport_in_power_menu = 32;
+ SettingProto adb_enabled = 33;
+ SettingProto debug_view_attributes = 34;
+ SettingProto assisted_gps_enabled = 35;
+ SettingProto bluetooth_on = 36;
+ SettingProto cdma_cell_broadcast_sms = 37;
+ SettingProto cdma_roaming_mode = 38;
+ SettingProto cdma_subscription_mode = 39;
+ SettingProto data_activity_timeout_mobile = 40;
+ SettingProto data_activity_timeout_wifi = 41;
+ SettingProto data_roaming = 42;
+ SettingProto mdc_initial_max_retry = 43;
+ SettingProto force_allow_on_external = 44;
+ SettingProto development_force_resizable_activities = 45;
+ SettingProto development_enable_freeform_windows_support = 46;
+ SettingProto development_settings_enabled = 47;
+ SettingProto device_provisioned = 48;
+ SettingProto device_provisioning_mobile_data_enabled = 49;
+ SettingProto display_size_forced = 50;
+ SettingProto display_scaling_force = 51;
+ SettingProto download_max_bytes_over_mobile = 52;
+ SettingProto download_recommended_max_bytes_over_mobile = 53;
+ SettingProto hdmi_control_enabled = 54;
+ SettingProto hdmi_system_audio_enabled = 55;
+ SettingProto hdmi_control_auto_wakeup_enabled = 56;
+ SettingProto hdmi_control_auto_device_off_enabled = 57;
+ SettingProto mhl_input_switching_enabled = 58;
+ SettingProto mhl_power_charge_enabled = 59;
+ SettingProto mobile_data = 60;
+ SettingProto mobile_data_always_on = 61;
+ SettingProto connectivity_metrics_buffer_size = 62;
+ SettingProto netstats_enabled = 63;
+ SettingProto netstats_poll_interval = 64;
+ SettingProto netstats_time_cache_max_age = 65;
+ SettingProto netstats_global_alert_bytes = 66;
+ SettingProto netstats_sample_enabled = 67;
+ SettingProto netstats_dev_bucket_duration = 68;
+ SettingProto netstats_dev_persist_bytes = 69;
+ SettingProto netstats_dev_rotate_age = 70;
+ SettingProto netstats_dev_delete_age = 71;
+ SettingProto netstats_uid_bucket_duration = 72;
+ SettingProto netstats_uid_persist_bytes = 73;
+ SettingProto netstats_uid_rotate_age = 74;
+ SettingProto netstats_uid_delete_age = 75;
+ SettingProto netstats_uid_tag_bucket_duration = 76;
+ SettingProto netstats_uid_tag_persist_bytes = 77;
+ SettingProto netstats_uid_tag_rotate_age = 78;
+ SettingProto netstats_uid_tag_delete_age = 79;
+ SettingProto network_preference = 80;
+ SettingProto network_scorer_app = 81;
+ SettingProto nitz_update_diff = 82;
+ SettingProto nitz_update_spacing = 83;
+ SettingProto ntp_server = 84;
+ SettingProto ntp_timeout = 85;
+ SettingProto storage_benchmark_interval = 86;
+ SettingProto dns_resolver_sample_validity_seconds = 87;
+ SettingProto dns_resolver_success_threshold_percent = 88;
+ SettingProto dns_resolver_min_samples = 89;
+ SettingProto dns_resolver_max_samples = 90;
+ SettingProto ota_disable_automatic_update = 91;
+ SettingProto package_verifier_enable = 92;
+ SettingProto package_verifier_timeout = 93;
+ SettingProto package_verifier_default_response = 94;
+ SettingProto package_verifier_setting_visible = 95;
+ SettingProto package_verifier_include_adb = 96;
+ SettingProto fstrim_mandatory_interval = 97;
+ SettingProto pdp_watchdog_poll_interval_ms = 98;
+ SettingProto pdp_watchdog_long_poll_interval_ms = 99;
+ SettingProto pdp_watchdog_error_poll_interval_ms = 100;
+ SettingProto pdp_watchdog_trigger_packet_count = 101;
+ SettingProto pdp_watchdog_error_poll_count = 102;
+ SettingProto pdp_watchdog_max_pdp_reset_fail_count = 103;
+ SettingProto sampling_profiler_ms = 104;
+ SettingProto setup_prepaid_data_service_url = 105;
+ SettingProto setup_prepaid_detection_target_url = 106;
+ SettingProto setup_prepaid_detection_redir_host = 107;
+ SettingProto sms_outgoing_check_interval_ms = 108;
+ SettingProto sms_outgoing_check_max_count = 109;
+ SettingProto sms_short_code_confirmation = 110;
+ SettingProto sms_short_code_rule = 111;
+ SettingProto tcp_default_init_rwnd = 112;
+ SettingProto tether_supported = 113;
+ SettingProto tether_dun_required = 114;
+ SettingProto tether_dun_apn = 115;
+ SettingProto carrier_app_whitelist = 116;
+ SettingProto usb_mass_storage_enabled = 117;
+ SettingProto use_google_mail = 118;
+ SettingProto webview_data_reduction_proxy_key = 119;
+ SettingProto webview_fallback_logic_enabled = 120;
+ SettingProto webview_provider = 121;
+ SettingProto webview_multiprocess = 122;
+ SettingProto network_switch_notification_daily_limit = 123;
+ SettingProto network_switch_notification_rate_limit_millis = 124;
+ SettingProto network_avoid_bad_wifi = 125;
+ SettingProto wifi_display_on = 126;
+ SettingProto wifi_display_certification_on = 127;
+ SettingProto wifi_display_wps_config = 128;
+ SettingProto wifi_networks_available_notification_on = 129;
+ SettingProto wimax_networks_available_notification_on = 130;
+ SettingProto wifi_networks_available_repeat_delay = 131;
+ SettingProto wifi_country_code = 132;
+ SettingProto wifi_framework_scan_interval_ms = 133;
+ SettingProto wifi_idle_ms = 134;
+ SettingProto wifi_num_open_networks_kept = 135;
+ SettingProto wifi_on = 136;
+ SettingProto wifi_scan_always_available = 137;
+ SettingProto wifi_wakeup_enabled = 138;
+ SettingProto network_recommendations_enabled = 139;
+ SettingProto ble_scan_always_available = 140;
+ SettingProto wifi_saved_state = 141;
+ SettingProto wifi_supplicant_scan_interval_ms = 142;
+ SettingProto wifi_enhanced_auto_join = 143;
+ SettingProto wifi_network_show_rssi = 144;
+ SettingProto wifi_scan_interval_when_p2p_connected_ms = 145;
+ SettingProto wifi_watchdog_on = 146;
+ SettingProto wifi_watchdog_poor_network_test_enabled = 147;
+ SettingProto wifi_suspend_optimizations_enabled = 148;
+ SettingProto wifi_verbose_logging_enabled = 149;
+ SettingProto wifi_max_dhcp_retry_count = 150;
+ SettingProto wifi_mobile_data_transition_wakelock_timeout_ms = 151;
+ SettingProto wifi_device_owner_configs_lockdown = 152;
+ SettingProto wifi_frequency_band = 153;
+ SettingProto wifi_p2p_device_name = 154;
+ SettingProto wifi_reenable_delay_ms = 155;
+ SettingProto wifi_ephemeral_out_of_range_timeout_ms = 156;
+ SettingProto data_stall_alarm_non_aggressive_delay_in_ms = 157;
+ SettingProto data_stall_alarm_aggressive_delay_in_ms = 158;
+ SettingProto provisioning_apn_alarm_delay_in_ms = 159;
+ SettingProto gprs_register_check_period_ms = 160;
+ SettingProto wtf_is_fatal = 161;
+ SettingProto mode_ringer = 162;
+ SettingProto overlay_display_devices = 163;
+ SettingProto battery_discharge_duration_threshold = 164;
+ SettingProto battery_discharge_threshold = 165;
+ SettingProto send_action_app_error = 166;
+ SettingProto dropbox_age_seconds = 167;
+ SettingProto dropbox_max_files = 168;
+ SettingProto dropbox_quota_kb = 169;
+ SettingProto dropbox_quota_percent = 170;
+ SettingProto dropbox_reserve_percent = 171;
+ SettingProto dropbox_tag_prefix = 172;
+ SettingProto error_logcat_prefix = 173;
+ SettingProto sys_free_storage_log_interval = 174;
+ SettingProto disk_free_change_reporting_threshold = 175;
+ SettingProto sys_storage_threshold_percentage = 176;
+ SettingProto sys_storage_threshold_max_bytes = 177;
+ SettingProto sys_storage_full_threshold_bytes = 178;
+ SettingProto sync_max_retry_delay_in_seconds = 179;
+ SettingProto connectivity_change_delay = 180;
+ SettingProto connectivity_sampling_interval_in_seconds = 181;
+ SettingProto pac_change_delay = 182;
+ SettingProto captive_portal_mode = 183;
+ SettingProto captive_portal_server = 184;
+ SettingProto captive_portal_https_url = 185;
+ SettingProto captive_portal_http_url = 186;
+ SettingProto captive_portal_fallback_url = 187;
+ SettingProto captive_portal_use_https = 188;
+ SettingProto captive_portal_user_agent = 189;
+ SettingProto nsd_on = 190;
+ SettingProto set_install_location = 191;
+ SettingProto default_install_location = 192;
+ SettingProto inet_condition_debounce_up_delay = 193;
+ SettingProto inet_condition_debounce_down_delay = 194;
+ SettingProto read_external_storage_enforced_default = 195;
+ SettingProto http_proxy = 196;
+ SettingProto global_http_proxy_host = 197;
+ SettingProto global_http_proxy_port = 198;
+ SettingProto global_http_proxy_exclusion_list = 199;
+ SettingProto global_http_proxy_pac = 200;
+ SettingProto set_global_http_proxy = 201;
+ SettingProto default_dns_server = 202;
+ SettingProto bluetooth_headset_priority_prefix = 203;
+ SettingProto bluetooth_a2dp_sink_priority_prefix = 204;
+ SettingProto bluetooth_a2dp_src_priority_prefix = 205;
+ SettingProto bluetooth_input_device_priority_prefix = 206;
+ SettingProto bluetooth_map_priority_prefix = 207;
+ SettingProto bluetooth_map_client_priority_prefix = 208;
+ SettingProto bluetooth_pbap_client_priority_prefix = 209;
+ SettingProto bluetooth_sap_priority_prefix = 210;
+ SettingProto bluetooth_pan_priority_prefix = 211;
+ SettingProto device_idle_constants = 212;
+ SettingProto device_idle_constants_watch = 213;
+ SettingProto app_idle_constants = 214;
+ SettingProto alarm_manager_constants = 215;
+ SettingProto job_scheduler_constants = 216;
+ SettingProto shortcut_manager_constants = 217;
+ SettingProto window_animation_scale = 218;
+ SettingProto transition_animation_scale = 219;
+ SettingProto animator_duration_scale = 220;
+ SettingProto fancy_ime_animations = 221;
+ SettingProto compatibility_mode = 222;
+ SettingProto emergency_tone = 223;
+ SettingProto call_auto_retry = 224;
+ SettingProto emergency_affordance_needed = 225;
+ SettingProto preferred_network_mode = 226;
+ SettingProto debug_app = 227;
+ SettingProto wait_for_debugger = 228;
+ SettingProto low_power_mode = 229;
+ SettingProto low_power_mode_trigger_level = 230;
+ SettingProto always_finish_activities = 231;
+ SettingProto dock_audio_media_enabled = 232;
+ SettingProto encoded_surround_output = 233;
+ SettingProto audio_safe_volume_state = 234;
+ SettingProto tzinfo_update_content_url = 235;
+ SettingProto tzinfo_update_metadata_url = 236;
+ SettingProto selinux_update_content_url = 237;
+ SettingProto selinux_update_metadata_url = 238;
+ SettingProto sms_short_codes_update_content_url = 239;
+ SettingProto sms_short_codes_update_metadata_url = 240;
+ SettingProto apn_db_update_content_url = 241;
+ SettingProto apn_db_update_metadata_url = 242;
+ SettingProto cert_pin_update_content_url = 243;
+ SettingProto cert_pin_update_metadata_url = 244;
+ SettingProto intent_firewall_update_content_url = 245;
+ SettingProto intent_firewall_update_metadata_url = 246;
+ SettingProto selinux_status = 247;
+ SettingProto development_force_rtl = 248;
+ SettingProto low_battery_sound_timeout = 249;
+ SettingProto wifi_bounce_delay_override_ms = 250;
+ SettingProto policy_control = 251;
+ SettingProto zen_mode = 252;
+ SettingProto zen_mode_ringer_level = 253;
+ SettingProto zen_mode_config_etag = 254;
+ SettingProto heads_up_notifications_enabled = 255;
+ SettingProto device_name = 256;
+ SettingProto network_scoring_provisioned = 257;
+ SettingProto require_password_to_decrypt = 258;
+ SettingProto enhanced_4g_mode_enabled = 259;
+ SettingProto vt_ims_enabled = 260;
+ SettingProto wfc_ims_enabled = 261;
+ SettingProto wfc_ims_mode = 262;
+ SettingProto wfc_ims_roaming_mode = 263;
+ SettingProto wfc_ims_roaming_enabled = 264;
+ SettingProto lte_service_forced = 265;
+ SettingProto ephemeral_cookie_max_size_bytes = 266;
+ SettingProto enable_ephemeral_feature = 267;
+ SettingProto uninstalled_ephemeral_app_cache_duration_millis = 268;
+ SettingProto allow_user_switching_when_system_user_locked = 269;
+ SettingProto boot_count = 270;
+ SettingProto safe_boot_disallowed = 271;
+ SettingProto device_demo_mode = 272;
+ SettingProto retail_demo_mode_constants = 273;
+ SettingProto database_downgrade_reason = 274;
+ SettingProto contacts_database_wal_enabled = 275;
+ SettingProto multi_sim_voice_call_subscription = 276;
+ SettingProto multi_sim_voice_prompt = 277;
+ SettingProto multi_sim_data_call_subscription = 278;
+ SettingProto multi_sim_sms_subscription = 279;
+ SettingProto multi_sim_sms_prompt = 280;
+ SettingProto new_contact_aggregator = 281;
+ SettingProto contact_metadata_sync_enabled = 282;
+ SettingProto enable_cellular_on_boot = 283;
+ SettingProto max_notification_enqueue_rate = 284;
+ SettingProto cell_on = 285;
+}
+
+message SecureSettingsProto {
+ // Historical operations
+ repeated SettingsOperationProto historical_op = 1;
+
+ SettingProto android_id = 2;
+ SettingProto default_input_method = 3;
+ SettingProto selected_input_method_subtype = 4;
+ SettingProto input_methods_subtype_history = 5;
+ SettingProto input_method_selector_visibility = 6;
+ SettingProto voice_interaction_service = 7;
+ SettingProto auto_fill_service = 8;
+ SettingProto bluetooth_hci_log = 9;
+ SettingProto user_setup_complete = 10;
+ SettingProto completed_category_prefix = 11;
+ SettingProto enabled_input_methods = 12;
+ SettingProto disabled_system_input_methods = 13;
+ SettingProto show_ime_with_hard_keyboard = 14;
+ SettingProto always_on_vpn_app = 15;
+ SettingProto always_on_vpn_lockdown = 16;
+ SettingProto install_non_market_apps = 17;
+ SettingProto location_mode = 18;
+ SettingProto location_previous_mode = 19;
+ SettingProto lock_to_app_exit_locked = 20;
+ SettingProto lock_screen_lock_after_timeout = 21;
+ SettingProto lock_screen_allow_remote_input = 22;
+ SettingProto show_note_about_notification_hiding = 23;
+ SettingProto trust_agents_initialized = 24;
+ SettingProto parental_control_enabled = 25;
+ SettingProto parental_control_last_update = 26;
+ SettingProto parental_control_redirect_url = 27;
+ SettingProto settings_classname = 28;
+ SettingProto accessibility_enabled = 29;
+ SettingProto touch_exploration_enabled = 30;
+ SettingProto enabled_accessibility_services = 31;
+ SettingProto touch_exploration_granted_accessibility_services = 32;
+ SettingProto accessibility_speak_password = 33;
+ SettingProto accessibility_high_text_contrast_enabled = 34;
+ SettingProto accessibility_script_injection = 35;
+ SettingProto accessibility_screen_reader_url = 36;
+ SettingProto accessibility_web_content_key_bindings = 37;
+ SettingProto accessibility_display_magnification_enabled = 38;
+ SettingProto accessibility_display_magnification_scale = 39;
+ SettingProto accessibility_soft_keyboard_mode = 40;
+ SettingProto accessibility_captioning_enabled = 41;
+ SettingProto accessibility_captioning_locale = 42;
+ SettingProto accessibility_captioning_preset = 43;
+ SettingProto accessibility_captioning_background_color = 44;
+ SettingProto accessibility_captioning_foreground_color = 45;
+ SettingProto accessibility_captioning_edge_type = 46;
+ SettingProto accessibility_captioning_edge_color = 47;
+ SettingProto accessibility_captioning_window_color = 48;
+ SettingProto accessibility_captioning_typeface = 49;
+ SettingProto accessibility_captioning_font_scale = 50;
+ SettingProto accessibility_display_inversion_enabled = 51;
+ SettingProto accessibility_display_daltonizer_enabled = 52;
+ SettingProto accessibility_display_daltonizer = 53;
+ SettingProto accessibility_autoclick_enabled = 54;
+ SettingProto accessibility_autoclick_delay = 55;
+ SettingProto accessibility_large_pointer_icon = 56;
+ SettingProto long_press_timeout = 57;
+ SettingProto multi_press_timeout = 58;
+ SettingProto enabled_print_services = 59;
+ SettingProto disabled_print_services = 60;
+ SettingProto display_density_forced = 61;
+ SettingProto tts_default_rate = 62;
+ SettingProto tts_default_pitch = 63;
+ SettingProto tts_default_synth = 64;
+ SettingProto tts_default_locale = 65;
+ SettingProto tts_enabled_plugins = 66;
+ SettingProto connectivity_release_pending_intent_delay_ms = 67;
+ SettingProto allowed_geolocation_origins = 68;
+ SettingProto preferred_tty_mode = 69;
+ SettingProto enhanced_voice_privacy_enabled = 70;
+ SettingProto tty_mode_enabled = 71;
+ SettingProto backup_enabled = 72;
+ SettingProto backup_auto_restore = 73;
+ SettingProto backup_provisioned = 74;
+ SettingProto backup_transport = 75;
+ SettingProto last_setup_shown = 76;
+ SettingProto search_global_search_activity = 77;
+ SettingProto search_num_promoted_sources = 78;
+ SettingProto search_max_results_to_display = 79;
+ SettingProto search_max_results_per_source = 80;
+ SettingProto search_web_results_override_limit = 81;
+ SettingProto search_promoted_source_deadline_millis = 82;
+ SettingProto search_source_timeout_millis = 83;
+ SettingProto search_prefill_millis = 84;
+ SettingProto search_max_stat_age_millis = 85;
+ SettingProto search_max_source_event_age_millis = 86;
+ SettingProto search_min_impressions_for_source_ranking = 87;
+ SettingProto search_min_clicks_for_source_ranking = 88;
+ SettingProto search_max_shortcuts_returned = 89;
+ SettingProto search_query_thread_core_pool_size = 90;
+ SettingProto search_query_thread_max_pool_size = 91;
+ SettingProto search_shortcut_refresh_core_pool_size = 92;
+ SettingProto search_shortcut_refresh_max_pool_size = 93;
+ SettingProto search_thread_keepalive_seconds = 94;
+ SettingProto search_per_source_concurrent_query_limit = 95;
+ SettingProto mount_play_notification_snd = 96;
+ SettingProto mount_ums_autostart = 97;
+ SettingProto mount_ums_prompt = 98;
+ SettingProto mount_ums_notify_enabled = 99;
+ SettingProto anr_show_background = 100;
+ SettingProto voice_recognition_service = 101;
+ SettingProto package_verifier_user_consent = 102;
+ SettingProto selected_spell_checker = 103;
+ SettingProto selected_spell_checker_subtype = 104;
+ SettingProto spell_checker_enabled = 105;
+ SettingProto incall_power_button_behavior = 106;
+ SettingProto incall_back_button_behavior = 107;
+ SettingProto wake_gesture_enabled = 108;
+ SettingProto doze_enabled = 109;
+ SettingProto doze_always_on = 110;
+ SettingProto doze_pulse_on_pick_up = 111;
+ SettingProto doze_pulse_on_double_tap = 112;
+ SettingProto ui_night_mode = 113;
+ SettingProto screensaver_enabled = 114;
+ SettingProto screensaver_components = 115;
+ SettingProto screensaver_activate_on_dock = 116;
+ SettingProto screensaver_activate_on_sleep = 117;
+ SettingProto screensaver_default_component = 118;
+ SettingProto nfc_payment_default_component = 119;
+ SettingProto nfc_payment_foreground = 120;
+ SettingProto sms_default_application = 121;
+ SettingProto dialer_default_application = 122;
+ SettingProto emergency_assistance_application = 123;
+ SettingProto assist_structure_enabled = 124;
+ SettingProto assist_screenshot_enabled = 125;
+ SettingProto assist_disclosure_enabled = 126;
+ SettingProto enabled_notification_assistant = 127;
+ SettingProto enabled_notification_listeners = 128;
+ SettingProto enabled_notification_policy_access_packages = 129;
+ SettingProto sync_parent_sounds = 130;
+ SettingProto immersive_mode_confirmations = 131;
+ SettingProto print_service_search_uri = 132;
+ SettingProto payment_service_search_uri = 133;
+ SettingProto skip_first_use_hints = 134;
+ SettingProto unsafe_volume_music_active_ms = 135;
+ SettingProto lock_screen_show_notifications = 136;
+ SettingProto tv_input_hidden_inputs = 137;
+ SettingProto tv_input_custom_labels = 138;
+ SettingProto usb_audio_automatic_routing_disabled = 139;
+ SettingProto sleep_timeout = 140;
+ SettingProto double_tap_to_wake = 141;
+ SettingProto assistant = 142;
+ SettingProto camera_gesture_disabled = 143;
+ SettingProto camera_double_tap_power_gesture_disabled = 144;
+ SettingProto camera_double_twist_to_flip_enabled = 145;
+ SettingProto night_display_activated = 146;
+ SettingProto night_display_auto_mode = 147;
+ SettingProto night_display_custom_start_time = 148;
+ SettingProto night_display_custom_end_time = 149;
+ SettingProto brightness_use_twilight = 150;
+ SettingProto enabled_vr_listeners = 151;
+ SettingProto vr_display_mode = 152;
+ SettingProto carrier_apps_handled = 153;
+ SettingProto managed_profile_contact_remote_search = 154;
+ SettingProto automatic_storage_manager_enabled = 155;
+ SettingProto automatic_storage_manager_days_to_retain = 156;
+ SettingProto automatic_storage_manager_bytes_cleared = 157;
+ SettingProto automatic_storage_manager_last_run = 158;
+ SettingProto system_navigation_keys_enabled = 159;
+ SettingProto downloads_backup_enabled = 160;
+ SettingProto downloads_backup_allow_metered = 161;
+ SettingProto downloads_backup_charging_only = 162;
+ SettingProto automatic_storage_manager_downloads_days_to_retain = 163;
+ SettingProto qs_tiles = 164;
+ SettingProto demo_user_setup_complete = 165;
+ SettingProto web_action_enabled = 166;
+ SettingProto device_paired = 167;
+}
+
+message SystemSettingsProto {
+ // Historical operations
+ repeated SettingsOperationProto historical_op = 1;
+
+ SettingProto end_button_behavior = 2;
+ SettingProto advanced_settings = 3;
+ SettingProto bluetooth_discoverability = 4;
+ SettingProto bluetooth_discoverability_timeout = 5;
+ SettingProto font_scale = 6;
+ SettingProto system_locales = 7;
+ SettingProto screen_off_timeout = 8;
+ SettingProto screen_brightness = 9;
+ SettingProto screen_brightness_for_vr = 10;
+ SettingProto screen_brightness_mode = 11;
+ SettingProto screen_auto_brightness_adj = 12;
+ SettingProto mode_ringer_streams_affected = 13;
+ SettingProto mute_streams_affected = 14;
+ SettingProto vibrate_on = 15;
+ SettingProto vibrate_input_devices = 16;
+ SettingProto volume_ring = 17;
+ SettingProto volume_system = 18;
+ SettingProto volume_voice = 19;
+ SettingProto volume_music = 20;
+ SettingProto volume_alarm = 21;
+ SettingProto volume_notification = 22;
+ SettingProto volume_bluetooth_sco = 23;
+ SettingProto volume_master = 24;
+ SettingProto master_mono = 25;
+ SettingProto vibrate_in_silent = 26;
+ SettingProto append_for_last_audible = 27;
+ SettingProto ringtone = 28;
+ SettingProto ringtone_cache = 29;
+ SettingProto notification_sound = 30;
+ SettingProto notification_sound_cache = 31;
+ SettingProto alarm_alert = 32;
+ SettingProto alarm_alert_cache = 33;
+ SettingProto media_button_receiver = 34;
+ SettingProto text_auto_replace = 35;
+ SettingProto text_auto_caps = 36;
+ SettingProto text_auto_punctuate = 37;
+ SettingProto text_show_password = 38;
+ SettingProto show_gtalk_service_status = 39;
+ SettingProto time_12_24 = 40;
+ SettingProto date_format = 41;
+ SettingProto setup_wizard_has_run = 42;
+ SettingProto accelerometer_rotation = 43;
+ SettingProto user_rotation = 44;
+ SettingProto hide_rotation_lock_toggle_for_accessibility = 45;
+ SettingProto vibrate_when_ringing = 46;
+ SettingProto dtmf_tone_when_dialing = 47;
+ SettingProto dtmf_tone_type_when_dialing = 48;
+ SettingProto hearing_aid = 49;
+ SettingProto tty_mode = 50;
+ SettingProto sound_effects_enabled = 51;
+ SettingProto haptic_feedback_enabled = 52;
+ SettingProto notification_light_pulse = 53;
+ SettingProto pointer_location = 54;
+ SettingProto show_touches = 55;
+ SettingProto window_orientation_listener_log = 56;
+ SettingProto lockscreen_sounds_enabled = 57;
+ SettingProto lockscreen_disabled = 58;
+ SettingProto sip_receive_calls = 59;
+ SettingProto sip_call_options = 60;
+ SettingProto sip_always = 61;
+ SettingProto sip_address_only = 62;
+ SettingProto pointer_speed = 63;
+ SettingProto lock_to_app_enabled = 64;
+ SettingProto egg_mode = 65;
+ SettingProto when_to_make_wifi_calls = 66;
+}
+
+message SettingProto {
+ // ID of the setting
+ string id = 1;
+
+ // Name of the setting
+ string name = 2;
+
+ // Package name of the setting
+ string pkg = 3;
+
+ // Value of this setting
+ string value = 4;
+
+ // Default value of this setting
+ string default_value = 5;
+
+ // Whether the default is set by the system
+ bool default_from_system = 6;
+}
+
+message SettingsOperationProto {
+ // When the operation happened
+ int64 timestamp = 1;
+
+ // Type of the operation
+ string operation = 2;
+
+ // Name of the setting that was affected (optional)
+ string setting = 3;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2692bf2..094e2b8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -443,6 +443,7 @@
<protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
<protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
+ <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
<protected-broadcast android:name="EventConditionProvider.EVALUATE" />
<protected-broadcast android:name="SnoozeHelper.EVALUATE" />
@@ -514,6 +515,7 @@
<!-- Added in O -->
<!-- TODO: temporary broadcast used by AutoFillManagerServiceImpl; will be removed -->
<protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
+ <protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -1963,7 +1965,7 @@
{@link android.content.pm.PackageManager#addPackageToPreferred}
for details. -->
<permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|verifier" />
<!-- Allows an application to receive the
{@link android.content.Intent#ACTION_BOOT_COMPLETED} that is
@@ -2645,6 +2647,21 @@
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to set the volume key long-press listener.
+ <p>When it's set, the application will receive the volume key long-press event
+ instead of changing volume.</p>
+ <p>Not for use by third-party applications</p> -->
+ <permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- @SystemApi @hide Allows an application to set media key event listener.
+ <p>When it's set, the application will receive the media key event before
+ any other media sessions. If the event is handled by the listener, other sessions
+ cannot get the event.</p>
+ <p>Not for use by third-party applications</p> -->
+ <permission android:name="android.permission.SET_MEDIA_KEY_LISTENER"
+ android:protectionLevel="signature|privileged|development" />
+
<!-- @SystemApi Required to be able to disable the device (very dangerous!).
<p>Not for use by third-party applications.
@hide
diff --git a/core/res/res/anim/app_starting_exit.xml b/core/res/res/anim/app_starting_exit.xml
index aaf7f15..dfa42e2 100644
--- a/core/res/res/anim/app_starting_exit.xml
+++ b/core/res/res/anim/app_starting_exit.xml
@@ -21,8 +21,8 @@
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:detachWallpaper="true"
- android:interpolator="@interpolator/decelerate_quad"
+ android:interpolator="@interpolator/linear"
android:fromAlpha="1.0"
android:toAlpha="0.0"
- android:duration="160" />
+ android:duration="150" />
diff --git a/core/res/res/values-mcc214-mnc01/config.xml b/core/res/res/values-mcc214-mnc01/config.xml
index 895b770..24150a7 100644
--- a/core/res/res/values-mcc214-mnc01/config.xml
+++ b/core/res/res/values-mcc214-mnc01/config.xml
@@ -40,27 +40,4 @@
<item>INTERNET,airtelnet.es,,,vodafone,vodafone,,,,,214,01,1,DUN</item>
</string-array>
- <string-array translatable="false" name="config_operatorConsideredNonRoaming">
- <item>21402</item>
- <item>21403</item>
- <item>21404</item>
- <item>21405</item>
- <item>21406</item>
- <item>21407</item>
- <item>21408</item>
- <item>21409</item>
- <item>21410</item>
- <item>21411</item>
- <item>21412</item>
- <item>21413</item>
- <item>21414</item>
- <item>21415</item>
- <item>21416</item>
- <item>21417</item>
- <item>21418</item>
- <item>21419</item>
- <item>21420</item>
- <item>21421</item>
- </string-array>
-
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7c01dea..c333629 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2274,13 +2274,16 @@
<!-- Sets the padding, in pixels, of the end edge; see {@link android.R.attr#padding}. -->
<attr name="paddingEnd" format="dimension" />
- <!-- Boolean that controls whether a view can take focus. By default the user can not
- move focus to a view; by setting this attribute to true the view is
- allowed to take focus. This value does not impact the behavior of
+ <!-- Controls whether a view can take focus. By default, this is "auto" which lets the
+ framework determine whether a user can move focus to a view. By setting this attribute
+ to true the view is allowed to take focus. By setting it to "false" the view will not
+ take focus. This value does not impact the behavior of
directly calling {@link android.view.View#requestFocus}, which will
always request focus regardless of this view. It only impacts where
focus navigation will try to move focus. -->
- <attr name="focusable" format="boolean" />
+ <attr name="focusable" format="boolean|enum">
+ <enum name="auto" value="0x00000010" />
+ </attr>
<!-- Boolean that controls whether a view can take focus while in touch mode.
If this is true for a view, that view can gain focus when clicked on, and can keep
@@ -2896,9 +2899,7 @@
See {@link android.view.View#setKeyboardNavigationCluster(boolean)}. -->
<attr name="keyboardNavigationCluster" format="boolean" />
- <!-- Whether this view is a root of a keyboard navigation section.
- See {@link android.view.View#setKeyboardNavigationSection(boolean)}. -->
- <attr name="keyboardNavigationSection" format="boolean" />
+ <attr name="__removed0" format="boolean" />
<!-- Defines the next keyboard navigation cluster.
@@ -2907,12 +2908,7 @@
will result when the reference is accessed.-->
<attr name="nextClusterForward" format="reference"/>
- <!-- Defines the next keyboard navigation section.
-
- If the reference refers to a view that does not exist or is part
- of a hierarchy that is invisible, a {@link java.lang.RuntimeException}
- will result when the reference is accessed.-->
- <attr name="nextSectionForward" format="reference"/>
+ <attr name="__removed1" format="reference"/>
<!-- Whether this view is a default-focus view.
Only one view per keyboard navigation cluster can have this attribute set to true.
@@ -5843,6 +5839,14 @@
<attr name="color" />
</declare-styleable>
+ <!-- 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>
+
<!-- Drawable used to show animated touch feedback. -->
<declare-styleable name="RippleDrawable">
<!-- The color to use for ripple effects. This attribute is required. -->
@@ -7919,7 +7923,6 @@
<attr name="queryBackground" format="reference" />
<!-- Background for the section containing the action (e.g. voice search) -->
<attr name="submitBackground" format="reference" />
- <attr name="focusable" />
</declare-styleable>
<declare-styleable name="Switch">
@@ -8477,4 +8480,14 @@
<attr name="font" format="reference" />
<attr name="fontWeight" format="integer" />
</declare-styleable>
+
+ <!-- @hide -->
+ <declare-styleable name="RecyclerView">
+ <attr name="layoutManager" format="string" />
+ <attr name="orientation" />
+ <attr name="descendantFocusability" />
+ <attr name="spanCount" format="integer"/>
+ <attr name="reverseLayout" format="boolean" />
+ <attr name="stackFromEnd" format="boolean" />
+ </declare-styleable>
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 5235116..113ace3 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -864,9 +864,8 @@
<flag name="density" value="0x1000" />
<!-- The layout direction has changed. For example going from LTR to RTL. -->
<flag name="layoutDirection" value="0x2000" />
- <!-- The colorimetry capabilities of the screen have changed (color gamut
- or dynamic range). -->
- <flag name="colorimetry" value="0x4000" />
+ <!-- The color mode of the screen has changed (color gamut or dynamic range). -->
+ <flag name="colorMode" value="0x4000" />
<!-- The font scaling factor has changed, that is the user has
selected a new global font size. -->
<flag name="fontScale" value="0x40000000" />
@@ -1159,18 +1158,15 @@
resizeable activities when in multi-window mode. -->
<attr name="resizeableActivity" format="boolean" />
- <!-- Indicates that the activity supports the picture-in-picture (PiP) form of multi-window.
- While it makes sense to be able to resize most activities types in multi-window mode when
- {@link android.R.attr#resizeableActivity} is set. It only makes sense to put specific types
- of activities in PiP mode of multi-window. For example, activities that play video. When
- set the activity will be allowed to enter PiP mode when the system deems it appropriate on
- devices that support PiP.
+ <!-- Indicates that the activity specifically supports the picture-in-picture form of
+ multi-window. If true, this activity will support entering picture-in-picture, but will
+ only support split-screen and other forms of multi-window if
+ {@link android.R.attr#resizeableActivity} is also set to true.
- <p>The default value is <code>false</code> for applications with
- <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#N} and
- <code>true</code> otherwise.
+ Note that your activity may still be resized even if this attribute is true and
+ {@link android.R.attr#resizeableActivity} is false.
- <p>NOTE: Attribute is only used if {@link android.R.attr#resizeableActivity} is true. -->
+ <p>The default value is <code>false</code>. -->
<attr name="supportsPictureInPicture" format="boolean" />
<!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
@@ -1240,6 +1236,10 @@
<!-- An XML resource with the application's Network Security Config. -->
<attr name="networkSecurityConfig" format="reference" />
+ <!-- When an application is partitioned into splits, this is the name of the
+ split that contains the defined component. -->
+ <attr name="splitName" format="string" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -1823,6 +1823,8 @@
<attr name="singleUser" />
<attr name="directBootAware" />
<attr name="visibleToInstantApps" />
+ <!-- The code for this component is located in the given split. -->
+ <attr name="splitName" />
</declare-styleable>
<!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -1913,6 +1915,8 @@
must also be {@link android.R.attr#exported} if this flag is set. -->
<attr name="externalService" format="boolean" />
<attr name="visibleToInstantApps" />
+ <!-- The code for this component is located in the given split. -->
+ <attr name="splitName" />
</declare-styleable>
<!-- The <code>receiver</code> tag declares an
@@ -2036,6 +2040,18 @@
<attr name="onTopLauncher" format="boolean" />
<attr name="rotationAnimation" />
<attr name="visibleToInstantApps" />
+ <!-- The code for this component is located in the given split. -->
+ <attr name="splitName" />
+ <!-- Specify the color mode the activity desires. The requested color mode may be ignored
+ depending on the capabilities of the display the activity is displayed on. -->
+ <attr name="colorMode">
+ <!-- The default color mode (typically sRGB, low-dynamic range). -->
+ <enum name="default" value="0" />
+ <!-- Wide color gamut color mode. -->
+ <enum name="wideColorGamut" value="1" />
+ <!-- High dynamic range color mode. -->
+ <enum name="hdr" value="2" />
+ </attr>
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7de48d3..6a8b556 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2090,6 +2090,7 @@
<item>com.android.server.notification.ImportanceExtractor</item>
<item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
<item>com.android.server.notification.VisibilityExtractor</item>
+ <item>com.android.server.notification.BadgeExtractor</item>
</string-array>
<!-- Flag indicating that this device does not rotate and will always remain in its default
@@ -2624,8 +2625,14 @@
<!-- Component that is the default launcher when demo mode is enabled. -->
<string name="config_demoModeLauncherComponent">com.android.retaildemo/.DemoPlayer</string>
- <!-- Hashed password (SHA-256) used to restrict demo mode operation -->
- <string name="config_demoModePassword" translatable="false"></string>
+ <!-- Hashed password (SHA-256) used to restrict carrier demo mode operation. -->
+ <string name="config_carrierDemoModePassword" translatable="false"></string>
+
+ <!-- Secure setting used to activate carrier demo mode. -->
+ <string name="config_carrierDemoModeSetting" translatable="false"></string>
+
+ <!-- List of packages to enable in carrier demo mode (comma separated). -->
+ <string name="config_carrierDemoModePackages" translatable="false"></string>
<!-- Flag indicating whether round icons should be parsed from the application manifest. -->
<bool name="config_useRoundIcon">false</bool>
@@ -2711,6 +2718,9 @@
<!-- Component name of the default cell broadcast receiver -->
<string name="config_defaultCellBroadcastReceiverComponent" translatable="false">com.android.cellbroadcastreceiver/.PrivilegedCellBroadcastReceiver</string>
+ <!-- Specifies the path that is used by MaskableIconDrawable class to crop launcher icons. -->
+ <string name="config_icon_mask" translatable="false">"M50,0L100,0 100,100 0,100 0,0z"</string>
+
<!-- The component name, flattened to a string, for the default accessibility service to be
enabled by the accessibility shortcut. This service must be trusted, as it can be activated
without explicit consent of the user. If no accessibility service with the specified name
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bd19521..e6358a3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -509,4 +509,10 @@
<dimen name="tooltip_precise_anchor_threshold">96dp</dimen>
<!-- Extra tooltip offset used when anchoring to the mouse/touch position -->
<dimen name="tooltip_precise_anchor_extra_offset">8dp</dimen>
+
+ <!-- The max amount of scroll ItemTouchHelper will trigger if dragged view is out of
+ RecyclerView's bounds.-->
+ <dimen name="item_touch_helper_max_drag_scroll_per_frame">20dp</dimen>
+ <dimen name="item_touch_helper_swipe_escape_velocity">120dp</dimen>
+ <dimen name="item_touch_helper_swipe_escape_max_velocity">800dp</dimen>
</resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 5547706..613616f 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -97,6 +97,7 @@
<item type="id" name="redo" />
<item type="id" name="replaceText" />
<item type="id" name="shareText" />
+ <item type="id" name="textAssist" />
<item type="id" name="selection_start_handle" />
<item type="id" name="selection_end_handle" />
<item type="id" name="insertion_handle" />
@@ -131,4 +132,7 @@
<item type="id" name="cross_task_transition" />
<item type="id" name="accessibilityActionClickOnClickableSpan" />
+
+ <!-- ItemTouchHelper uses this id to save a View's original elevation. -->
+ <item type="id" name="item_touch_helper_previous_elevation"/>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 060c59e..e387650 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2777,9 +2777,9 @@
<public name="paddingVertical" />
<public name="visibleToInstantApps" />
<public name="keyboardNavigationCluster" />
- <public name="keyboardNavigationSection" />
+ <public name="__removed0" />
<public name="nextClusterForward" />
- <public name="nextSectionForward" />
+ <public name="__removed1" />
<public name="textColorError" />
<public name="focusedByDefault" />
<public name="appCategory" />
@@ -2787,12 +2787,15 @@
<public name="supportsDismissingWindow" />
<public name="restartOnConfigChanges" />
<public name="certDigest" />
+ <public name="splitName" />
+ <public name="colorMode" />
</public-group>
<public-group type="style" first-id="0x010302e0">
</public-group>
<public-group type="id" first-id="0x01020041">
+ <public name="textAssist" />
</public-group>
<public type="attr" name="primaryContentAlpha" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d09b190..eece9fc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2589,6 +2589,15 @@
<!-- Title for EditText context menu [CHAR LIMIT=20] -->
<string name="editTextMenuTitle">Text actions</string>
+ <!-- Label for item in the text selection menu to trigger an Email app [CHAR LIMIT=20] -->
+ <string name="email">Email</string>
+
+ <!-- Label for item in the text selection menu to trigger a Dialer app [CHAR LIMIT=20] -->
+ <string name="dial">Dial</string>
+
+ <!-- Label for item in the text selection menu to trigger a Map app [CHAR LIMIT=20] -->
+ <string name="map">Map</string>
+
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the title of that notification. -->
<string name="low_internal_storage_view_title">Storage space running out</string>
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the message of that notification. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c370ef7..58c925f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -476,6 +476,9 @@
<java-symbol type="string" name="replace" />
<java-symbol type="string" name="undo" />
<java-symbol type="string" name="redo" />
+ <java-symbol type="string" name="email" />
+ <java-symbol type="string" name="dial" />
+ <java-symbol type="string" name="map" />
<java-symbol type="string" name="textSelectionCABTitle" />
<java-symbol type="string" name="BaMmi" />
<java-symbol type="string" name="CLIRDefaultOffNextCallOff" />
@@ -1133,7 +1136,9 @@
<java-symbol type="string" name="config_ethernet_tcp_buffers" />
<java-symbol type="string" name="config_wifi_tcp_buffers" />
<java-symbol type="string" name="config_demoModeLauncherComponent" />
- <java-symbol type="string" name="config_demoModePassword" />
+ <java-symbol type="string" name="config_carrierDemoModePassword" />
+ <java-symbol type="string" name="config_carrierDemoModeSetting" />
+ <java-symbol type="string" name="config_carrierDemoModePackages" />
<java-symbol type="string" name="demo_starting_message" />
<java-symbol type="string" name="demo_restarting_message" />
<java-symbol type="string" name="conference_call" />
@@ -2796,6 +2801,8 @@
<java-symbol type="raw" name="fallback_categories" />
+ <java-symbol type="string" name="config_icon_mask" />
+
<java-symbol type="attr" name="primaryContentAlpha" />
<!-- Accessibility Shortcut -->
@@ -2806,4 +2813,10 @@
<java-symbol type="string" name="disable_accessibility_shortcut" />
<java-symbol type="string" name="leave_accessibility_shortcut_on" />
<java-symbol type="string" name="config_defaultAccessibilityService" />
+
+ <!-- com.android.internal.widget.RecyclerView -->
+ <java-symbol type="id" name="item_touch_helper_previous_elevation"/>
+ <java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/>
+ <java-symbol type="dimen" name="item_touch_helper_swipe_escape_velocity"/>
+ <java-symbol type="dimen" name="item_touch_helper_swipe_escape_max_velocity"/>
</resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index cba485a..91ce7a4 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1168,6 +1168,7 @@
<activity android:name="android.app.EmptyActivity">
</activity>
<activity android:name="com.android.internal.app.ChooserWrapperActivity"/>
+ <activity android:name="com.android.internal.app.ResolverWrapperActivity"/>
<receiver android:name="android.app.activity.AbortReceiver">
<intent-filter android:priority="1">
diff --git a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
similarity index 80%
rename from core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
rename to core/tests/coretests/src/android/metrics/LogMakerTest.java
index a340559..0f75cb6 100644
--- a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.logging;
+package android.metrics;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import junit.framework.TestCase;
-public class LogBuilderTest extends TestCase {
+public class LogMakerTest extends TestCase {
public void testSerialize() {
- LogBuilder builder = new LogBuilder(0);
+ LogMaker builder = new LogMaker(0);
builder.addTaggedData(1, "one");
builder.addTaggedData(2, "two");
Object[] out = builder.serialize();
@@ -41,7 +41,7 @@
int bucket = 13;
int value = 14;
- LogBuilder builder = new LogBuilder(category);
+ LogMaker builder = new LogMaker(category);
builder.setType(type);
builder.setSubtype(subtype);
builder.setTimestamp(timestamp);
@@ -53,7 +53,7 @@
builder.addTaggedData(2, "two");
Object[] out = builder.serialize();
- LogBuilder parsed = new LogBuilder(out);
+ LogMaker parsed = new LogMaker(out);
assertEquals(category, parsed.getCategory());
assertEquals(type, parsed.getType());
@@ -68,7 +68,7 @@
}
public void testIntBucket() {
- LogBuilder builder = new LogBuilder(0);
+ LogMaker builder = new LogMaker(0);
builder.setCounterBucket(100);
assertEquals(100, builder.getCounterBucket());
assertEquals(false, builder.isLongCounterBucket());
@@ -76,14 +76,14 @@
public void testLongBucket() {
long longBucket = Long.MAX_VALUE;
- LogBuilder builder = new LogBuilder(0);
+ LogMaker builder = new LogMaker(0);
builder.setCounterBucket(longBucket);
assertEquals(longBucket, builder.getCounterBucket());
assertEquals(true, builder.isLongCounterBucket());
}
public void testInvalidInputThrows() {
- LogBuilder builder = new LogBuilder(0);
+ LogMaker builder = new LogMaker(0);
boolean threw = false;
try {
builder.addTaggedData(0, new Object());
@@ -95,7 +95,7 @@
}
public void testValidInputTypes() {
- LogBuilder builder = new LogBuilder(0);
+ LogMaker builder = new LogMaker(0);
builder.addTaggedData(1, "onetwothree");
builder.addTaggedData(2, 123);
builder.addTaggedData(3, 123L);
@@ -107,11 +107,21 @@
assertEquals(123.0F, out[7]);
}
- public void testCategoryDefault() {
- LogBuilder builder = new LogBuilder(10);
+ public void testCategoryDefault() {
+ LogMaker builder = new LogMaker(10);
Object[] out = builder.serialize();
assertEquals(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, out[0]);
assertEquals(10, out[1]);
}
+ public void testGiantLogOmitted() {
+ LogMaker badBuilder = new LogMaker(0);
+ StringBuilder b = new StringBuilder();
+ for (int i = 0; i < 4000; i++) {
+ b.append("test, " + i);
+ }
+ badBuilder.addTaggedData(100, b.toString());
+ assertTrue(badBuilder.serialize().length < LogMaker.MAX_SERIALIZED_SIZE);
+ }
+
}
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
new file mode 100644
index 0000000..1afe9da
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -0,0 +1,75 @@
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkKeyTest {
+ private static final String VALID_SSID = "\"ssid1\"";
+ private static final String VALID_BSSID = "00:00:00:00:00:00";
+ @Mock private WifiInfo mWifiInfo;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void createFromWifi_nullInput() throws Exception {
+ assertNull(NetworkKey.createFromWifiInfo(null));
+ }
+
+ @Test
+ public void createFromWifi_nullSsid() throws Exception {
+ when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_emptySsid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn("");
+ when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_noneSsid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(WifiSsid.NONE);
+ when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_nullBssid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_emptyBssid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+ when(mWifiInfo.getBSSID()).thenReturn("");
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_validWifiInfo() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+ when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+
+ NetworkKey expected = new NetworkKey(new WifiKey(VALID_SSID, VALID_BSSID));
+ final NetworkKey actual = NetworkKey.createFromWifiInfo(mWifiInfo);
+ assertEquals(expected, actual);
+ }
+}
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 aab4698..daebf88 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -222,7 +222,7 @@
private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ChooserDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
}
return infoList;
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 41016e1..c446f3c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -24,11 +24,10 @@
import static org.mockito.Mockito.mock;
-
-/**
- * Simple wrapper around chooser activity to be able to initiate it under test
- */
public class ChooserWrapperActivity extends ChooserActivity {
+ /*
+ * Simple wrapper around chooser activity to be able to initiate it under test
+ */
static final OverrideData sOverrides = new OverrideData();
private UsageStatsManager mUsm;
@@ -94,4 +93,4 @@
resolverListController = mock(ResolverListController.class);
}
}
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
new file mode 100644
index 0000000..84b844a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import com.android.internal.R;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static android.os.SystemClock.sleep;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+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;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Resolver activity instrumentation tests
+ */
+@RunWith(AndroidJUnit4.class)
+public class ResolverActivityTest {
+ @Rule
+ public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
+ new ActivityTestRule<>(ResolverWrapperActivity.class, false,
+ false);
+
+ @Before
+ public void cleanOverrideData() {
+ sOverrides.reset();
+ }
+
+ @Test
+ public void twoOptionsAndUserSelectsOne() throws InterruptedException {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getAdapter().getCount(), is(2));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+ onView(withText(toChoose.activityInfo.name))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ @Test
+ public void hasLastChosenActivity() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+ 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;
+ };
+
+ 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));
+ }
+
+ private Intent createSendImageIntent() {
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+ sendIntent.setType("image/jpeg");
+ return sendIntent;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ return infoList;
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
similarity index 94%
rename from core/tests/coretests/src/com/android/internal/app/ChooserDataProvider.java
rename to core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
index f6f63f1..ae06306 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserDataProvider.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
@@ -22,16 +22,15 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
-import android.service.chooser.ChooserTarget;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * Utility class used by chooser tests to create mock data
+ * Utility class used by resolver tests to create mock data
*/
-class ChooserDataProvider {
+class ResolverDataProvider {
static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(int i) {
return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
new file mode 100644
index 0000000..163211e
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -0,0 +1,91 @@
+/*
+ * 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.internal.app;
+
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+import java.util.function.Function;
+
+import static org.mockito.Mockito.mock;
+
+
+/*
+ * Simple wrapper around chooser activity to be able to initiate it under test
+ */
+public class ResolverWrapperActivity extends ResolverActivity {
+ static final OverrideData sOverrides = new OverrideData();
+ private UsageStatsManager mUsm;
+
+ ResolveListAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public boolean isVoiceInteraction() {
+ if (sOverrides.isVoiceInteraction != null) {
+ return sOverrides.isVoiceInteraction;
+ }
+ return super.isVoiceInteraction();
+ }
+
+ @Override
+ public void safelyStartActivity(TargetInfo cti) {
+ if (sOverrides.onSafelyStartCallback != null &&
+ sOverrides.onSafelyStartCallback.apply(cti)) {
+ return;
+ }
+ super.safelyStartActivity(cti);
+ }
+
+ @Override
+ protected ResolverListController createListController() {
+ return sOverrides.resolverListController;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ if (sOverrides.createPackageManager != null) {
+ return sOverrides.createPackageManager.apply(super.getPackageManager());
+ }
+ return super.getPackageManager();
+ }
+
+ /**
+ * We cannot directly mock the activity created since instrumentation creates it.
+ * <p>
+ * Instead, we use static instances of this object to modify behavior.
+ */
+ static class OverrideData {
+ @SuppressWarnings("Since15")
+ public Function<PackageManager, PackageManager> createPackageManager;
+ public Function<TargetInfo, Boolean> onSafelyStartCallback;
+ public ResolverListController resolverListController;
+ public Boolean isVoiceInteraction;
+
+ public void reset() {
+ onSafelyStartCallback = null;
+ isVoiceInteraction = null;
+ createPackageManager = null;
+ resolverListController = mock(ResolverListController.class);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 34c34d7..dc75417 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -33,7 +33,8 @@
import java.util.List;
public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTestCase {
- private static final String DUMMY_PACKAGE_NAME = "dymmy package name";
+ private static final String DUMMY_PACKAGE_NAME = "dummy package name";
+ private static final String DUMMY_IME_LABEL = "dummy ime label";
private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
private static final boolean DUMMY_IS_AUX_IME = false;
private static final boolean DUMMY_FORCE_DEFAULT = false;
@@ -88,6 +89,35 @@
}
}
+ private static ImeSubtypeListItem createDummyItem(String imeName,
+ String subtypeName, String subtypeLocale, int subtypeIndex, String systemLocale) {
+ final ResolveInfo ri = new ResolveInfo();
+ final ServiceInfo si = new ServiceInfo();
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = DUMMY_PACKAGE_NAME;
+ ai.enabled = true;
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = DUMMY_PACKAGE_NAME;
+ si.name = imeName;
+ si.exported = true;
+ si.nonLocalizedLabel = DUMMY_IME_LABEL;
+ ri.serviceInfo = si;
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+ subtypes.add(new InputMethodSubtypeBuilder()
+ .setSubtypeNameResId(0)
+ .setSubtypeIconResId(0)
+ .setSubtypeLocale(subtypeLocale)
+ .setIsAsciiCapable(true)
+ .build());
+ final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
+ DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
+ DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */,
+ false /* supportsDismissingWindow */);
+ return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
+ systemLocale);
+ }
+
private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
final List<ImeSubtypeListItem> items = new ArrayList<>();
addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
@@ -329,4 +359,56 @@
assertFalse(item_e.mIsSystemLocale);
assertFalse(item_EN_US.mIsSystemLocale);
}
+
+ @SmallTest
+ public void testImeSubtypeListComparator() throws Exception {
+ {
+ final List<ImeSubtypeListItem> items = Arrays.asList(
+ createDummyItem("X", "A", "en_US", 0, "en_US"),
+ createDummyItem("X", "A", "en", 1, "en_US"),
+ createDummyItem("X", "A", "ja", 2, "en_US"),
+ createDummyItem("X", "Z", "en_US", 3, "en_US"),
+ createDummyItem("X", "Z", "en", 4, "en_US"),
+ createDummyItem("X", "Z", "ja", 5, "en_US"),
+ createDummyItem("X", "", "en_US", 6, "en_US"),
+ createDummyItem("X", "", "en", 7, "en_US"),
+ createDummyItem("X", "", "ja", 8, "en_US"),
+ createDummyItem("Y", "A", "en_US", 9, "en_US"),
+ createDummyItem("Y", "A", "en", 10, "en_US"),
+ createDummyItem("Y", "A", "ja", 11, "en_US"),
+ createDummyItem("Y", "Z", "en_US", 12, "en_US"),
+ createDummyItem("Y", "Z", "en", 13, "en_US"),
+ createDummyItem("Y", "Z", "ja", 14, "en_US"),
+ createDummyItem("Y", "", "en_US", 15, "en_US"),
+ createDummyItem("Y", "", "en", 16, "en_US"),
+ createDummyItem("Y", "", "ja", 17, "en_US"),
+ createDummyItem("", "A", "en_US", 18, "en_US"),
+ createDummyItem("", "A", "en", 19, "en_US"),
+ createDummyItem("", "A", "ja", 20, "en_US"),
+ createDummyItem("", "Z", "en_US", 21, "en_US"),
+ createDummyItem("", "Z", "en", 22, "en_US"),
+ createDummyItem("", "Z", "ja", 23, "en_US"),
+ createDummyItem("", "", "en_US", 24, "en_US"),
+ createDummyItem("", "", "en", 25, "en_US"),
+ createDummyItem("", "", "ja", 26, "en_US"));
+
+ for (int i = 0; i < items.size(); ++i) {
+ assertEquals(0, items.get(i).compareTo(items.get(i)));
+ for (int j = i + 1; j < items.size(); ++j) {
+ assertTrue(items.get(i).compareTo(items.get(j)) < 0);
+ assertTrue(items.get(j).compareTo(items.get(i)) > 0);
+ }
+ }
+ }
+
+ {
+ // Following two items have the same priority.
+ final ImeSubtypeListItem nonSystemLocale1 =
+ createDummyItem("X", "A", "ja_JP", 0, "en_us");
+ final ImeSubtypeListItem nonSystemLocale2 =
+ createDummyItem("X", "A", "hi_IN", 1, "en_us");
+ assertEquals(0, nonSystemLocale1.compareTo(nonSystemLocale2));
+ assertEquals(0, nonSystemLocale2.compareTo(nonSystemLocale1));
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java
index 0bff850..c023b57 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java
@@ -19,7 +19,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class LockscreenGestureParserTest extends ParserTest {
@@ -79,7 +79,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
@@ -95,6 +95,6 @@
mParser.parseEvent(mLogger, t, objects);
- verify(mLogger, times(1)).addEvent((LogBuilder) anyObject());
+ verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
index 2119c25..f05205d 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
@@ -20,7 +20,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationActionClickedParserTest extends ParserTest {
@@ -49,12 +49,12 @@
validateGoodData(t, mTag, index, objects);
}
- private LogBuilder validateGoodData(int t, String tag, int index, Object[] objects) {
+ private LogMaker validateGoodData(int t, String tag, int index, Object[] objects) {
mParser.parseEvent(mLogger, t, objects);
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM_ACTION, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
@@ -69,7 +69,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testWrongType() throws Throwable {
@@ -79,7 +79,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testBadKey() throws Throwable {
@@ -89,7 +89,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testMncTimestamps() throws Throwable {
@@ -102,7 +102,7 @@
objects[3] = mSinceUpdateMillis;
objects[4] = mSinceVisibleMillis;
- LogBuilder proto = validateGoodData(t, "", index, objects);
+ LogMaker proto = validateGoodData(t, "", index, objects);
validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
mSinceVisibleMillis);
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
index 1e117ee..7771e84 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
@@ -21,12 +21,9 @@
import static org.mockito.Mockito.when;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import java.util.Collections;
-import java.util.List;
-
import org.mockito.ArgumentCaptor;
public class NotificationAlertParserTest extends ParserTest {
@@ -126,7 +123,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(mTime, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ALERT, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
index de16919..77b2ed6 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
@@ -21,11 +21,9 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import java.util.List;
-
public class NotificationCanceledParserTest extends ParserTest {
public NotificationCanceledParserTest() {
@@ -57,7 +55,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
@@ -108,7 +106,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
validateNotificationTimes(proto, life, freshness, exposure);
}
@@ -121,7 +119,7 @@
mParser.parseEvent(mLogger, 0, objects);
if (intentional) {
- verify(mLogger, times(1)).addEvent((LogBuilder) anyObject());
+ verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
}
}
@@ -164,7 +162,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testWrongType() throws Throwable {
@@ -174,7 +172,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testBadKey() throws Throwable {
@@ -184,7 +182,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testIgnoreUnexpectedData() throws Throwable {
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
index 2f61dd7..cc65132 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
@@ -21,7 +21,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationClickedParserTest extends ParserTest {
@@ -46,12 +46,12 @@
validateGoodData(t, mTag, objects);
}
- private LogBuilder validateGoodData(int t, String tag, Object[] objects) {
+ private LogMaker validateGoodData(int t, String tag, Object[] objects) {
mParser.parseEvent(mLogger, t, objects);
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
@@ -66,7 +66,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testWrongType() throws Throwable {
@@ -75,7 +75,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testBadKey() throws Throwable {
@@ -84,7 +84,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testMncTimestamps() throws Throwable {
@@ -95,7 +95,7 @@
objects[2] = mSinceUpdateMillis;
objects[3] = mSinceVisibleMillis;
- LogBuilder proto = validateGoodData(t, "", objects);
+ LogMaker proto = validateGoodData(t, "", objects);
validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
mSinceVisibleMillis);
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
index 86b4a45..f337f91 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
@@ -21,7 +21,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationExpansionParserTest extends ParserTest {
@@ -54,12 +54,12 @@
validateGoodData(t, mTag, objects);
}
- private LogBuilder validateGoodData(int t, String tag, Object[] objects) {
+ private LogMaker validateGoodData(int t, String tag, Object[] objects) {
mParser.parseEvent(mLogger, t, objects);
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
@@ -79,7 +79,7 @@
mParser.parseEvent(mLogger, t, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testCollapsedByUser() throws Throwable {
@@ -93,7 +93,7 @@
mParser.parseEvent(mLogger, t, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testAutoCollapsed() throws Throwable {
@@ -107,7 +107,7 @@
mParser.parseEvent(mLogger, t, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testMissingData() throws Throwable {
@@ -115,7 +115,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testWrongType() throws Throwable {
@@ -126,7 +126,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testBadKey() throws Throwable {
@@ -137,7 +137,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testMncTimestamps() throws Throwable {
@@ -152,7 +152,7 @@
objects[4] = mSinceUpdateMillis;
objects[5] = mSinceVisibleMillis;
- LogBuilder proto = validateGoodData(t, "", objects);
+ LogMaker proto = validateGoodData(t, "", objects);
validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
mSinceVisibleMillis);
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
index 3efc48f..ce6f1f4 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
@@ -19,7 +19,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationPanelHiddenParserTest extends ParserTest {
@@ -48,7 +48,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
index 34dda98..9e15812 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
@@ -20,7 +20,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationPanelRevealedParserTest extends ParserTest {
@@ -37,7 +37,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
@@ -57,7 +57,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
@@ -69,7 +69,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, times(1)).addEvent((LogBuilder) anyObject());
+ verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
}
public void testIgnoreUnexpectedData() throws Throwable {
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
index cc5421c..7fef929 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
@@ -16,17 +16,13 @@
package com.android.internal.logging.legacy;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import org.mockito.ArgumentCaptor;
-
public class NotificationVisibilityParserTest extends ParserTest {
private final int mCreationTime = 23124;
private final int mUpdateTime = 3412;
@@ -84,7 +80,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(mTime, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java
index 2e5c6d2..4adf629 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java
@@ -15,7 +15,7 @@
*/
package com.android.internal.logging.legacy;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static org.mockito.Matchers.any;
@@ -39,8 +39,8 @@
protected TagParser mParser;
- protected LogBuilder[] mProto;
- protected ArgumentCaptor<LogBuilder> mProtoCaptor;
+ protected LogMaker[] mProto;
+ protected ArgumentCaptor<LogMaker> mProtoCaptor;
protected ArgumentCaptor<String> mNameCaptor;
protected ArgumentCaptor<Integer> mCountCaptor;
protected String mKey = "0|com.android.example.notificationshowcase|31338|null|10090";
@@ -54,9 +54,9 @@
public ParserTest() {
- mProto = new LogBuilder[5];
+ mProto = new LogMaker[5];
for (int i = 0; i < mProto.length; i++) {
- mProto[i] = new LogBuilder(MetricsEvent.VIEW_UNKNOWN);
+ mProto[i] = new LogMaker(MetricsEvent.VIEW_UNKNOWN);
}
}
@@ -66,19 +66,19 @@
MockitoAnnotations.initMocks(this);
- mProtoCaptor = ArgumentCaptor.forClass(LogBuilder.class);
+ mProtoCaptor = ArgumentCaptor.forClass(LogMaker.class);
mNameCaptor = ArgumentCaptor.forClass(String.class);
mCountCaptor = ArgumentCaptor.forClass(Integer.class);
- OngoingStubbing<LogBuilder> stub = when(mLogger.obtain()).thenReturn(mProto[0]);
+ OngoingStubbing<LogMaker> stub = when(mLogger.obtain()).thenReturn(mProto[0]);
for (int i = 1; i < mProto.length; i++) {
stub.thenReturn(mProto[i]);
}
- doNothing().when(mLogger).addEvent(any(LogBuilder.class));
+ doNothing().when(mLogger).addEvent(any(LogMaker.class));
doNothing().when(mLogger).incrementBy(anyString(), anyInt());
}
- protected void validateNotificationTimes(LogBuilder proto, int life, int freshness,
+ protected void validateNotificationTimes(LogMaker proto, int life, int freshness,
int exposure) {
validateNotificationTimes(proto, life, freshness);
if (exposure != 0) {
@@ -89,7 +89,7 @@
}
}
- protected void validateNotificationTimes(LogBuilder proto, int life, int freshness) {
+ protected void validateNotificationTimes(LogMaker proto, int life, int freshness) {
if (life != 0) {
assertEquals(life,
proto.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS));
@@ -104,7 +104,7 @@
}
}
- protected void validateNotificationIdAndTag(LogBuilder proto, int id, String tag) {
+ protected void validateNotificationIdAndTag(LogMaker proto, int id, String tag) {
assertEquals(tag, proto.getTaggedData(MetricsEvent.NOTIFICATION_TAG));
assertEquals(id, proto.getTaggedData(MetricsEvent.NOTIFICATION_ID));
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java
index be918cd..b480e61 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java
@@ -19,7 +19,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class PowerScreenStateParserTest extends ParserTest {
@@ -60,7 +60,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(type, proto.getType());
assertEquals(MetricsEvent.SCREEN, proto.getCategory());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java
index 906ec04..def9628 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java
@@ -19,7 +19,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class StatusBarStateParserTest extends ParserTest {
@@ -64,7 +64,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(type, proto.getType());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
index 111909f..2ad76c1 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
@@ -23,7 +23,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class SysuiActionParserTest extends ParserTest {
@@ -47,7 +47,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
@@ -66,7 +66,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(packageName, proto.getPackageName());
@@ -117,7 +117,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(expectedValue, proto.getSubtype());
@@ -130,7 +130,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -141,7 +141,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -151,7 +151,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java
index 7853f77..e7a05d8 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java
@@ -15,16 +15,11 @@
*/
package com.android.internal.logging.legacy;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import android.metrics.LogMaker;
public class SysuiMultiActionParserTest extends ParserTest {
@@ -41,7 +36,7 @@
String counterName = "sheep";
int bucket = 13;
int value = 14;
- LogBuilder builder = new LogBuilder(category);
+ LogMaker builder = new LogMaker(category);
builder.setType(type);
builder.setSubtype(subtype);
builder.setPackageName(packageName);
@@ -57,7 +52,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(category, proto.getCategory());
assertEquals(type, proto.getType());
assertEquals(subtype, proto.getSubtype());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
index 1291508..64d69a4 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
@@ -23,7 +23,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class SysuiViewVisibilityParserTest extends ParserTest {
@@ -47,7 +47,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
@@ -64,7 +64,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType());
}
@@ -73,7 +73,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -84,7 +84,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -95,7 +95,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -105,7 +105,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml
index 2f18de0..62ef25b 100644
--- a/data/etc/framework-sysconfig.xml
+++ b/data/etc/framework-sysconfig.xml
@@ -21,5 +21,6 @@
delivery restrictions. -->
<allow-implicit-broadcast action="android.intent.action.SIM_STATE_CHANGED" />
<allow-implicit-broadcast action="android.intent.action.PACKAGE_CHANGED" />
+ <allow-implicit-broadcast action="android.intent.action.MEDIA_SCANNER_SCAN_FILE" />
</config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f1be4a9..c5961ab 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -303,6 +303,7 @@
<permission name="android.permission.BIND_APPWIDGET"/>
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+ <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.CONTROL_VPN"/>
<permission name="android.permission.DUMP"/>
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index cc5cc7b..8572345 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -21,11 +21,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
-import android.text.GraphicsOperations;
-import android.text.SpannableString;
-import android.text.SpannedString;
-import android.text.TextUtils;
+import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
@@ -501,8 +498,10 @@
* an error to call restore() more times than save() was called.
*/
public void restore() {
- boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();
- nRestore(mNativeCanvasWrapper, throwOnUnderflow);
+ if (!nRestore(mNativeCanvasWrapper)
+ && (!sCompatibilityRestore || !isHardwareAccelerated())) {
+ throw new IllegalStateException("Underflow in restore - more restores than saves");
+ }
}
/**
@@ -527,8 +526,16 @@
* @param saveCount The save level to restore to.
*/
public void restoreToCount(int saveCount) {
- boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();
- nRestoreToCount(mNativeCanvasWrapper, saveCount, throwOnUnderflow);
+ if (saveCount < 1) {
+ if (!sCompatibilityRestore || !isHardwareAccelerated()) {
+ // do nothing and throw without restoring
+ throw new IllegalArgumentException(
+ "Underflow in restoreToCount - more restores than saves");
+ }
+ // compat behavior - restore as far as possible
+ saveCount = 1;
+ }
+ nRestoreToCount(mNativeCanvasWrapper, saveCount);
}
/**
@@ -643,7 +650,7 @@
*/
@Deprecated
public void getMatrix(@NonNull Matrix ctm) {
- nGetCTM(mNativeCanvasWrapper, ctm.native_instance);
+ nGetMatrix(mNativeCanvasWrapper, ctm.native_instance);
}
/**
@@ -1059,79 +1066,66 @@
// ---------------- @FastNative -------------------
@FastNative
- private static native void nSetBitmap(long canvasHandle,
- Bitmap bitmap);
+ private static native void nSetBitmap(long canvasHandle, Bitmap bitmap);
+
@FastNative
+ private static native boolean nGetClipBounds(long nativeCanvas, Rect bounds);
+
+ // ---------------- @CriticalNative -------------------
+
+ @CriticalNative
private static native boolean nIsOpaque(long canvasHandle);
- @FastNative
+ @CriticalNative
private static native void nSetHighContrastText(long renderer, boolean highContrastText);
- @FastNative
+ @CriticalNative
private static native int nGetWidth(long canvasHandle);
- @FastNative
+ @CriticalNative
private static native int nGetHeight(long canvasHandle);
- @FastNative
+ @CriticalNative
private static native int nSave(long canvasHandle, int saveFlags);
- @FastNative
- private static native int nSaveLayer(long nativeCanvas, float l,
- float t, float r, float b,
- long nativePaint,
- int layerFlags);
- @FastNative
- private static native int nSaveLayerAlpha(long nativeCanvas, float l,
- float t, float r, float b,
- int alpha, int layerFlags);
- @FastNative
- private static native void nRestore(long canvasHandle, boolean tolerateUnderflow);
- @FastNative
- private static native void nRestoreToCount(long canvasHandle,
- int saveCount,
- boolean tolerateUnderflow);
- @FastNative
+ @CriticalNative
+ private static native int nSaveLayer(long nativeCanvas, float l, float t, float r, float b,
+ long nativePaint, int layerFlags);
+ @CriticalNative
+ private static native int nSaveLayerAlpha(long nativeCanvas, float l, float t, float r, float b,
+ int alpha, int layerFlags);
+ @CriticalNative
+ private static native boolean nRestore(long canvasHandle);
+ @CriticalNative
+ private static native void nRestoreToCount(long canvasHandle, int saveCount);
+ @CriticalNative
private static native int nGetSaveCount(long canvasHandle);
- @FastNative
- private static native void nTranslate(long canvasHandle,
- float dx, float dy);
- @FastNative
- private static native void nScale(long canvasHandle,
- float sx, float sy);
- @FastNative
+ @CriticalNative
+ private static native void nTranslate(long canvasHandle, float dx, float dy);
+ @CriticalNative
+ private static native void nScale(long canvasHandle, float sx, float sy);
+ @CriticalNative
private static native void nRotate(long canvasHandle, float degrees);
- @FastNative
- private static native void nSkew(long canvasHandle,
- float sx, float sy);
- @FastNative
- private static native void nConcat(long nativeCanvas,
- long nativeMatrix);
- @FastNative
- private static native void nSetMatrix(long nativeCanvas,
- long nativeMatrix);
- @FastNative
+ @CriticalNative
+ private static native void nSkew(long canvasHandle, float sx, float sy);
+ @CriticalNative
+ private static native void nConcat(long nativeCanvas, long nativeMatrix);
+ @CriticalNative
+ private static native void nSetMatrix(long nativeCanvas, long nativeMatrix);
+ @CriticalNative
private static native boolean nClipRect(long nativeCanvas,
- float left, float top,
- float right, float bottom,
- int regionOp);
- @FastNative
- private static native boolean nClipPath(long nativeCanvas,
- long nativePath,
- int regionOp);
- @FastNative
- private static native void nSetDrawFilter(long nativeCanvas,
- long nativeFilter);
- @FastNative
- private static native boolean nGetClipBounds(long nativeCanvas,
- Rect bounds);
- @FastNative
- private static native void nGetCTM(long nativeCanvas,
- long nativeMatrix);
- @FastNative
- private static native boolean nQuickReject(long nativeCanvas,
- long nativePath);
- @FastNative
- private static native boolean nQuickReject(long nativeCanvas,
- float left, float top,
- float right, float bottom);
+ float left, float top, float right, float bottom, int regionOp);
+ @CriticalNative
+ private static native boolean nClipPath(long nativeCanvas, long nativePath, int regionOp);
+ @CriticalNative
+ private static native void nSetDrawFilter(long nativeCanvas, long nativeFilter);
+ @CriticalNative
+ private static native void nGetMatrix(long nativeCanvas, long nativeMatrix);
+ @CriticalNative
+ private static native boolean nQuickReject(long nativeCanvas, long nativePath);
+ @CriticalNative
+ private static native boolean nQuickReject(long nativeCanvas, float left, float top,
+ float right, float bottom);
+
+
+ // ---------------- Draw Methods -------------------
/**
* <p>
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index cd5071e8..2c93c32 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,7 +17,9 @@
package android.graphics;
import android.content.res.AssetManager;
+import android.text.FontConfig;
import android.util.Log;
+import dalvik.annotation.optimization.CriticalNative;
import java.io.FileInputStream;
import java.io.IOException;
@@ -39,11 +41,11 @@
*/
public long mNativePtr;
+ // Points native font family builder. Must be zero after freezing this family.
+ private long mBuilderPtr;
+
public FontFamily() {
- mNativePtr = nCreateFamily(null, 0);
- if (mNativePtr == 0) {
- throw new IllegalStateException("error creating native FontFamily");
- }
+ mBuilderPtr = nInitBuilder(null, 0);
}
public FontFamily(String lang, String variant) {
@@ -53,49 +55,84 @@
} else if ("elegant".equals(variant)) {
varEnum = 2;
}
- mNativePtr = nCreateFamily(lang, varEnum);
- if (mNativePtr == 0) {
- throw new IllegalStateException("error creating native FontFamily");
+ mBuilderPtr = nInitBuilder(lang, varEnum);
+ }
+
+ public void freeze() {
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("This FontFamily is already frozen");
}
+ mNativePtr = nCreateFamily(mBuilderPtr);
+ mBuilderPtr = 0;
+ }
+
+ public void abortCreation() {
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("This FontFamily is already frozen or abandoned");
+ }
+ nAbort(mBuilderPtr);
+ mBuilderPtr = 0;
}
@Override
protected void finalize() throws Throwable {
try {
- nUnrefFamily(mNativePtr);
+ if (mNativePtr != 0) {
+ nUnrefFamily(mNativePtr);
+ }
+ if (mBuilderPtr != 0) {
+ nAbort(mBuilderPtr);
+ }
} finally {
super.finalize();
}
}
public boolean addFont(String path, int ttcIndex) {
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("Unable to call addFont after freezing.");
+ }
try (FileInputStream file = new FileInputStream(path)) {
FileChannel fileChannel = file.getChannel();
long fontSize = fileChannel.size();
ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
- return nAddFont(mNativePtr, fontBuffer, ttcIndex);
+ return nAddFont(mBuilderPtr, fontBuffer, ttcIndex);
} catch (IOException e) {
Log.e(TAG, "Error mapping font file " + path);
return false;
}
}
- public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontListParser.Axis> axes,
+ public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontConfig.Axis> axes,
int weight, boolean style) {
- return nAddFontWeightStyle(mNativePtr, font, ttcIndex, axes, weight, style);
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("Unable to call addFontWeightStyle after freezing.");
+ }
+ return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, axes, weight, style);
}
public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
boolean isAsset) {
- return nAddFontFromAssetManager(mNativePtr, mgr, path, cookie, isAsset);
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("Unable to call addFontFromAsset after freezing.");
+ }
+ return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset);
}
- private static native long nCreateFamily(String lang, int variant);
+ private static native long nInitBuilder(String lang, int variant);
+
+ @CriticalNative
+ private static native long nCreateFamily(long mBuilderPtr);
+
+ @CriticalNative
+ private static native void nAbort(long mBuilderPtr);
+
+ @CriticalNative
private static native void nUnrefFamily(long nativePtr);
- private static native boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex);
- private static native boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
- int ttcIndex, List<FontListParser.Axis> listOfAxis,
+ private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex);
+ private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
+ int ttcIndex, List<FontConfig.Axis> listOfAxis,
int weight, boolean isItalic);
- private static native boolean nAddFontFromAssetManager(long nativeFamily, AssetManager mgr,
+ private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr,
String path, int cookie, boolean isAsset);
}
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 9490436..4ec564a 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.text.FontConfig;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -36,61 +37,8 @@
*/
public class FontListParser {
- public static class Config {
- Config() {
- families = new ArrayList<Family>();
- aliases = new ArrayList<Alias>();
- }
- public List<Family> families;
- public List<Alias> aliases;
- }
-
- public static class Axis {
- Axis(int tag, float styleValue) {
- this.tag = tag;
- this.styleValue = styleValue;
- }
- public final int tag;
- public final float styleValue;
- }
-
- public static class Font {
- Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) {
- this.fontName = fontName;
- this.ttcIndex = ttcIndex;
- this.axes = axes;
- this.weight = weight;
- this.isItalic = isItalic;
- }
- public String fontName;
- public int ttcIndex;
- public final List<Axis> axes;
- public int weight;
- public boolean isItalic;
- }
-
- public static class Alias {
- public String name;
- public String toName;
- public int weight;
- }
-
- public static class Family {
- public Family(String name, List<Font> fonts, String lang, String variant) {
- this.name = name;
- this.fonts = fonts;
- this.lang = lang;
- this.variant = variant;
- }
-
- public String name;
- public List<Font> fonts;
- public String lang;
- public String variant;
- }
-
/* Parse fallback list (no names) */
- public static Config parse(InputStream in) throws XmlPullParserException, IOException {
+ public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
@@ -104,9 +52,9 @@
// Note that a well-formed variation contains a four-character tag and a float as styleValue,
// with spacers in between. The tag is enclosd either by double quotes or single quotes.
@VisibleForTesting
- public static Axis[] parseFontVariationSettings(String settings) {
+ public static FontConfig.Axis[] parseFontVariationSettings(String settings) {
String[] settingList = settings.split(",");
- ArrayList<Axis> axisList = new ArrayList<>();
+ ArrayList<FontConfig.Axis> axisList = new ArrayList<>();
settingLoop:
for (String setting : settingList) {
int pos = 0;
@@ -148,9 +96,9 @@
}
int tag = makeTag(tagString.charAt(0), tagString.charAt(1), tagString.charAt(2),
tagString.charAt(3));
- axisList.add(new Axis(tag, styleValue));
+ axisList.add(new FontConfig.Axis(tag, styleValue));
}
- return axisList.toArray(new Axis[axisList.size()]);
+ return axisList.toArray(new FontConfig.Axis[axisList.size()]);
}
@VisibleForTesting
@@ -162,17 +110,17 @@
return c == ' ' || c == '\r' || c == '\t' || c == '\n';
}
- private static Config readFamilies(XmlPullParser parser)
+ private static FontConfig readFamilies(XmlPullParser parser)
throws XmlPullParserException, IOException {
- Config config = new Config();
+ FontConfig config = new FontConfig();
parser.require(XmlPullParser.START_TAG, null, "familyset");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
- config.families.add(readFamily(parser));
+ config.getFamilies().add(readFamily(parser));
} else if (tag.equals("alias")) {
- config.aliases.add(readAlias(parser));
+ config.getAliases().add(readAlias(parser));
} else {
skip(parser);
}
@@ -180,12 +128,12 @@
return config;
}
- private static Family readFamily(XmlPullParser parser)
+ private static FontConfig.Family readFamily(XmlPullParser parser)
throws XmlPullParserException, IOException {
String name = parser.getAttributeValue(null, "name");
String lang = parser.getAttributeValue(null, "lang");
String variant = parser.getAttributeValue(null, "variant");
- List<Font> fonts = new ArrayList<Font>();
+ List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
@@ -195,18 +143,18 @@
skip(parser);
}
}
- return new Family(name, fonts, lang, variant);
+ return new FontConfig.Family(name, fonts, lang, variant);
}
/** Matches leading and trailing XML whitespace. */
private static final Pattern FILENAME_WHITESPACE_PATTERN =
Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
- private static Font readFont(XmlPullParser parser)
+ private static FontConfig.Font readFont(XmlPullParser parser)
throws XmlPullParserException, IOException {
String indexStr = parser.getAttributeValue(null, "index");
int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
- List<Axis> axes = new ArrayList<Axis>();
+ List<FontConfig.Axis> axes = new ArrayList<FontConfig.Axis>();
String weightStr = parser.getAttributeValue(null, "weight");
int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
@@ -225,7 +173,7 @@
}
String fullFilename = "/system/fonts/" +
FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
- return new Font(fullFilename, index, axes, weight, isItalic);
+ return new FontConfig.Font(fullFilename, index, axes, weight, isItalic);
}
/** The 'tag' attribute value is read as four character values between U+0020 and U+007E
@@ -239,7 +187,7 @@
private static final Pattern STYLE_VALUE_PATTERN =
Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))");
- private static Axis readAxis(XmlPullParser parser)
+ private static FontConfig.Axis readAxis(XmlPullParser parser)
throws XmlPullParserException, IOException {
int tag = 0;
String tagStr = parser.getAttributeValue(null, "tag");
@@ -258,22 +206,22 @@
}
skip(parser); // axis tag is empty, ignore any contents and consume end tag
- return new Axis(tag, styleValue);
+ return new FontConfig.Axis(tag, styleValue);
}
- private static Alias readAlias(XmlPullParser parser)
+ private static FontConfig.Alias readAlias(XmlPullParser parser)
throws XmlPullParserException, IOException {
- Alias alias = new Alias();
- alias.name = parser.getAttributeValue(null, "name");
- alias.toName = parser.getAttributeValue(null, "to");
+ String name = parser.getAttributeValue(null, "name");
+ String toName = parser.getAttributeValue(null, "to");
String weightStr = parser.getAttributeValue(null, "weight");
+ int weight;
if (weightStr == null) {
- alias.weight = 400;
+ weight = 400;
} else {
- alias.weight = Integer.parseInt(weightStr);
+ weight = Integer.parseInt(weightStr);
}
skip(parser); // alias tag is empty, ignore any contents and consume end tag
- return alias;
+ return new FontConfig.Alias(name, toName, weight);
}
private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index eebe538..38e8061 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -16,19 +16,34 @@
package android.graphics;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.res.AssetManager;
+import android.graphics.fonts.FontRequest;
+import android.graphics.fonts.FontResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+import android.provider.FontsContract;
+import android.text.FontConfig;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.LruCache;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
+
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
@@ -63,7 +78,11 @@
static Typeface[] sDefaults;
private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
- new LongSparseArray<SparseArray<Typeface>>(3);
+ new LongSparseArray<>(3);
+ @GuardedBy("sLock")
+ private static FontsContract sFontsContract;
+ @GuardedBy("sLock")
+ private static Handler mHandler;
/**
* Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
@@ -73,6 +92,7 @@
static Typeface sDefaultTypeface;
static Map<String, Typeface> sSystemFontMap;
static FontFamily[] sFallbackFonts;
+ private static final Object sLock = new Object();
static final String FONTS_CONFIG = "fonts.xml";
@@ -123,7 +143,7 @@
FontFamily fontFamily = new FontFamily();
if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */)) {
- FontFamily[] families = { fontFamily };
+ FontFamily[] families = {fontFamily};
typeface = createFromFamiliesWithDefault(families);
sDynamicTypefaceCache.put(key, typeface);
return typeface;
@@ -134,6 +154,139 @@
}
/**
+ * Create a typeface object given a font request. The font will be asynchronously fetched,
+ * therefore the result is delivered to the given callback. See {@link FontRequest}.
+ * Only one of the methods in callback will be invoked, depending on whether the request
+ * succeeds or fails. These calls will happen on the main thread.
+ * @param request A {@link FontRequest} object that identifies the provider and query for the
+ * request. May not be null.
+ * @param callback A callback that will be triggered when results are obtained. May not be null.
+ */
+ public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) {
+ synchronized (sLock) {
+ if (sFontsContract == null) {
+ sFontsContract = new FontsContract();
+ mHandler = new Handler();
+ }
+ final ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ public void onReceiveResult(int resultCode, Bundle resultData) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ receiveResult(request, callback, resultCode, resultData);
+ }
+ });
+ }
+ };
+ sFontsContract.getFont(request, receiver);
+ }
+ }
+
+ private static void receiveResult(FontRequest request, FontRequestCallback callback,
+ int resultCode, Bundle resultData) {
+ if (resultCode == FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND) {
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND);
+ return;
+ }
+ if (resultCode == FontsContract.RESULT_CODE_FONT_NOT_FOUND
+ || resultData == null) {
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
+ return;
+ }
+ List<FontResult> resultList =
+ resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
+ if (resultList == null || resultList.isEmpty()) {
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
+ return;
+ }
+ FontFamily fontFamily = new FontFamily();
+ for (int i = 0; i < resultList.size(); ++i) {
+ FontResult result = resultList.get(i);
+ ParcelFileDescriptor fd = result.getFileDescriptor();
+ if (fd == null) {
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
+ return;
+ }
+ try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) {
+ FileChannel fileChannel = is.getChannel();
+ long fontSize = fileChannel.size();
+ ByteBuffer fontBuffer = fileChannel.map(
+ FileChannel.MapMode.READ_ONLY, 0, fontSize);
+ int style = result.getStyle();
+ int weight = (style & BOLD) != 0 ? 700 : 400;
+ // TODO: this method should be
+ // create(fd, ttcIndex, fontVariationSettings, style).
+ if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(),
+ null, weight, (style & ITALIC) != 0)) {
+ Log.e(TAG, "Error creating font " + request.getQuery());
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
+ return;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading font " + request.getQuery(), e);
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
+ return;
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ }
+ fontFamily.freeze();
+ callback.onTypefaceRetrieved(Typeface.createFromFamiliesWithDefault(
+ new FontFamily[] {fontFamily}));
+ }
+
+ /**
+ * Interface used to receive asynchronously fetched typefaces.
+ */
+ public interface FontRequestCallback {
+ /**
+ * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
+ * provider was not found on the device.
+ */
+ int FAIL_REASON_PROVIDER_NOT_FOUND = 0;
+ /**
+ * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
+ * returned by the provider was not loaded properly.
+ */
+ int FAIL_REASON_FONT_LOAD_ERROR = 1;
+ /**
+ * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
+ * provider did not return any results for the given query.
+ */
+ int FAIL_REASON_FONT_NOT_FOUND = 2;
+
+ /** @hide */
+ @IntDef({FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
+ FAIL_REASON_FONT_NOT_FOUND})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface FontRequestFailReason {}
+
+ /**
+ * Called then a Typeface request done via {@link Typeface#create(FontRequest,
+ * FontRequestCallback)} is complete. Note that this method will not be called if
+ * {@link #onTypefaceRequestFailed(int)} is called instead.
+ * @param typeface The Typeface object retrieved.
+ */
+ void onTypefaceRetrieved(Typeface typeface);
+
+ /**
+ * Called when a Typeface request done via {@link Typeface#create(FontRequest,
+ * FontRequestCallback)} fails.
+ * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND},
+ * {@link #FAIL_REASON_FONT_NOT_FOUND} or
+ * {@link #FAIL_REASON_FONT_LOAD_ERROR}.
+ */
+ void onTypefaceRequestFailed(@FontRequestFailReason int reason);
+ }
+
+ /**
* 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.
* The resulting typeface object can be queried (getStyle()) to discover what
@@ -221,10 +374,13 @@
FontFamily fontFamily = new FontFamily();
if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */)) {
+ fontFamily.freeze();
FontFamily[] families = { fontFamily };
typeface = createFromFamiliesWithDefault(families);
sDynamicTypefaceCache.put(key, typeface);
return typeface;
+ } else {
+ fontFamily.abortCreation();
}
}
}
@@ -270,8 +426,11 @@
if (sFallbackFonts != null) {
FontFamily fontFamily = new FontFamily();
if (fontFamily.addFont(path, 0 /* ttcIndex */)) {
+ fontFamily.freeze();
FontFamily[] families = { fontFamily };
return createFromFamiliesWithDefault(families);
+ } else {
+ fontFamily.abortCreation();
}
}
throw new RuntimeException("Font not found " + path);
@@ -319,27 +478,28 @@
mStyle = nativeGetStyle(ni);
}
- private static FontFamily makeFamilyFromParsed(FontListParser.Family family,
+ private static FontFamily makeFamilyFromParsed(FontConfig.Family family,
Map<String, ByteBuffer> bufferForPath) {
- FontFamily fontFamily = new FontFamily(family.lang, family.variant);
- for (FontListParser.Font font : family.fonts) {
- ByteBuffer fontBuffer = bufferForPath.get(font.fontName);
+ FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
+ for (FontConfig.Font font : family.getFonts()) {
+ ByteBuffer fontBuffer = bufferForPath.get(font.getFontName());
if (fontBuffer == null) {
- try (FileInputStream file = new FileInputStream(font.fontName)) {
+ try (FileInputStream file = new FileInputStream(font.getFontName())) {
FileChannel fileChannel = file.getChannel();
long fontSize = fileChannel.size();
fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
- bufferForPath.put(font.fontName, fontBuffer);
+ bufferForPath.put(font.getFontName(), fontBuffer);
} catch (IOException e) {
- Log.e(TAG, "Error mapping font file " + font.fontName);
+ Log.e(TAG, "Error mapping font file " + font.getFontName());
continue;
}
}
- if (!fontFamily.addFontWeightStyle(fontBuffer, font.ttcIndex, font.axes,
- font.weight, font.isItalic)) {
- Log.e(TAG, "Error creating font " + font.fontName + "#" + font.ttcIndex);
+ if (!fontFamily.addFontWeightStyle(fontBuffer, font.getTtcIndex(), font.getAxes(),
+ font.getWeight(), font.isItalic())) {
+ Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
}
}
+ fontFamily.freeze();
return fontFamily;
}
@@ -354,16 +514,16 @@
File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
try {
FileInputStream fontsIn = new FileInputStream(configFilename);
- FontListParser.Config fontConfig = FontListParser.parse(fontsIn);
+ FontConfig fontConfig = FontListParser.parse(fontsIn);
Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
List<FontFamily> familyList = new ArrayList<FontFamily>();
// Note that the default typeface is always present in the fallback list;
// this is an enhancement from pre-Minikin behavior.
- for (int i = 0; i < fontConfig.families.size(); i++) {
- FontListParser.Family f = fontConfig.families.get(i);
- if (i == 0 || f.name == null) {
+ for (int i = 0; i < fontConfig.getFamilies().size(); i++) {
+ FontConfig.Family f = fontConfig.getFamilies().get(i);
+ if (i == 0 || f.getName() == null) {
familyList.add(makeFamilyFromParsed(f, bufferForPath));
}
}
@@ -371,10 +531,10 @@
setDefault(Typeface.createFromFamilies(sFallbackFonts));
Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
- for (int i = 0; i < fontConfig.families.size(); i++) {
+ for (int i = 0; i < fontConfig.getFamilies().size(); i++) {
Typeface typeface;
- FontListParser.Family f = fontConfig.families.get(i);
- if (f.name != null) {
+ FontConfig.Family f = fontConfig.getFamilies().get(i);
+ if (f.getName() != null) {
if (i == 0) {
// The first entry is the default typeface; no sense in
// duplicating the corresponding FontFamily.
@@ -384,17 +544,17 @@
FontFamily[] families = { fontFamily };
typeface = Typeface.createFromFamiliesWithDefault(families);
}
- systemFonts.put(f.name, typeface);
+ systemFonts.put(f.getName(), typeface);
}
}
- for (FontListParser.Alias alias : fontConfig.aliases) {
- Typeface base = systemFonts.get(alias.toName);
+ for (FontConfig.Alias alias : fontConfig.getAliases()) {
+ Typeface base = systemFonts.get(alias.getToName());
Typeface newFace = base;
- int weight = alias.weight;
+ int weight = alias.getWeight();
if (weight != 400) {
newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
}
- systemFonts.put(alias.name, newFace);
+ systemFonts.put(alias.getName(), newFace);
}
sSystemFontMap = systemFonts;
diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java
index 348af70d..6d0bbdf 100644
--- a/graphics/java/android/graphics/drawable/DrawableInflater.java
+++ b/graphics/java/android/graphics/drawable/DrawableInflater.java
@@ -147,6 +147,8 @@
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
+ case "maskable-icon":
+ return new MaskableIconDrawable();
case "color":
return new ColorDrawable();
case "shape":
diff --git a/graphics/java/android/graphics/drawable/MaskableIconDrawable.java b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
new file mode 100644
index 0000000..3467b1a
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
@@ -0,0 +1,1001 @@
+/*
+ * 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.graphics.drawable;
+
+import static android.graphics.drawable.Drawable.obtainAttributes;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.PathParser;
+
+import com.android.internal.R;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * This drawable supports two layers: foreground and background.
+ *
+ * <p>The layers are clipped when rendering using the mask path defined in the device configuration.
+ *
+ * <p>This class can also be created via XML inflation using <code><maskable-icon></code> tag
+ * in addition to dynamic creation.
+ */
+public class MaskableIconDrawable extends Drawable implements Drawable.Callback {
+
+ /**
+ * Mask path is defined inside device configuration in following dimension: [100 x 100]
+ */
+ public static final float MASK_SIZE = 100f;
+
+ /**
+ * The view port of the layers is smaller than their intrinsic width and height by this factor.
+ *
+ * It is part of the API contract that all four sides of the layers are padded so as to provide
+ * extra content to reveal within the clip path when performing affine transformations on the
+ * layers.
+ */
+ public static final float DEFAULT_VIEW_PORT_SCALE = 2f / 3f;
+
+ /**
+ * Clip path defined in {@link com.android.internal.R.string.config_icon_mask}.
+ */
+ private static Path sMask;
+
+ /**
+ * Scaled mask based on the view bounds.
+ */
+ private final Path mMask;
+ private final Matrix mMaskMatrix;
+ private final Region mTransparentRegion;
+
+ private Bitmap mMaskBitmap;
+
+ /**
+ * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
+ * background layer.
+ */
+ private static final int BACKGROUND_ID = 0;
+ private static final int FOREGROUND_ID = 1;
+
+ /**
+ * State variable that maintains the {@link ChildDrawable} array.
+ */
+ LayerState mLayerState;
+
+ private Shader mLayersShader;
+ private Bitmap mLayersBitmap;
+
+ private final Rect mTmpOutRect = new Rect();
+ private Rect mHotspotBounds;
+ private boolean mMutated;
+
+ private boolean mSuspendChildInvalidation;
+ private boolean mChildRequestedInvalidation;
+ private final Canvas mCanvas;
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+ Paint.FILTER_BITMAP_FLAG);
+
+ /**
+ * Constructor used for xml inflation.
+ */
+ MaskableIconDrawable() {
+ this((LayerState) null, null);
+ }
+
+ /**
+ * The one constructor to rule them all. This is called by all public
+ * constructors to set the state and initialize local properties.
+ */
+ MaskableIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
+ mLayerState = createConstantState(state, res);
+
+ if (sMask == null) {
+ sMask = PathParser.createPathFromPathData(
+ Resources.getSystem().getString(com.android.internal.R.string.config_icon_mask));
+ }
+ mMask = new Path();
+ mMaskMatrix = new Matrix();
+ mCanvas = new Canvas();
+ mTransparentRegion = new Region();
+ }
+
+ private ChildDrawable createChildDrawable(Drawable drawable) {
+ final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
+ layer.mDrawable = drawable;
+ layer.mDrawable.setCallback(this);
+ mLayerState.mChildrenChangingConfigurations |=
+ layer.mDrawable.getChangingConfigurations();
+ return layer;
+ }
+
+ LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
+ return new LayerState(state, this, res);
+ }
+
+ /**
+ * Constructor used to dynamically create this drawable.
+ *
+ * @param backgroundDrawable drawable that should be rendered in the background
+ * @param foregroundDrawable drawable that should be rendered in the foreground
+ */
+ public MaskableIconDrawable(Drawable backgroundDrawable,
+ Drawable foregroundDrawable) {
+ this((LayerState)null, null);
+ addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
+ addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
+ }
+
+ /**
+ * Sets the layer to the {@param index} and invalidates cache.
+ *
+ * @param index The index of the layer.
+ * @param layer The layer to add.
+ */
+ private void addLayer(int index, @NonNull ChildDrawable layer) {
+ mLayerState.mChildren[index] = layer;
+ mLayerState.invalidateCache();
+ }
+
+ @Override
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ super.inflate(r, parser, attrs, theme);
+
+ final LayerState state = mLayerState;
+ if (state == null) {
+ return;
+ }
+
+ // The density may have changed since the last update. This will
+ // apply scaling to any existing constant state properties.
+ final int density = Drawable.resolveDensity(r, 0);
+ state.setDensity(density);
+
+ final ChildDrawable[] array = state.mChildren;
+ for (int i = 0; i < state.mChildren.length; i++) {
+ final ChildDrawable layer = array[i];
+ layer.setDensity(density);
+ }
+ inflateLayers(r, parser, attrs, theme);
+ }
+
+ /**
+ * @return the mask path object used to clip the drawable
+ */
+ public Path getIconMask() {
+ return mMask;
+ }
+
+ /**
+ * @return the foreground drawable managed by this drawable
+ */
+ public Drawable getForeground() {
+ return mLayerState.mChildren[FOREGROUND_ID].mDrawable;
+ }
+
+ /**
+ * @return the background drawable managed by this drawable
+ */
+ public Drawable getBackground() {
+ return mLayerState.mChildren[BACKGROUND_ID].mDrawable;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ if (bounds.isEmpty()) {
+ return;
+ }
+ updateLayerBounds(bounds);
+ }
+
+ private void updateLayerBounds(Rect bounds) {
+ try {
+ suspendChildInvalidation();
+ updateLayerBoundsInternal(bounds);
+ updateMaskBoundsInternal(bounds);
+ } finally {
+ resumeChildInvalidation();
+ }
+ }
+
+ private void updateLayerBoundsInternal(Rect bounds) {
+ int cX = bounds.centerX();
+ int cY = bounds.centerY();
+
+ for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
+ int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
+ int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
+ final Rect outRect = mTmpOutRect;
+ outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
+
+ final ChildDrawable r = mLayerState.mChildren[i];
+ final Drawable d = r.mDrawable;
+ d.setBounds(outRect);
+ }
+ }
+
+ private void updateMaskBoundsInternal(Rect b) {
+ mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
+ sMask.transform(mMaskMatrix, mMask);
+
+ mMaskBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ALPHA_8);
+ mCanvas.setBitmap(mMaskBitmap);
+ mPaint.setShader(null);
+ mCanvas.drawPath(mMask, mPaint);
+
+ // reset everything that depends on the view bounds
+ mTransparentRegion.setEmpty();
+ mLayersShader = null;
+ mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mLayersShader == null) {
+ mCanvas.setBitmap(mLayersBitmap);
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = mLayerState.mChildren[i].mDrawable;
+ if (dr != null) {
+ dr.draw(mCanvas);
+ }
+ }
+ mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
+ mPaint.setShader(mLayersShader);
+ }
+ canvas.drawBitmap(mMaskBitmap, 0.0f, 0.0f, mPaint);
+ }
+
+ @Override
+ public void invalidateSelf() {
+ mLayersShader = null;
+ super.invalidateSelf();
+ }
+
+ @Override
+ public void getOutline(@NonNull Outline outline) {
+ outline.setConvexPath(mMask);
+ }
+
+ @Override
+ public @Nullable Region getTransparentRegion() {
+ if (mTransparentRegion.isEmpty()) {
+ mMask.toggleInverseFillType();
+ mTransparentRegion.set(getBounds());
+ mTransparentRegion.setPath(mMask, mTransparentRegion);
+ mMask.toggleInverseFillType();
+ }
+ return mTransparentRegion;
+ }
+
+ @Override
+ public void applyTheme(@NonNull Theme t) {
+ super.applyTheme(t);
+
+ final LayerState state = mLayerState;
+ if (state == null) {
+ return;
+ }
+
+ final int density = Drawable.resolveDensity(t.getResources(), 0);
+ state.setDensity(density);
+
+ final ChildDrawable[] array = state.mChildren;
+ for (int i = 0; i < state.N_CHILDREN; i++) {
+ final ChildDrawable layer = array[i];
+ layer.setDensity(density);
+
+ if (layer.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(
+ layer.mThemeAttrs, R.styleable.MaskableIconDrawableLayer);
+ updateLayerFromTypedArray(layer, a);
+ a.recycle();
+ }
+
+ final Drawable d = layer.mDrawable;
+ if (d != null && d.canApplyTheme()) {
+ d.applyTheme(t);
+
+ // Update cached mask of child changing configurations.
+ state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
+ }
+ }
+ }
+
+ /**
+ * Inflates child layers using the specified parser.
+ */
+ void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ final LayerState state = mLayerState;
+
+ final int innerDepth = parser.getDepth() + 1;
+ int type;
+ int depth;
+ int childIndex = 0;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (depth > innerDepth) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("background")) {
+ childIndex = BACKGROUND_ID;
+ } else if (tagName.equals("foreground")) {
+ childIndex = FOREGROUND_ID;
+ } else {
+ continue;
+ }
+
+ final ChildDrawable layer = new ChildDrawable(state.mDensity);
+ final TypedArray a = obtainAttributes(r, theme, attrs,
+ R.styleable.MaskableIconDrawableLayer);
+ updateLayerFromTypedArray(layer, a);
+ a.recycle();
+
+ // If the layer doesn't have a drawable or unresolved theme
+ // attribute for a drawable, attempt to parse one from the child
+ // element. If multiple child elements exist, we'll only use the
+ // first one.
+ if (layer.mDrawable == null && (layer.mThemeAttrs == null)) {
+ while ((type = parser.next()) == XmlPullParser.TEXT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException(parser.getPositionDescription()
+ + ": <foreground> or <background> tag requires a 'color' or 'drawable'"
+ + "attribute or child tag defining a drawable");
+ }
+
+ // We found a child drawable. Take ownership.
+ layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
+ layer.mDrawable.setCallback(this);
+ state.mChildrenChangingConfigurations |=
+ layer.mDrawable.getChangingConfigurations();
+ }
+ addLayer(childIndex, layer);
+ }
+ }
+
+ private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
+ final LayerState state = mLayerState;
+
+ // Account for any configuration changes.
+ state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
+
+ // Extract the theme attributes, if any.
+ 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
+ // we should clear the callback. We may have also integrated the
+ // drawable's changing configurations, but we don't have enough
+ // information to revert that change.
+ layer.mDrawable.setCallback(null);
+ }
+
+ // Take ownership of the new drawable.
+ layer.mDrawable = dr;
+ layer.mDrawable.setCallback(this);
+ state.mChildrenChangingConfigurations |=
+ layer.mDrawable.getChangingConfigurations();
+ }
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean isProjected() {
+ if (super.isProjected()) {
+ return true;
+ }
+
+ final ChildDrawable[] layers = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ if (layers[i].mDrawable.isProjected()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Temporarily suspends child invalidation.
+ *
+ * @see #resumeChildInvalidation()
+ */
+ private void suspendChildInvalidation() {
+ mSuspendChildInvalidation = true;
+ }
+
+ /**
+ * Resumes child invalidation after suspension, immediately performing an
+ * invalidation if one was requested by a child during suspension.
+ *
+ * @see #suspendChildInvalidation()
+ */
+ private void resumeChildInvalidation() {
+ mSuspendChildInvalidation = false;
+
+ if (mChildRequestedInvalidation) {
+ mChildRequestedInvalidation = false;
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void invalidateDrawable(@NonNull Drawable who) {
+ if (mSuspendChildInvalidation) {
+ mChildRequestedInvalidation = true;
+ } else {
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ unscheduleSelf(what);
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
+ }
+
+ @Override
+ public void setHotspot(float x, float y) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setHotspot(x, y);
+ }
+ }
+ }
+
+ @Override
+ public void setHotspotBounds(int left, int top, int right, int bottom) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setHotspotBounds(left, top, right, bottom);
+ }
+ }
+
+ if (mHotspotBounds == null) {
+ mHotspotBounds = new Rect(left, top, right, bottom);
+ } else {
+ mHotspotBounds.set(left, top, right, bottom);
+ }
+ }
+
+ @Override
+ public void getHotspotBounds(Rect outRect) {
+ if (mHotspotBounds != null) {
+ outRect.set(mHotspotBounds);
+ } else {
+ super.getHotspotBounds(outRect);
+ }
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ final boolean changed = super.setVisible(visible, restart);
+ final ChildDrawable[] array = mLayerState.mChildren;
+
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setVisible(visible, restart);
+ }
+ }
+
+ return changed;
+ }
+
+ @Override
+ public void setDither(boolean dither) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setDither(dither);
+ }
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setAlpha(alpha);
+ }
+ }
+ }
+
+ @Override
+ public int getAlpha() {
+ final Drawable dr = getFirstNonNullDrawable();
+ if (dr != null) {
+ return dr.getAlpha();
+ } else {
+ return super.getAlpha();
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setColorFilter(colorFilter);
+ }
+ }
+ }
+
+ @Override
+ public void setTintList(ColorStateList tint) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final int N = mLayerState.N_CHILDREN;
+ for (int i = 0; i < N; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setTintList(tint);
+ }
+ }
+ }
+
+ @Override
+ public void setTintMode(Mode tintMode) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final int N = mLayerState.N_CHILDREN;
+ for (int i = 0; i < N; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setTintMode(tintMode);
+ }
+ }
+ }
+
+ private Drawable getFirstNonNullDrawable() {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ return dr;
+ }
+ }
+ return null;
+ }
+
+ public void setOpacity(int opacity) {
+ mLayerState.mOpacityOverride = opacity;
+ }
+
+ @Override
+ public int getOpacity() {
+ if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
+ return mLayerState.mOpacityOverride;
+ }
+ return mLayerState.getOpacity();
+ }
+
+ @Override
+ public void setAutoMirrored(boolean mirrored) {
+ mLayerState.mAutoMirrored = mirrored;
+
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setAutoMirrored(mirrored);
+ }
+ }
+ }
+
+ @Override
+ public boolean isAutoMirrored() {
+ return mLayerState.mAutoMirrored;
+ }
+
+ @Override
+ public void jumpToCurrentState() {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.jumpToCurrentState();
+ }
+ }
+ }
+
+ @Override
+ public boolean isStateful() {
+ return mLayerState.isStateful();
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ boolean changed = false;
+
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null && dr.isStateful() && dr.setState(state)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ updateLayerBounds(getBounds());
+ }
+
+ return changed;
+ }
+
+ @Override
+ protected boolean onLevelChange(int level) {
+ boolean changed = false;
+
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null && dr.setLevel(level)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ updateLayerBounds(getBounds());
+ }
+
+ return changed;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE);
+ }
+
+ private int getMaxIntrinsicWidth() {
+ int width = -1;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final ChildDrawable r = mLayerState.mChildren[i];
+ final int w = r.mDrawable.getIntrinsicWidth();
+ if (w > width) {
+ width = w;
+ }
+ }
+ return width;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE);
+ }
+
+ private int getMaxIntrinsicHeight() {
+ int height = -1;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final ChildDrawable r = mLayerState.mChildren[i];
+ final int h = r.mDrawable.getIntrinsicHeight();
+ if (h > height) {
+ height = h;
+ }
+ }
+ return height;
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ if (mLayerState.canConstantState()) {
+ mLayerState.mChangingConfigurations = getChangingConfigurations();
+ return mLayerState;
+ }
+ return null;
+ }
+
+ @Override
+ public Drawable mutate() {
+ if (!mMutated && super.mutate() == this) {
+ mLayerState = createConstantState(mLayerState, null);
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = mLayerState.mChildren[i].mDrawable;
+ if (dr != null) {
+ dr.mutate();
+ }
+ }
+ mMutated = true;
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.clearMutated();
+ }
+ }
+ mMutated = false;
+ }
+
+ static class ChildDrawable {
+ public Drawable mDrawable;
+ public int[] mThemeAttrs;
+ public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+ ChildDrawable(int density) {
+ mDensity = density;
+ }
+
+ ChildDrawable(@NonNull ChildDrawable orig, @NonNull MaskableIconDrawable owner,
+ @Nullable Resources res) {
+
+ final Drawable dr = orig.mDrawable;
+ final Drawable clone;
+ if (dr != null) {
+ final ConstantState cs = dr.getConstantState();
+ if (cs == null) {
+ clone = dr;
+ } else if (res != null) {
+ clone = cs.newDrawable(res);
+ } else {
+ clone = cs.newDrawable();
+ }
+ clone.setCallback(owner);
+ clone.setBounds(dr.getBounds());
+ clone.setLevel(dr.getLevel());
+ } else {
+ clone = null;
+ }
+
+ mDrawable = clone;
+ mThemeAttrs = orig.mThemeAttrs;
+
+ mDensity = Drawable.resolveDensity(res, orig.mDensity);
+ }
+
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null
+ || (mDrawable != null && mDrawable.canApplyTheme());
+ }
+
+ public final void setDensity(int targetDensity) {
+ if (mDensity != targetDensity) {
+ mDensity = targetDensity;
+ }
+ }
+ }
+
+ static class LayerState extends ConstantState {
+ private int[] mThemeAttrs;
+
+ final static int N_CHILDREN = 2;
+ ChildDrawable[] mChildren;
+
+ int mDensity;
+ int mOpacityOverride = PixelFormat.UNKNOWN;
+
+ @Config int mChangingConfigurations;
+ @Config int mChildrenChangingConfigurations;
+
+ private boolean mCheckedOpacity;
+ private int mOpacity;
+
+ private boolean mCheckedStateful;
+ private boolean mIsStateful;
+ private boolean mAutoMirrored = false;
+
+ LayerState(@Nullable LayerState orig, @NonNull MaskableIconDrawable owner,
+ @Nullable Resources res) {
+ mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
+ mChildren = new ChildDrawable[N_CHILDREN];
+ if (orig != null) {
+ final ChildDrawable[] origChildDrawable = orig.mChildren;
+
+ mChangingConfigurations = orig.mChangingConfigurations;
+ mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
+
+ for (int i = 0; i < N_CHILDREN; i++) {
+ final ChildDrawable or = origChildDrawable[i];
+ mChildren[i] = new ChildDrawable(or, owner, res);
+ }
+
+ mCheckedOpacity = orig.mCheckedOpacity;
+ mOpacity = orig.mOpacity;
+ mCheckedStateful = orig.mCheckedStateful;
+ mIsStateful = orig.mIsStateful;
+ mAutoMirrored = orig.mAutoMirrored;
+ mThemeAttrs = orig.mThemeAttrs;
+ mOpacityOverride = orig.mOpacityOverride;
+ } else {
+ for (int i = 0; i < N_CHILDREN; i++) {
+ mChildren[i] = new ChildDrawable(mDensity);
+ }
+ }
+ }
+
+ public final void setDensity(int targetDensity) {
+ if (mDensity != targetDensity) {
+ mDensity = targetDensity;
+ }
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ if (mThemeAttrs != null || super.canApplyTheme()) {
+ return true;
+ }
+
+ final ChildDrawable[] array = mChildren;
+ for (int i = 0; i < N_CHILDREN; i++) {
+ final ChildDrawable layer = array[i];
+ if (layer.canApplyTheme()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new MaskableIconDrawable(this, null);
+ }
+
+ @Override
+ public Drawable newDrawable(@Nullable Resources res) {
+ return new MaskableIconDrawable(this, res);
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return mChangingConfigurations
+ | mChildrenChangingConfigurations;
+ }
+
+ public final int getOpacity() {
+ if (mCheckedOpacity) {
+ return mOpacity;
+ }
+
+ final ChildDrawable[] array = mChildren;
+
+ // Seek to the first non-null drawable.
+ int firstIndex = -1;
+ for (int i = 0; i < N_CHILDREN; i++) {
+ if (array[i].mDrawable != null) {
+ firstIndex = i;
+ break;
+ }
+ }
+
+ int op;
+ if (firstIndex >= 0) {
+ op = array[firstIndex].mDrawable.getOpacity();
+ } else {
+ op = PixelFormat.TRANSPARENT;
+ }
+
+ // Merge all remaining non-null drawables.
+ for (int i = firstIndex + 1; i < N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ op = Drawable.resolveOpacity(op, dr.getOpacity());
+ }
+ }
+
+ mOpacity = op;
+ mCheckedOpacity = true;
+ return op;
+ }
+
+ public final boolean isStateful() {
+ if (mCheckedStateful) {
+ return mIsStateful;
+ }
+
+ final ChildDrawable[] array = mChildren;
+ boolean isStateful = false;
+ for (int i = 0; i < N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null && dr.isStateful()) {
+ isStateful = true;
+ break;
+ }
+ }
+
+ mIsStateful = isStateful;
+ mCheckedStateful = true;
+ return isStateful;
+ }
+
+ public final boolean canConstantState() {
+ final ChildDrawable[] array = mChildren;
+ for (int i = 0; i < N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null && dr.getConstantState() == null) {
+ return false;
+ }
+ }
+
+ // Don't cache the result, this method is not called very often.
+ return true;
+ }
+
+ public void invalidateCache() {
+ mCheckedOpacity = false;
+ mCheckedStateful = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/graphics/java/android/graphics/fonts/FontRequest.java b/graphics/java/android/graphics/fonts/FontRequest.java
new file mode 100644
index 0000000..e50df6f
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontRequest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Information about a font request that may be sent to a Font Provider.
+ */
+public final class FontRequest implements Parcelable {
+ private final String mProviderAuthority;
+ private final String mQuery;
+
+ /**
+ * @param providerAuthority The authority of the Font Provider to be used for the request.
+ * @param query The query to be sent over to the provider. Refer to your font provider's
+ * documentation on the format of this string.
+ */
+ public FontRequest(@NonNull String providerAuthority, @NonNull String query) {
+ mProviderAuthority = Preconditions.checkNotNull(providerAuthority);
+ mQuery = Preconditions.checkNotNull(query);
+ }
+
+ /**
+ * Returns the selected font provider's authority. This tells the system what font provider
+ * it should request the font from.
+ */
+ public String getProviderAuthority() {
+ return mProviderAuthority;
+ }
+
+ /**
+ * Returns the query string. Refer to your font provider's documentation on the format of this
+ * string.
+ */
+ public String getQuery() {
+ return mQuery;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mProviderAuthority);
+ dest.writeString(mQuery);
+ }
+
+ private FontRequest(Parcel in) {
+ mProviderAuthority = in.readString();
+ mQuery = in.readString();
+ }
+
+ public static final Parcelable.Creator<FontRequest> CREATOR =
+ new Parcelable.Creator<FontRequest>() {
+ @Override
+ public FontRequest createFromParcel(Parcel in) {
+ return new FontRequest(in);
+ }
+
+ @Override
+ public FontRequest[] newArray(int size) {
+ return new FontRequest[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "FontRequest {"
+ + "mProviderAuthority: " + mProviderAuthority
+ + ", mQuery: " + mQuery
+ + "}";
+ }
+}
diff --git a/graphics/java/android/graphics/fonts/FontResult.java b/graphics/java/android/graphics/fonts/FontResult.java
new file mode 100644
index 0000000..3ef99fd
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontResult.java
@@ -0,0 +1,108 @@
+/*
+ * 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Paint;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Results returned from a Font Provider to the system.
+ * @hide
+ */
+public final class FontResult implements Parcelable {
+ private final ParcelFileDescriptor mFileDescriptor;
+ private final int mTtcIndex;
+ private final String mFontVariationSettings;
+ private final int mStyle;
+
+ /**
+ * Creates a FontResult with all the information needed about a provided font.
+ * @param fileDescriptor A ParcelFileDescriptor pointing to the font file. This shoult point to
+ * a real file or shared memory, as the client will mmap the given file
+ * descriptor. Pipes, sockets and other non-mmap-able file descriptors
+ * will fail to load in the client application.
+ * @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0.
+ * @param fontVariationSettings If providing a variation font, the settings for it. May be null.
+ * @param style One of {@link android.graphics.Typeface#NORMAL},
+ * {@link android.graphics.Typeface#BOLD}, {@link android.graphics.Typeface#ITALIC}
+ * or {@link android.graphics.Typeface#BOLD_ITALIC}
+ */
+ public FontResult(@NonNull ParcelFileDescriptor fileDescriptor, int ttcIndex,
+ @Nullable String fontVariationSettings, int style) {
+ mFileDescriptor = Preconditions.checkNotNull(fileDescriptor);
+ mTtcIndex = ttcIndex;
+ mFontVariationSettings = fontVariationSettings;
+ mStyle = style;
+ }
+
+ public ParcelFileDescriptor getFileDescriptor() {
+ return mFileDescriptor;
+ }
+
+ public int getTtcIndex() {
+ return mTtcIndex;
+ }
+
+ public String getFontVariationSettings() {
+ return mFontVariationSettings;
+ }
+
+ public int getStyle() {
+ return mStyle;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mFileDescriptor, flags);
+ dest.writeInt(mTtcIndex);
+ dest.writeString(mFontVariationSettings);
+ dest.writeInt(mStyle);
+ }
+
+ private FontResult(Parcel in) {
+ mFileDescriptor = in.readParcelable(null);
+ mTtcIndex = in.readInt();
+ mFontVariationSettings = in.readString();
+ mStyle = in.readInt();
+ }
+
+ public static final Parcelable.Creator<FontResult> CREATOR =
+ new Parcelable.Creator<FontResult>() {
+ @Override
+ public FontResult createFromParcel(Parcel in) {
+ return new FontResult(in);
+ }
+
+ @Override
+ public FontResult[] newArray(int size) {
+ return new FontResult[size];
+ }
+ };
+}
diff --git a/graphics/java/android/graphics/fonts/FontSpec.aidl b/graphics/java/android/graphics/fonts/FontSpec.aidl
new file mode 100644
index 0000000..dddea25
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontSpec.aidl
@@ -0,0 +1,18 @@
+/* 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.graphics.fonts;
+
+parcelable FontSpec;
\ No newline at end of file
diff --git a/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java b/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
index d046c11..2b4e6c2 100644
--- a/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
+++ b/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
@@ -17,50 +17,53 @@
package android.graphics;
import android.test.suitebuilder.annotation.SmallTest;
+import android.text.FontConfig;
import junit.framework.TestCase;
+import java.util.List;
+
public class VariationParserTest extends TestCase {
@SmallTest
public void testParseFontVariationSetting() {
int tag = FontListParser.makeTag('w', 'd', 't', 'h');
- FontListParser.Axis[] axis = FontListParser.parseFontVariationSettings("'wdth' 1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ FontConfig.Axis[] axis = FontListParser.parseFontVariationSettings("'wdth' 1");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("\"wdth\" 100");
- assertEquals(tag, axis[0].tag);
- assertEquals(100.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(100.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings(" 'wdth' 100");
- assertEquals(tag, axis[0].tag);
- assertEquals(100.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(100.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("\t'wdth' 0.5");
- assertEquals(tag, axis[0].tag);
- assertEquals(0.5f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(0.5f, axis[0].getStyleValue());
tag = FontListParser.makeTag('A', 'X', ' ', ' ');
axis = FontListParser.parseFontVariationSettings("'AX ' 1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("'AX '\t1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("'AX '\n1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("'AX '\r1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("'AX '\r\t\n 1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
// Test for invalid input
axis = FontListParser.parseFontVariationSettings("");
@@ -87,26 +90,26 @@
@SmallTest
public void testParseFontVariationStyleSettings() {
- FontListParser.Axis[] axis =
+ FontConfig.Axis[] axis =
FontListParser.parseFontVariationSettings("'wdth' 10,'AX '\r1");
int tag1 = FontListParser.makeTag('w', 'd', 't', 'h');
int tag2 = FontListParser.makeTag('A', 'X', ' ', ' ');
- assertEquals(tag1, axis[0].tag);
- assertEquals(10.0f, axis[0].styleValue);
- assertEquals(tag2, axis[1].tag);
- assertEquals(1.0f, axis[1].styleValue);
+ assertEquals(tag1, axis[0].getTag());
+ assertEquals(10.0f, axis[0].getStyleValue());
+ assertEquals(tag2, axis[1].getTag());
+ assertEquals(1.0f, axis[1].getStyleValue());
// Test only spacers are allowed before tag
axis = FontListParser.parseFontVariationSettings(" 'wdth' 10,ab'wdth' 1");
tag1 = FontListParser.makeTag('w', 'd', 't', 'h');
- assertEquals(tag1, axis[0].tag);
- assertEquals(10.0f, axis[0].styleValue);
+ assertEquals(tag1, axis[0].getTag());
+ assertEquals(10.0f, axis[0].getStyleValue());
assertEquals(1, axis.length);
}
@SmallTest
public void testInvalidTagCharacters() {
- FontListParser.Axis[] axis =
+ FontConfig.Axis[] axis =
FontListParser.parseFontVariationSettings("'\u0000\u0000\u0000\u0000' 10");
assertEquals(0, axis.length);
axis = FontListParser.parseFontVariationSettings("'\u3042\u3044\u3046\u3048' 10");
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
new file mode 100644
index 0000000..e36632a
--- /dev/null
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -0,0 +1,228 @@
+/*
+ * 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.security.keystore;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.Build;
+import android.os.Process;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterCertificateChain;
+import android.security.keymaster.KeymasterDefs;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Utilities for attesting the device's hardware identifiers.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public abstract class AttestationUtils {
+ private static AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ private AttestationUtils() {
+ }
+
+ /**
+ * Specifies that the device should attest its serial number. For use with
+ * {@link #attestDeviceIds}.
+ *
+ * @see #attestDeviceIds
+ */
+ public static final int ID_TYPE_SERIAL = 1;
+
+ /**
+ * Specifies that the device should attest its IMEIs. For use with {@link #attestDeviceIds}.
+ *
+ * @see #attestDeviceIds
+ */
+ public static final int ID_TYPE_IMEI = 2;
+
+ /**
+ * Specifies that the device should attest its MEIDs. For use with {@link #attestDeviceIds}.
+ *
+ * @see #attestDeviceIds
+ */
+ public static final int ID_TYPE_MEID = 3;
+
+ /**
+ * Performs attestation of the device's identifiers. This method returns a certificate chain
+ * whose first element contains the requested device identifiers in an extension. The device's
+ * brand, device and product are always also included in the attestation. If the device supports
+ * attestation in secure hardware, the chain will be rooted at a trustworthy CA key. Otherwise,
+ * the chain will be rooted at an untrusted certificate. See
+ * <a href="https://developer.android.com/training/articles/security-key-attestation.html">
+ * Key Attestation</a> for the format of the certificate extension.
+ * <p>
+ * Attestation will only be successful when all of the following are true:
+ * 1) The device has been set up to support device identifier attestation at the factory.
+ * 2) The user has not permanently disabled device identifier attestation.
+ * 3) You have permission to access the device identifiers you are requesting attestation for.
+ * <p>
+ * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is
+ * unsuccessful, the device may not support it in general or the user may have permanently
+ * disabled it.
+ * <p>
+ * The caller must hold {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
+ * permission.
+ *
+ * @param context the context to use for retrieving device identifiers.
+ * @param idTypes the types of device identifiers to attest.
+ * @param attestationChallenge a blob to include in the certificate alongside the device
+ * identifiers.
+ *
+ * @return a certificate chain containing the requested device identifiers in the first element
+ *
+ * @exception SecurityException if you are not permitted to obtain an attestation of the
+ * device's identifiers.
+ * @exception DeviceIdAttestationException if the attestation operation fails.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @NonNull public static X509Certificate[] attestDeviceIds(Context context,
+ @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
+ DeviceIdAttestationException {
+ // Check method arguments, retrieve requested device IDs and prepare attestation arguments.
+ if (idTypes == null) {
+ throw new NullPointerException("Missing id types");
+ }
+ if (attestationChallenge == null) {
+ throw new NullPointerException("Missing attestation challenge");
+ }
+ final KeymasterArguments attestArgs = new KeymasterArguments();
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, attestationChallenge);
+ final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+ for (int idType : idTypes) {
+ idTypesSet.add(idType);
+ }
+ TelephonyManager telephonyService = null;
+ if (idTypesSet.contains(ID_TYPE_IMEI) || idTypesSet.contains(ID_TYPE_MEID)) {
+ telephonyService = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (telephonyService == null) {
+ throw new DeviceIdAttestationException("Unable to access telephony service");
+ }
+ }
+ for (final Integer idType : idTypesSet) {
+ switch (idType) {
+ case ID_TYPE_SERIAL:
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+ Build.getSerial().getBytes(StandardCharsets.UTF_8));
+ break;
+ case ID_TYPE_IMEI: {
+ final String imei = telephonyService.getImei(0);
+ if (imei == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+ }
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+ imei.getBytes(StandardCharsets.UTF_8));
+ break;
+ }
+ case ID_TYPE_MEID: {
+ final String meid = telephonyService.getDeviceId();
+ if (meid == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve MEID");
+ }
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+ meid.getBytes(StandardCharsets.UTF_8));
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown device ID type " + idType);
+ }
+ }
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
+ Build.BRAND.getBytes(StandardCharsets.UTF_8));
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
+ Build.DEVICE.getBytes(StandardCharsets.UTF_8));
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
+ Build.PRODUCT.getBytes(StandardCharsets.UTF_8));
+
+ final KeyStore keyStore = KeyStore.getInstance();
+ final String keyAlias = "android_internal_device_id_attestation-"
+ + Process.myPid() + "-" + sSequenceNumber.incrementAndGet();
+ // Clear any leftover temporary key.
+ if (!keyStore.delete(keyAlias)) {
+ throw new DeviceIdAttestationException("Unable to remove temporary key");
+ }
+ try {
+ // Generate a temporary key.
+ final KeymasterArguments generateArgs = new KeymasterArguments();
+ generateArgs.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_VERIFY);
+ generateArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
+ generateArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ generateArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
+ generateArgs.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
+ generateArgs.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
+ generateArgs.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
+ RSAKeyGenParameterSpec.F4);
+ int errorCode = keyStore.generateKey(keyAlias, generateArgs, null, 0,
+ new KeyCharacteristics());
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new DeviceIdAttestationException("Unable to create temporary key",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+
+ // Perform attestation.
+ final KeymasterCertificateChain outChain = new KeymasterCertificateChain();
+ errorCode = keyStore.attestKey(keyAlias, attestArgs, outChain);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new DeviceIdAttestationException("Unable to perform attestation",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+
+ // Extract certificate chain.
+ final Collection<byte[]> rawChain = outChain.getCertificates();
+ if (rawChain.size() < 2) {
+ throw new DeviceIdAttestationException("Attestation certificate chain contained "
+ + rawChain.size() + " entries. At least two are required.");
+ }
+ final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream();
+ try {
+ for (final byte[] cert : rawChain) {
+ concatenatedRawChain.write(cert);
+ }
+ return CertificateFactory.getInstance("X.509").generateCertificates(
+ new ByteArrayInputStream(concatenatedRawChain.toByteArray()))
+ .toArray(new X509Certificate[0]);
+ } catch (Exception e) {
+ throw new DeviceIdAttestationException("Unable to construct certificate chain", e);
+ }
+ } finally {
+ // Remove temporary key.
+ keyStore.delete(keyAlias);
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore/DeviceIdAttestationException.java b/keystore/java/android/security/keystore/DeviceIdAttestationException.java
new file mode 100644
index 0000000..e18d193
--- /dev/null
+++ b/keystore/java/android/security/keystore/DeviceIdAttestationException.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 android.security.keystore;
+
+/**
+ * Thrown when {@link AttestationUtils} is unable to attest the given device ids.
+ *
+ * @hide
+ */
+public class DeviceIdAttestationException extends Exception {
+ /**
+ * Constructs a new {@code DeviceIdAttestationException} with the current stack trace and the
+ * specified detail message.
+ *
+ * @param detailMessage the detail message for this exception.
+ */
+ public DeviceIdAttestationException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ /**
+ * Constructs a new {@code DeviceIdAttestationException} with the current stack trace, the
+ * specified detail message and the specified cause.
+ *
+ * @param message the detail message for this exception.
+ * @param cause the cause of this exception, may be {@code null}.
+ */
+ public DeviceIdAttestationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index e068900..acacd76 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -288,22 +288,34 @@
{
AutoMutex _l(mLock);
const String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) };
- ResTable tables[2];
+ Asset* assets[2] = {NULL, NULL};
+ bool ret = false;
+ {
+ ResTable tables[2];
- for (int i = 0; i < 2; ++i) {
- asset_path ap;
- ap.type = kFileTypeRegular;
- ap.path = paths[i];
- Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
- if (ass == NULL) {
- ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
- return false;
+ for (int i = 0; i < 2; ++i) {
+ asset_path ap;
+ ap.type = kFileTypeRegular;
+ ap.path = paths[i];
+ assets[i] = openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER, ap);
+ if (assets[i] == NULL) {
+ ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
+ goto exit;
+ }
+ if (tables[i].add(assets[i]) != NO_ERROR) {
+ ALOGW("failed to add %s to resource table", paths[i].string());
+ goto exit;
+ }
}
- tables[i].add(ass);
+ ret = tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
+ targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
}
- return tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
- targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
+exit:
+ delete assets[0];
+ delete assets[1];
+ return ret;
}
bool AssetManager::addDefaultAssets()
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index a4bcc62..763a178 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1907,7 +1907,7 @@
if (diff != 0) return diff;
diff = (int32_t)(screenLayout2 - o.screenLayout2);
if (diff != 0) return diff;
- diff = (int32_t)(colorimetry - o.colorimetry);
+ diff = (int32_t)(colorMode - o.colorMode);
if (diff != 0) return diff;
diff = (int32_t)(uiMode - o.uiMode);
if (diff != 0) return diff;
@@ -1969,8 +1969,8 @@
if (screenLayout2 != o.screenLayout2) {
return screenLayout2 < o.screenLayout2 ? -1 : 1;
}
- if (colorimetry != o.colorimetry) {
- return colorimetry < o.colorimetry ? -1 : 1;
+ if (colorMode != o.colorMode) {
+ return colorMode < o.colorMode ? -1 : 1;
}
if (uiMode != o.uiMode) {
return uiMode < o.uiMode ? -1 : 1;
@@ -1997,8 +1997,8 @@
if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) diffs |= CONFIG_LAYOUTDIR;
if ((screenLayout & ~MASK_LAYOUTDIR) != (o.screenLayout & ~MASK_LAYOUTDIR)) diffs |= CONFIG_SCREEN_LAYOUT;
if ((screenLayout2 & MASK_SCREENROUND) != (o.screenLayout2 & MASK_SCREENROUND)) diffs |= CONFIG_SCREEN_ROUND;
- if ((colorimetry & MASK_WIDE_COLOR_GAMUT) != (o.colorimetry & MASK_WIDE_COLOR_GAMUT)) diffs |= CONFIG_COLORIMETRY;
- if ((colorimetry & MASK_HDR) != (o.colorimetry & MASK_HDR)) diffs |= CONFIG_COLORIMETRY;
+ if ((colorMode & MASK_WIDE_COLOR_GAMUT) != (o.colorMode & MASK_WIDE_COLOR_GAMUT)) diffs |= CONFIG_COLOR_MODE;
+ if ((colorMode & MASK_HDR) != (o.colorMode & MASK_HDR)) diffs |= CONFIG_COLOR_MODE;
if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
@@ -2110,14 +2110,14 @@
}
}
- if (colorimetry || o.colorimetry) {
- if (((colorimetry^o.colorimetry) & MASK_HDR) != 0) {
- if (!(colorimetry & MASK_HDR)) return false;
- if (!(o.colorimetry & MASK_HDR)) return true;
+ if (colorMode || o.colorMode) {
+ if (((colorMode^o.colorMode) & MASK_HDR) != 0) {
+ if (!(colorMode & MASK_HDR)) return false;
+ if (!(o.colorMode & MASK_HDR)) return true;
}
- if (((colorimetry^o.colorimetry) & MASK_WIDE_COLOR_GAMUT) != 0) {
- if (!(colorimetry & MASK_WIDE_COLOR_GAMUT)) return false;
- if (!(o.colorimetry & MASK_WIDE_COLOR_GAMUT)) return true;
+ if (((colorMode^o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0) {
+ if (!(colorMode & MASK_WIDE_COLOR_GAMUT)) return false;
+ if (!(o.colorMode & MASK_WIDE_COLOR_GAMUT)) return true;
}
}
@@ -2408,14 +2408,14 @@
}
}
- if (colorimetry || o.colorimetry) {
- if (((colorimetry^o.colorimetry) & MASK_WIDE_COLOR_GAMUT) != 0 &&
- (requested->colorimetry & MASK_WIDE_COLOR_GAMUT)) {
- return colorimetry & MASK_WIDE_COLOR_GAMUT;
+ if (colorMode || o.colorMode) {
+ if (((colorMode^o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0 &&
+ (requested->colorMode & MASK_WIDE_COLOR_GAMUT)) {
+ return colorMode & MASK_WIDE_COLOR_GAMUT;
}
- if (((colorimetry^o.colorimetry) & MASK_HDR) != 0 &&
- (requested->colorimetry & MASK_HDR)) {
- return colorimetry & MASK_HDR;
+ if (((colorMode^o.colorMode) & MASK_HDR) != 0 &&
+ (requested->colorMode & MASK_HDR)) {
+ return colorMode & MASK_HDR;
}
}
@@ -2669,14 +2669,14 @@
return false;
}
- const int hdr = colorimetry & MASK_HDR;
- const int setHdr = settings.colorimetry & MASK_HDR;
+ const int hdr = colorMode & MASK_HDR;
+ const int setHdr = settings.colorMode & MASK_HDR;
if (hdr != 0 && hdr != setHdr) {
return false;
}
- const int wideColorGamut = colorimetry & MASK_WIDE_COLOR_GAMUT;
- const int setWideColorGamut = settings.colorimetry & MASK_WIDE_COLOR_GAMUT;
+ const int wideColorGamut = colorMode & MASK_WIDE_COLOR_GAMUT;
+ const int setWideColorGamut = settings.colorMode & MASK_WIDE_COLOR_GAMUT;
if (wideColorGamut != 0 && wideColorGamut != setWideColorGamut) {
return false;
}
@@ -3000,9 +3000,9 @@
break;
}
}
- if ((colorimetry&MASK_HDR) != 0) {
+ if ((colorMode&MASK_HDR) != 0) {
if (res.size() > 0) res.append("-");
- switch (colorimetry&MASK_HDR) {
+ switch (colorMode&MASK_HDR) {
case ResTable_config::HDR_NO:
res.append("lowdr");
break;
@@ -3010,13 +3010,13 @@
res.append("highdr");
break;
default:
- res.appendFormat("hdr=%d", dtohs(colorimetry&MASK_HDR));
+ res.appendFormat("hdr=%d", dtohs(colorMode&MASK_HDR));
break;
}
}
- if ((colorimetry&MASK_WIDE_COLOR_GAMUT) != 0) {
+ if ((colorMode&MASK_WIDE_COLOR_GAMUT) != 0) {
if (res.size() > 0) res.append("-");
- switch (colorimetry&MASK_WIDE_COLOR_GAMUT) {
+ switch (colorMode&MASK_WIDE_COLOR_GAMUT) {
case ResTable_config::WIDE_COLOR_GAMUT_NO:
res.append("nowidecg");
break;
@@ -3024,7 +3024,7 @@
res.append("widecg");
break;
default:
- res.appendFormat("wideColorGamut=%d", dtohs(colorimetry&MASK_WIDE_COLOR_GAMUT));
+ res.appendFormat("wideColorGamut=%d", dtohs(colorMode&MASK_WIDE_COLOR_GAMUT));
break;
}
}
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 1e4aee9..86ab123 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1147,25 +1147,25 @@
};
enum {
- // colorimetry bits for wide-color gamut/narrow-color gamut.
+ // colorMode bits for wide-color gamut/narrow-color gamut.
MASK_WIDE_COLOR_GAMUT = 0x03,
WIDE_COLOR_GAMUT_ANY = ACONFIGURATION_WIDE_COLOR_GAMUT_ANY,
WIDE_COLOR_GAMUT_NO = ACONFIGURATION_WIDE_COLOR_GAMUT_NO,
WIDE_COLOR_GAMUT_YES = ACONFIGURATION_WIDE_COLOR_GAMUT_YES,
- // colorimetry bits for HDR/LDR.
+ // colorMode bits for HDR/LDR.
MASK_HDR = 0x0c,
- SHIFT_COLORIMETRY_HDR = 2,
- HDR_ANY = ACONFIGURATION_HDR_ANY << SHIFT_COLORIMETRY_HDR,
- HDR_NO = ACONFIGURATION_HDR_NO << SHIFT_COLORIMETRY_HDR,
- HDR_YES = ACONFIGURATION_HDR_YES << SHIFT_COLORIMETRY_HDR,
+ SHIFT_COLOR_MODE_HDR = 2,
+ HDR_ANY = ACONFIGURATION_HDR_ANY << SHIFT_COLOR_MODE_HDR,
+ HDR_NO = ACONFIGURATION_HDR_NO << SHIFT_COLOR_MODE_HDR,
+ HDR_YES = ACONFIGURATION_HDR_YES << SHIFT_COLOR_MODE_HDR,
};
// An extension of screenConfig.
union {
struct {
uint8_t screenLayout2; // Contains round/notround qualifier.
- uint8_t colorimetry; // Wide-gamut, HDR, etc.
+ uint8_t colorMode; // Wide-gamut, HDR, etc.
uint16_t screenConfigPad2; // Reserved padding.
};
uint32_t screenConfig2;
@@ -1208,7 +1208,7 @@
CONFIG_UI_MODE = ACONFIGURATION_UI_MODE,
CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR,
CONFIG_SCREEN_ROUND = ACONFIGURATION_SCREEN_ROUND,
- CONFIG_COLORIMETRY = ACONFIGURATION_COLORIMETRY,
+ CONFIG_COLOR_MODE = ACONFIGURATION_COLOR_MODE,
};
// Compare two configuration, returning CONFIG_* flags set for each value
diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp
index 3e5aca7..b54915f 100644
--- a/libs/androidfw/tests/Config_test.cpp
+++ b/libs/androidfw/tests/Config_test.cpp
@@ -187,9 +187,9 @@
memset(&defaultConfig, 0, sizeof(defaultConfig));
ResTable_config wideGamutConfig = defaultConfig;
- wideGamutConfig.colorimetry = ResTable_config::WIDE_COLOR_GAMUT_YES;
+ wideGamutConfig.colorMode = ResTable_config::WIDE_COLOR_GAMUT_YES;
- EXPECT_EQ(defaultConfig.diff(wideGamutConfig), ResTable_config::CONFIG_COLORIMETRY);
+ EXPECT_EQ(defaultConfig.diff(wideGamutConfig), ResTable_config::CONFIG_COLOR_MODE);
}
TEST(ConfigTest, ScreenIsHdr) {
@@ -197,9 +197,9 @@
memset(&defaultConfig, 0, sizeof(defaultConfig));
ResTable_config hdrConfig = defaultConfig;
- hdrConfig.colorimetry = ResTable_config::HDR_YES;
+ hdrConfig.colorMode = ResTable_config::HDR_YES;
- EXPECT_EQ(defaultConfig.diff(hdrConfig), ResTable_config::CONFIG_COLORIMETRY);
+ EXPECT_EQ(defaultConfig.diff(hdrConfig), ResTable_config::CONFIG_COLOR_MODE);
}
} // namespace android.
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index a53a55a..1d8b021 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -180,16 +180,18 @@
}
}
- if (!backdrop.isEmpty()) {
- // content node translation to catch up with backdrop
- float dx = contentDrawBounds.left - backdrop.left;
- float dy = contentDrawBounds.top - backdrop.top;
+ if (!nodes[1]->nothingToDraw()) {
+ if (!backdrop.isEmpty()) {
+ // content node translation to catch up with backdrop
+ float dx = contentDrawBounds.left - backdrop.left;
+ float dy = contentDrawBounds.top - backdrop.top;
- Rect contentLocalClip = backdrop;
- contentLocalClip.translate(dx, dy);
- deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]);
- } else {
- deferRenderNode(*nodes[1]);
+ Rect contentLocalClip = backdrop;
+ contentLocalClip.translate(dx, dy);
+ deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]);
+ } else {
+ deferRenderNode(*nodes[1]);
+ }
}
// remaining overlay nodes, simply defer
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index ca43156..9041b44 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -130,9 +130,9 @@
sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(fontData.release());
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
- minikin::FontFamily* family = new minikin::FontFamily();
minikin::MinikinFont* font = new MinikinFontSkia(std::move(typeface), data, st.st_size, 0);
- family->addFont(font);
+ minikin::FontFamily* family = new minikin::FontFamily(
+ std::vector<minikin::Font>({ minikin::Font(font, minikin::FontStyle()) }));
font->Unref();
std::vector<minikin::FontFamily*> typefaces = { family };
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 275ce16..79daa3f 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -193,10 +193,9 @@
}
SkRect TestUtils::getClipBounds(const SkCanvas* canvas) {
- SkClipStack::BoundsType boundType;
- SkRect clipBounds;
- canvas->getClipStack()->getBounds(&clipBounds, &boundType);
- return clipBounds;
+ SkIRect bounds;
+ (void)canvas->getClipDeviceBounds(&bounds);
+ return SkRect::Make(bounds);
}
SkRect TestUtils::getLocalClipBounds(const SkCanvas* canvas) {
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 6f3ed9c..5a2791c 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -311,13 +311,32 @@
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
}
- FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600,
- sLightGeometry, Caches::getInstance());
- frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
+ {
+ FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
- DeferRenderNodeSceneTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
+ DeferRenderNodeSceneTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+ }
+
+ for (auto& node : nodes) {
+ EXPECT_FALSE(node->nothingToDraw());
+ node->setStagingDisplayList(nullptr, nullptr);
+ node->destroyHardwareResources(nullptr);
+ EXPECT_TRUE(node->nothingToDraw());
+ }
+
+ {
+ // Validate no crashes if any nodes are missing DisplayLists
+ FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
+
+ FailRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ }
}
RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, empty_noFbo0) {
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index f5ff058..2f1eae3 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -865,10 +865,8 @@
void onDrawPaint(const SkPaint&) {
switch (mDrawCounter++) {
case 0:
- // While this mirrors FrameBuilder::colorOp_unbounded, this value is different
- // because there is no root (root is clipped in SkiaPipeline::renderFrame).
- // SkiaPipeline.clipped and clip_replace verify the root clip.
- EXPECT_TRUE(TestUtils::getClipBounds(this).isEmpty());
+ EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT),
+ TestUtils::getClipBounds(this));
break;
case 1:
EXPECT_EQ(SkRect::MakeWH(10, 10), TestUtils::getClipBounds(this));
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 0b8c2a9..5bb0b6d 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -170,9 +170,7 @@
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
// 1 Overdraw, should be blue blended onto white.
- renderNodes.push_back(whiteNode); //this is the "content" node
- renderNodes.push_back(whiteNode); //the "content" node above does not cause an overdraw, because
- //it clips the first "background" node
+ renderNodes.push_back(whiteNode);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0d0ff);
diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
index 92d9d3d..95c6ed6 100644
--- a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
+++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
@@ -87,14 +87,7 @@
testProperty([](RenderProperties& properties) {
properties.mutableRevealClip().set(true, 50, 50, 25);
}, [](const SkCanvas& canvas) {
- SkClipStack::Iter it(*canvas.getClipStack(), SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::Element *top = it.next();
- ASSERT_NE(nullptr, top);
- SkPath clip;
- top->asPath(&clip);
- SkRect rect;
- EXPECT_TRUE(clip.isOval(&rect));
- EXPECT_EQ(SkRect::MakeLTRB(25, 25, 75, 75), rect);
+ EXPECT_EQ(SkRect::MakeLTRB(25, 25, 75, 75), TestUtils::getClipBounds(&canvas));
});
}
@@ -103,14 +96,7 @@
properties.mutableOutline().setShouldClip(true);
properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
}, [](const SkCanvas& canvas) {
- SkClipStack::Iter it(*canvas.getClipStack(), SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::Element *top = it.next();
- ASSERT_NE(nullptr, top);
- SkPath clip;
- top->asPath(&clip);
- SkRRect rrect;
- EXPECT_TRUE(clip.isRRect(&rrect));
- EXPECT_EQ(SkRRect::MakeRectXY(SkRect::MakeLTRB(10, 20, 30, 40), 5.0f, 5.0f), rrect);
+ EXPECT_EQ(SkRect::MakeLTRB(10, 20, 30, 40), TestUtils::getClipBounds(&canvas));
});
}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index ce23176..aac9727 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -660,8 +660,14 @@
/**
* Gets the carrier frequency of the tracked signal.
*
- * <p>For example it can be the GPS L1 = 1.57542e9 Hz, or L2, L5, varying GLO channels, etc. If
- * the field is not set, it is the primary common use frequency, e.g. L1 for GPS.
+ * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz,
+ * L5 = 1176.45 MHz, varying GLO channels, etc. If the field is not set, it is the primary
+ * common use central frequency, e.g. L1 = 1575.45 MHz for GPS.
+ *
+ * For an L1, L5 receiver tracking a satellite on L1 and L5 at the same time, two raw
+ * measurement objects will be reported for this same satellite, in one of the measurement
+ * objects, all the values related to L1 will be filled, and in the other all of the values
+ * related to L5 will be filled.
*
* <p>The value is only available if {@link #hasCarrierFrequencyHz()} is {@code true}.
*/
@@ -882,7 +888,7 @@
}
/**
- * Returns {@code true} if {@link #getAutomaticGainControlLevelInDb()} is available,
+ * Returns {@code true} if {@link #getAutomaticGainControlLevelInDb()} is available,
* {@code false} otherwise.
*/
public boolean hasAutomaticGainControlLevelInDb() {
@@ -892,11 +898,12 @@
/**
* Gets the Automatic Gain Control level in dB.
*
- * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal to
- * minimize the quantization losses. The AGC level may be used to indicate potential
- * interference. When AGC is at a nominal level, this value must be set as 0. Higher gain
- * (and/or lower input power) shall be output as a positive number. Hence in cases of strong
- * jamming, in the band of this signal, this value will go more negative.
+ * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC
+ * level may be used to indicate potential interference. When AGC is at a nominal level, this
+ * value must be set as 0. Higher gain (and/or lower input power) shall be output as a positive
+ * number. Hence in cases of strong jamming, in the band of this signal, this value will go more
+ * negative.
+ *
* <p>Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW
* components) may also affect the typical output of of this value on any given hardware design
* in an open sky test - the important aspect of this output is that changes in this value are
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 78dbc71..e90a174 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -231,8 +231,13 @@
/**
* Gets the carrier frequency of the signal tracked.
*
- * For example it can be the GPS L1 = 1.57542e9 Hz, or L2, L5, varying GLO channels, etc. If
- * the field is not set, it is the primary common use frequency, e.g. L1 for GPS.
+ * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz,
+ * L5 = 1176.45 MHz, varying GLO channels, etc. If the field is not set, it is the primary
+ * common use central frequency, e.g. L1 = 1575.45 MHz for GPS.
+ *
+ * For an L1, L5 receiver tracking a satellite on L1 and L5 at the same time, two measurements
+ * will be reported for this same satellite, in one all the values related to L1 will be filled,
+ * and in the other all of the values related to L5 will be filled.
*
* <p>The value is only available if {@link #hasCarrierFrequency(int satIndex)} is {@code true}.
*/
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7c60385..bcae71c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -730,61 +730,6 @@
}
/**
- * @hide
- */
- public void handleKeyDown(KeyEvent event, int stream) {
- int keyCode = event.getKeyCode();
- switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- /*
- * Adjust the volume in on key down since it is more
- * responsive to the user.
- */
- adjustSuggestedStreamVolume(
- keyCode == KeyEvent.KEYCODE_VOLUME_UP
- ? ADJUST_RAISE
- : ADJUST_LOWER,
- stream,
- FLAG_SHOW_UI | FLAG_VIBRATE);
- break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- if (event.getRepeatCount() == 0) {
- MediaSessionLegacyHelper.getHelper(getContext())
- .sendVolumeKeyEvent(event, false);
- }
- break;
- }
- }
-
- /**
- * @hide
- */
- public void handleKeyUp(KeyEvent event, int stream) {
- int keyCode = event.getKeyCode();
- switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- /*
- * Play a sound. This is done on key up since we don't want the
- * sound to play when a user holds down volume down to mute.
- */
- if (mUseVolumeKeySounds) {
- adjustSuggestedStreamVolume(
- ADJUST_SAME,
- stream,
- FLAG_PLAY_SOUND);
- }
- mVolumeKeyUpTime = SystemClock.uptimeMillis();
- break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- MediaSessionLegacyHelper.getHelper(getContext())
- .sendVolumeKeyEvent(event, false);
- break;
- }
- }
-
- /**
* Indicates if the device implements a fixed volume policy.
* <p>Some devices may not have volume control and may operate at a fixed volume,
* and may not enable muting or changing the volume of audio streams.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 432e77c..a76a328 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -192,5 +192,7 @@
oneway void releasePlayer(in int piid);
+ void disableRingtoneSync();
+
// WARNING: read warning at top of file, it is recommended to add new methods at the end
}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 7614999..8a1027b 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -33,8 +33,11 @@
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Environment;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.MediaStore;
@@ -850,6 +853,18 @@
public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
final ContentResolver resolver = context.getContentResolver();
+ if (Settings.Secure.getString(resolver, Settings.Secure.SYNC_PARENT_SOUNDS).equals("1")) {
+ // Sync is enabled, so we need to disable it
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ IAudioService audioService = IAudioService.Stub.asInterface(b);
+ try {
+ audioService.disableRingtoneSync();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to disable ringtone sync.");
+ return;
+ }
+ }
+
String setting = getSettingForType(type);
if (setting == null) return;
if(!isInternalRingtoneUri(ringtoneUri)) {
diff --git a/media/java/android/media/session/IOnMediaKeyListener.aidl b/media/java/android/media/session/IOnMediaKeyListener.aidl
new file mode 100644
index 0000000..7752357
--- /dev/null
+++ b/media/java/android/media/session/IOnMediaKeyListener.aidl
@@ -0,0 +1,28 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.os.ResultReceiver;
+import android.view.KeyEvent;
+
+/**
+ * Listener to handle media key.
+ * @hide
+ */
+interface IOnMediaKeyListener {
+ void onMediaKey(in KeyEvent event, in ResultReceiver result);
+}
+
diff --git a/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl b/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl
new file mode 100644
index 0000000..07b8347
--- /dev/null
+++ b/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl
@@ -0,0 +1,27 @@
+/* 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.media.session;
+
+import android.view.KeyEvent;
+
+/**
+ * Listener to handle volume key long-press.
+ * @hide
+ */
+oneway interface IOnVolumeKeyLongPressListener {
+ void onVolumeKeyLongPress(in KeyEvent event);
+}
+
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index bb59e5b..8a98773 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -18,6 +18,8 @@
import android.content.ComponentName;
import android.media.IRemoteVolumeController;
import android.media.session.IActiveSessionsListener;
+import android.media.session.IOnVolumeKeyLongPressListener;
+import android.media.session.IOnMediaKeyListener;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.os.Bundle;
@@ -31,6 +33,7 @@
ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
List<IBinder> getSessions(in ComponentName compName, int userId);
void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
+ void dispatchVolumeKeyEvent(in KeyEvent keyEvent, int stream, boolean musicOnly);
void dispatchAdjustVolume(int suggestedStream, int delta, int flags);
void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
int userId);
@@ -41,4 +44,8 @@
// For PhoneWindowManager to precheck media keys
boolean isGlobalPriorityActive();
-}
\ No newline at end of file
+
+ void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
+ void setOnMediaKeyListener(in IOnMediaKeyListener listener);
+}
+
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 95cb8ae..7c3af31 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -176,54 +176,12 @@
}
}
- public void sendVolumeKeyEvent(KeyEvent keyEvent, boolean musicOnly) {
+ public void sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
if (keyEvent == null) {
Log.w(TAG, "Tried to send a null key event. Ignoring.");
return;
}
- boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
- boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
- int direction = 0;
- boolean isMute = false;
- switch (keyEvent.getKeyCode()) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- direction = AudioManager.ADJUST_RAISE;
- break;
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- direction = AudioManager.ADJUST_LOWER;
- break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- isMute = true;
- break;
- }
- if (down || up) {
- int flags = AudioManager.FLAG_FROM_KEY;
- if (musicOnly) {
- // This flag is used when the screen is off to only affect
- // active media
- flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
- } else {
- // These flags are consistent with the home screen
- if (up) {
- flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
- } else {
- flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
- }
- }
- if (direction != 0) {
- // If this is action up we want to send a beep for non-music events
- if (up) {
- direction = 0;
- }
- mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
- direction, flags);
- } else if (isMute) {
- if (down && keyEvent.getRepeatCount() == 0) {
- mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
- AudioManager.ADJUST_TOGGLE_MUTE, flags);
- }
- }
- }
+ mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
}
public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 2364a13..80d2a0c 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
import android.media.AudioManager;
@@ -26,6 +27,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
@@ -50,6 +52,18 @@
public final class MediaSessionManager {
private static final String TAG = "SessionManager";
+ /**
+ * Used by IOnMediaKeyListener to indicate that the media key event isn't handled.
+ * @hide
+ */
+ public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0;
+
+ /**
+ * Used by IOnMediaKeyListener to indicate that the media key event is handled.
+ * @hide
+ */
+ public static final int RESULT_MEDIA_KEY_HANDLED = 1;
+
private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
= new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
private final Object mLock = new Object();
@@ -57,6 +71,9 @@
private Context mContext;
+ private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
+ private OnMediaKeyListenerImpl mOnMediaKeyListener;
+
/**
* @hide
*/
@@ -278,6 +295,21 @@
}
/**
+ * Send a volume key event. The receiver will be selected automatically.
+ *
+ * @param keyEvent The volume KeyEvent to send.
+ * @param needWakeLock True if a wake lock should be held while sending the key.
+ * @hide
+ */
+ public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {
+ try {
+ mService.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send volume key event.", e);
+ }
+ }
+
+ /**
* Dispatch an adjust volume request to the system. It will be sent to the
* most relevant audio stream or media session. The direction must be one of
* {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
@@ -313,6 +345,72 @@
}
/**
+ * Set the volume key long-press listener. While the listener is set, the listener
+ * gets the volume key long-presses instead of changing volume.
+ *
+ * <p>System can only have a single volume key long-press listener.
+ *
+ * @param listener The volume key long-press listener. {@code null} to reset.
+ * @param handler The handler on which the listener should be invoked, or {@code null}
+ * if the listener should be invoked on the calling thread's looper.
+ * @hide
+ */
+ @SystemApi
+ public void setOnVolumeKeyLongPressListener(
+ OnVolumeKeyLongPressListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ try {
+ if (listener == null) {
+ mOnVolumeKeyLongPressListener = null;
+ mService.setOnVolumeKeyLongPressListener(null);
+ } else {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ mOnVolumeKeyLongPressListener =
+ new OnVolumeKeyLongPressListenerImpl(listener, handler);
+ mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set volume key long press listener", e);
+ }
+ }
+ }
+
+ /**
+ * Set the media key listener. While the listener is set, the listener
+ * gets the media key before any other media sessions but after the global priority session.
+ * If the listener handles the key (i.e. returns {@code true}),
+ * other sessions will not get the event.
+ *
+ * <p>System can only have a single media key listener.
+ *
+ * @param listener The media key listener. {@code null} to reset.
+ * @param handler The handler on which the listener should be invoked, or {@code null}
+ * if the listener should be invoked on the calling thread's looper.
+ * @hide
+ */
+ @SystemApi
+ public void setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ try {
+ if (listener == null) {
+ mOnMediaKeyListener = null;
+ mService.setOnMediaKeyListener(null);
+ } else {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ mOnMediaKeyListener = new OnMediaKeyListenerImpl(listener, handler);
+ mService.setOnMediaKeyListener(mOnMediaKeyListener);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set media key listener", e);
+ }
+ }
+ }
+
+ /**
* Listens for changes to the list of active sessions. This can be added
* using {@link #addOnActiveSessionsChangedListener}.
*/
@@ -320,6 +418,33 @@
public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
}
+ /**
+ * Listens the volume key long-presses.
+ * @hide
+ */
+ @SystemApi
+ public interface OnVolumeKeyLongPressListener {
+ /**
+ * Called when the volume key is long-pressed.
+ * <p>This will be called for both down and up events.
+ */
+ void onVolumeKeyLongPress(KeyEvent event);
+ }
+
+ /**
+ * Listens the media key.
+ * @hide
+ */
+ @SystemApi
+ public interface OnMediaKeyListener {
+ /**
+ * Called when the media key is pressed.
+ * <p>If it takes more than 1s to return, the key event will be sent to
+ * other media sessions.
+ */
+ boolean onMediaKey(KeyEvent event);
+ }
+
private static final class SessionsChangedWrapper {
private Context mContext;
private OnActiveSessionsChangedListener mListener;
@@ -360,4 +485,60 @@
mHandler = null;
}
}
+
+ private static final class OnVolumeKeyLongPressListenerImpl
+ extends IOnVolumeKeyLongPressListener.Stub {
+ private OnVolumeKeyLongPressListener mListener;
+ private Handler mHandler;
+
+ public OnVolumeKeyLongPressListenerImpl(
+ OnVolumeKeyLongPressListener listener, Handler handler) {
+ mListener = listener;
+ mHandler = handler;
+ }
+
+ @Override
+ public void onVolumeKeyLongPress(KeyEvent event) {
+ if (mListener == null || mHandler == null) {
+ Log.w(TAG, "Failed to call volume key long-press listener." +
+ " Either mListener or mHandler is null");
+ return;
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onVolumeKeyLongPress(event);
+ }
+ });
+ }
+ }
+
+ private static final class OnMediaKeyListenerImpl extends IOnMediaKeyListener.Stub {
+ private OnMediaKeyListener mListener;
+ private Handler mHandler;
+
+ public OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler) {
+ mListener = listener;
+ mHandler = handler;
+ }
+
+ @Override
+ public void onMediaKey(KeyEvent event, ResultReceiver result) {
+ if (mListener == null || mHandler == null) {
+ Log.w(TAG, "Failed to call media key listener." +
+ " Either mListener or mHandler is null");
+ return;
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ boolean handled = mListener.onMediaKey(event);
+ Log.d(TAG, "The media key listener is returned " + handled);
+ result.send(
+ handled ? RESULT_MEDIA_KEY_HANDLED : RESULT_MEDIA_KEY_NOT_HANDLED,
+ null);
+ }
+ });
+ }
+ }
}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 20706fd..3ee80af 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -543,6 +543,17 @@
*/
public static final String TYPE_S_DMB = "TYPE_S_DMB";
+ /**
+ * The channel type for preview videos.
+ *
+ * <P>Unlike other broadcast TV channel types, the programs in the preview channel usually
+ * are promotional videos. The UI may treat the preview channels differently from the other
+ * broadcast channels.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_PREVIEW = "TYPE_PREVIEW";
+
/** A generic service type. */
public static final String SERVICE_TYPE_OTHER = "SERVICE_TYPE_OTHER";
@@ -1001,6 +1012,20 @@
*/
public static final String COLUMN_VERSION_NUMBER = "version_number";
+ /**
+ * The flag indicating whether this TV channel is transient or not.
+ *
+ * <p>A value of 1 indicates that the channel will be automatically removed by the system on
+ * reboot, and a value of 0 indicates that the channel is persistent across reboot. If not
+ * specified, this value is set to 0 (not transient) by default.
+ *
+ * <p>Type: INTEGER (boolean)
+ * @see Programs#COLUMN_TRANSIENT
+ * @hide
+ */
+ @SystemApi
+ public static final String COLUMN_TRANSIENT = "transient";
+
private Channels() {}
/**
@@ -1165,6 +1190,8 @@
* previous program in the same channel. In practice, start time will usually be the end
* time of the previous program.
*
+ * <p>Can be empty if this program belongs to a {@link Channels#TYPE_PREVIEW} channel.
+ *
* <p>Type: INTEGER (long)
*/
public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
@@ -1176,6 +1203,8 @@
* next program in the same channel. In practice, end time will usually be the start time of
* the next program.
*
+ * <p>Can be empty if this program belongs to a {@link Channels#TYPE_PREVIEW} channel.
+ *
* <p>Type: INTEGER (long)
*/
public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
@@ -1410,6 +1439,102 @@
*/
public static final String COLUMN_VERSION_NUMBER = "version_number";
+ /**
+ * 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
+ * apps.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+
+ /**
+ * The URI for the preview video.
+ *
+ * <p>This is only relevant to {@link Channels#TYPE_PREVIEW}. The data in the column must be
+ * a URL, or a URI in one of the following formats:
+ *
+ * <ul>
+ * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+ * </li>
+ * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+ * </ul>
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
+
+ /**
+ * The last playback position (in milliseconds) of the preview video.
+ *
+ * <p>This is only relevant to {@link Channels#TYPE_PREVIEW}.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION =
+ "preview_last_playback_position";
+
+ /**
+ * The duration (in milliseconds) of the preview video.
+ *
+ * <p>This is only relevant to {@link Channels#TYPE_PREVIEW}.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String COLUMN_PREVIEW_DURATION = "preview_duration";
+
+ /**
+ * The intent URI which is launched when the preview video is selected.
+ *
+ * <p>The URI is created using {@link Intent#toUri} with {@link Intent#URI_INTENT_SCHEME}
+ * and converted back to the original intent with {@link Intent#parseUri}. The intent is
+ * launched when the user selects the preview video item.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_PREVIEW_INTENT_URI =
+ "preview_intent_uri";
+
+ /**
+ * The weight of the preview program within the channel.
+ *
+ * <p>The UI may choose to show this item in a different position in the channel row.
+ * A larger weight value means the program is more important than other programs having
+ * smaller weight values. The value is relevant for the preview programs in the same
+ * channel. This is only relevant to {@link Channels#TYPE_PREVIEW}.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String COLUMN_PREVIEW_WEIGHT = "preview_weight";
+
+ /**
+ * The flag indicating whether this program is transient or not.
+ *
+ * <p>A value of 1 indicates that the channel will be automatically removed by the system on
+ * reboot, and a value of 0 indicates that the channel is persistent across reboot. If not
+ * specified, this value is set to 0 (not transient) by default.
+ *
+ * <p>Type: INTEGER (boolean)
+ * @see Channels#COLUMN_TRANSIENT
+ * @hide
+ */
+ @SystemApi
+ public static final String COLUMN_TRANSIENT = "transient";
+
private Programs() {}
/** Canonical genres for TV programs. */
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index dfddaa5..1eae8db 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -317,6 +317,13 @@
*/
public static final String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
+ /**
+ * Activity action to display the recording schedules. When invoked, the system will display an
+ * appropriate UI to browse the schedules.
+ */
+ public static final String ACTION_VIEW_RECORDING_SCHEDULES =
+ "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
+
private final ITvInputManager mService;
private final Object mLock = new Object();
diff --git a/native/android/Android.mk b/native/android/Android.mk
index da4e4ba..355f52e 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -9,6 +9,7 @@
asset_manager.cpp \
choreographer.cpp \
configuration.cpp \
+ hardware_buffer.cpp \
input.cpp \
looper.cpp \
native_activity.cpp \
diff --git a/native/android/hardware_buffer.cpp b/native/android/hardware_buffer.cpp
new file mode 100644
index 0000000..6a10cb5
--- /dev/null
+++ b/native/android/hardware_buffer.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AHardwareBuffer"
+
+#include <android/hardware_buffer_jni.h>
+
+#include <errno.h>
+#include <sys/socket.h>
+
+#include <memory>
+
+#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <binder/Binder.h>
+#include <binder/Parcel.h>
+#include <cutils/native_handle.h>
+#include <binder/IServiceManager.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <ui/GraphicBuffer.h>
+#include <utils/Flattenable.h>
+#include <utils/Log.h>
+
+static constexpr int kDataBufferSize = 64 * sizeof(int); // 64 ints
+
+using namespace android;
+
+static inline const GraphicBuffer* AHardwareBuffer_to_GraphicBuffer(
+ const AHardwareBuffer* buffer) {
+ return reinterpret_cast<const GraphicBuffer*>(buffer);
+}
+
+static inline GraphicBuffer* AHardwareBuffer_to_GraphicBuffer(
+ AHardwareBuffer* buffer) {
+ return reinterpret_cast<GraphicBuffer*>(buffer);
+}
+
+static inline AHardwareBuffer* GraphicBuffer_to_AHardwareBuffer(
+ GraphicBuffer* buffer) {
+ return reinterpret_cast<AHardwareBuffer*>(buffer);
+}
+
+// ----------------------------------------------------------------------------
+// Public functions
+// ----------------------------------------------------------------------------
+
+int AHardwareBuffer_allocate(const AHardwareBuffer_Desc* desc,
+ AHardwareBuffer** outBuffer) {
+ if (!outBuffer || !desc) return BAD_VALUE;
+
+ // The holder is used to destroy the buffer if an error occurs.
+ sp<IServiceManager> sm = defaultServiceManager();
+ if (sm == nullptr) {
+ ALOGE("Unable to connect to ServiceManager");
+ return PERMISSION_DENIED;
+ }
+
+ // Get the SurfaceFlingerService.
+ sp<ISurfaceComposer> composer = interface_cast<ISurfaceComposer>(
+ sm->getService(String16("SurfaceFlinger")));
+ if (composer == nullptr) {
+ ALOGE("Unable to connect to surface composer");
+ return PERMISSION_DENIED;
+ }
+ // Get an IGraphicBufferAlloc to create the buffer.
+ sp<IGraphicBufferAlloc> allocator = composer->createGraphicBufferAlloc();
+ if (allocator == nullptr) {
+ ALOGE("Unable to obtain a buffer allocator");
+ return PERMISSION_DENIED;
+ }
+
+ int format = android_hardware_HardwareBuffer_convertToPixelFormat(
+ desc->format);
+ if (format == 0) {
+ ALOGE("Invalid pixel format");
+ return BAD_VALUE;
+ }
+
+ status_t err;
+ uint32_t usage = android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+ desc->usage0, desc->usage1);
+ sp<GraphicBuffer> gbuffer = allocator->createGraphicBuffer(desc->width,
+ desc->height, format, desc->layers, usage, &err);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ *outBuffer = GraphicBuffer_to_AHardwareBuffer(gbuffer.get());
+ // Ensure the buffer doesn't get destroyed with the sp<> goes away.
+ AHardwareBuffer_acquire(*outBuffer);
+ return NO_ERROR;
+}
+
+void AHardwareBuffer_acquire(AHardwareBuffer* buffer) {
+ AHardwareBuffer_to_GraphicBuffer(buffer)->incStrong(
+ (void*)AHardwareBuffer_acquire);
+}
+
+void AHardwareBuffer_release(AHardwareBuffer* buffer) {
+ AHardwareBuffer_to_GraphicBuffer(buffer)->decStrong(
+ (void*)AHardwareBuffer_acquire);
+}
+
+void AHardwareBuffer_describe(const AHardwareBuffer* buffer,
+ AHardwareBuffer_Desc* outDesc) {
+ if (!buffer || !outDesc) return;
+
+ const GraphicBuffer* gbuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+
+ outDesc->width = gbuffer->getWidth();
+ outDesc->height = gbuffer->getHeight();
+ outDesc->layers = gbuffer->getLayerCount();
+ outDesc->usage0 =
+ android_hardware_HardwareBuffer_convertFromGrallocUsageBits(
+ gbuffer->getUsage());
+ outDesc->usage1 = 0;
+ outDesc->format = android_hardware_HardwareBuffer_convertFromPixelFormat(
+ static_cast<uint32_t>(gbuffer->getPixelFormat()));
+}
+
+int AHardwareBuffer_lock(AHardwareBuffer* buffer, uint64_t usage0,
+ int32_t fence, const ARect* rect, void** outVirtualAddress) {
+ if (!buffer) return BAD_VALUE;
+
+ if (usage0 & ~(AHARDWAREBUFFER_USAGE0_CPU_READ_OFTEN |
+ AHARDWAREBUFFER_USAGE0_CPU_WRITE_OFTEN)) {
+ ALOGE("Invalid usage flags passed to AHardwareBuffer_lock; only "
+ " AHARDWAREBUFFER_USAGE0_CPU_* flags are allowed");
+ return BAD_VALUE;
+ }
+
+ uint32_t usage = android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+ usage0, 0);
+ GraphicBuffer* gBuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+ if (!rect) {
+ return gBuffer->lockAsync(usage, outVirtualAddress, fence);
+ } else {
+ Rect bounds(rect->left, rect->top, rect->right, rect->bottom);
+ return gBuffer->lockAsync(usage, bounds, outVirtualAddress, fence);
+ }
+}
+
+int AHardwareBuffer_unlock(AHardwareBuffer* buffer, int32_t* fence) {
+ if (!buffer) return BAD_VALUE;
+
+ GraphicBuffer* gBuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+ return gBuffer->unlockAsync(fence);
+}
+
+int AHardwareBuffer_sendHandleToUnixSocket(const AHardwareBuffer* buffer,
+ int socketFd) {
+ if (!buffer) return BAD_VALUE;
+ const GraphicBuffer* gBuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+
+ size_t flattenedSize = gBuffer->getFlattenedSize();
+ size_t fdCount = gBuffer->getFdCount();
+
+ std::unique_ptr<uint8_t[]> data(new uint8_t[flattenedSize]);
+ std::unique_ptr<int[]> fds(new int[fdCount]);
+
+ // Make copies of needed items since flatten modifies them, and we don't
+ // want to send anything if there's an error during flatten.
+ size_t flattenedSizeCopy = flattenedSize;
+ size_t fdCountCopy = fdCount;
+ void* dataStart = data.get();
+ int* fdsStart = fds.get();
+ status_t err = gBuffer->flatten(dataStart, flattenedSizeCopy, fdsStart,
+ fdCountCopy);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ struct iovec iov[1];
+ iov[0].iov_base = data.get();
+ iov[0].iov_len = flattenedSize;
+
+ char buf[CMSG_SPACE(kDataBufferSize)];
+ struct msghdr msg = {
+ .msg_control = buf,
+ .msg_controllen = sizeof(buf),
+ .msg_iov = &iov[0],
+ .msg_iovlen = 1,
+ };
+
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fdCount);
+ int* fdData = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ memcpy(fdData, fds.get(), sizeof(int) * fdCount);
+ msg.msg_controllen = cmsg->cmsg_len;
+
+ int result = sendmsg(socketFd, &msg, 0);
+ if (result <= 0) {
+ ALOGE("Error writing AHardwareBuffer to socket: error %#x (%s)",
+ result, strerror(errno));
+ return result;
+ }
+ return NO_ERROR;
+}
+
+int AHardwareBuffer_recvHandleFromUnixSocket(int socketFd,
+ AHardwareBuffer** outBuffer) {
+ if (!outBuffer) return BAD_VALUE;
+
+ char dataBuf[CMSG_SPACE(kDataBufferSize)];
+ char fdBuf[CMSG_SPACE(kDataBufferSize)];
+ struct iovec iov[1];
+ iov[0].iov_base = dataBuf;
+ iov[0].iov_len = sizeof(dataBuf);
+
+ struct msghdr msg = {
+ .msg_control = fdBuf,
+ .msg_controllen = sizeof(fdBuf),
+ .msg_iov = &iov[0],
+ .msg_iovlen = 1,
+ };
+
+ int result = recvmsg(socketFd, &msg, 0);
+ if (result <= 0) {
+ ALOGE("Error reading AHardwareBuffer from socket: error %#x (%s)",
+ result, strerror(errno));
+ return result;
+ }
+
+ if (msg.msg_iovlen != 1) {
+ ALOGE("Error reading AHardwareBuffer from socket: bad data length");
+ return INVALID_OPERATION;
+ }
+
+ if (msg.msg_controllen % sizeof(int) != 0) {
+ ALOGE("Error reading AHardwareBuffer from socket: bad fd length");
+ return INVALID_OPERATION;
+ }
+
+ size_t dataLen = msg.msg_iov[0].iov_len;
+ const void* data = static_cast<const void*>(msg.msg_iov[0].iov_base);
+ if (!data) {
+ ALOGE("Error reading AHardwareBuffer from socket: no buffer data");
+ return INVALID_OPERATION;
+ }
+
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ if (!cmsg) {
+ ALOGE("Error reading AHardwareBuffer from socket: no fd header");
+ return INVALID_OPERATION;
+ }
+
+ size_t fdCount = msg.msg_controllen >> 2;
+ const int* fdData = reinterpret_cast<const int*>(CMSG_DATA(cmsg));
+ if (!fdData) {
+ ALOGE("Error reading AHardwareBuffer from socket: no fd data");
+ return INVALID_OPERATION;
+ }
+
+ GraphicBuffer* gBuffer = new GraphicBuffer();
+ status_t err = gBuffer->unflatten(data, dataLen, fdData, fdCount);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ *outBuffer = GraphicBuffer_to_AHardwareBuffer(gBuffer);
+ // Ensure the buffer has a positive ref-count.
+ AHardwareBuffer_acquire(*outBuffer);
+
+ return NO_ERROR;
+}
+
+const struct native_handle* AHardwareBuffer_getNativeHandle(
+ const AHardwareBuffer* buffer) {
+ if (!buffer) return nullptr;
+ const GraphicBuffer* gbuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+ return gbuffer->handle;
+}
+
+// ----------------------------------------------------------------------------
+// JNI functions
+// ----------------------------------------------------------------------------
+
+AHardwareBuffer* AHardwareBuffer_fromHardwareBuffer(JNIEnv* env,
+ jobject hardwareBufferObj) {
+ return android_hardware_HardwareBuffer_getNativeHardwareBuffer(env,
+ hardwareBufferObj);
+}
+
+jobject AHardwareBuffer_toHardwareBuffer(JNIEnv* env,
+ AHardwareBuffer* hardwareBuffer) {
+ return android_hardware_HardwareBuffer_createFromAHardwareBuffer(env,
+ hardwareBuffer);
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 5758a3c..f9e8fda 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -139,6 +139,17 @@
ANativeActivity_setWindowFlags;
ANativeActivity_setWindowFormat;
ANativeActivity_showSoftInput;
+ AHardwareBuffer_acquire; # introduced=26
+ AHardwareBuffer_allocate; # introduced=26
+ AHardwareBuffer_describe; # introduced=26
+ AHardwareBuffer_fromHardwareBuffer; # introduced=26
+ AHardwareBuffer_getNativeHandle; # introduced=26
+ AHardwareBuffer_lock; # introduced=26
+ AHardwareBuffer_recvHandleFromUnixSocket; # introduced=26
+ AHardwareBuffer_release; # introduced=26
+ AHardwareBuffer_sendHandleToUnixSocket; # introduced=26
+ AHardwareBuffer_toHardwareBuffer; # introduced=26
+ AHardwareBuffer_unlock; # introduced=26
ANativeWindow_acquire;
ANativeWindow_fromSurface;
ANativeWindow_fromSurfaceTexture; # introduced-arm=13 introduced-mips=13 introduced-x86=13
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 28d9e5c..e2080b0 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -37,5 +37,7 @@
<activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"/>
+ <service android:name="com.android.carrierdefaultapp.ProvisionObserver"
+ android:permission="android.permission.BIND_JOB_SERVICE"/>
</application>
</manifest>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
index bc0fa02..3fd89d9 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
@@ -28,6 +28,10 @@
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive intent: " + intent.getAction());
+ if (ProvisionObserver.isDeferredForProvision(context, intent)) {
+ Log.d(TAG, "skip carrier actions during provisioning");
+ return;
+ }
List<Integer> actionList = CustomConfigLoader.loadCarrierActionList(context, intent);
for (int actionIdx : actionList) {
Log.d(TAG, "apply carrier action idx: " + actionIdx);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
new file mode 100644
index 0000000..3e34f0a
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
@@ -0,0 +1,114 @@
+/*
+ * 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.carrierdefaultapp;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+/**
+ * Service to run {@link android.app.job.JobScheduler} job.
+ * Service to monitor when there is a change to conent URI
+ * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED}
+ */
+public class ProvisionObserver extends JobService {
+
+ private static final String TAG = ProvisionObserver.class.getSimpleName();
+ public static final int PROVISION_OBSERVER_REEVALUATION_JOB_ID = 1;
+ // minimum & maximum update delay TBD
+ private static final int CONTENT_UPDATE_DELAY_MS = 100;
+ private static final int CONTENT_MAX_DELAY_MS = 200;
+
+ @Override
+ public boolean onStartJob(JobParameters jobParameters) {
+ switch (jobParameters.getJobId()) {
+ case PROVISION_OBSERVER_REEVALUATION_JOB_ID:
+ if (isProvisioned(this)) {
+ Log.d(TAG, "device provisioned, force network re-evaluation");
+ final ConnectivityManager connMgr = ConnectivityManager.from(this);
+ Network[] info = connMgr.getAllNetworks();
+ for (Network nw : info) {
+ final NetworkCapabilities nc = connMgr.getNetworkCapabilities(nw);
+ if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ // force connectivity re-evaluation to assume skipped carrier actions.
+ // one of the following calls will match the last evaluation.
+ connMgr.reportNetworkConnectivity(nw, true);
+ connMgr.reportNetworkConnectivity(nw, false);
+ break;
+ }
+ }
+ }
+ default:
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ return false;
+ }
+
+ // Returns true if the device is not provisioned yet (in setup wizard), false otherwise
+ private static boolean isProvisioned(Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) == 1;
+ }
+
+ /**
+ * Static utility function to schedule a job to execute upon the change of content URI
+ * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED}.
+ * @param context The context used to retrieve the {@link ComponentName} and system services
+ * @return true carrier actions are deferred due to phone provisioning process, false otherwise
+ */
+ public static boolean isDeferredForProvision(Context context, Intent intent) {
+ if (isProvisioned(context)) {
+ return false;
+ }
+ int jobId;
+ switch(intent.getAction()) {
+ case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+ jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID;
+ break;
+ default:
+ return false;
+ }
+ final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
+ Context.JOB_SCHEDULER_SERVICE);
+ final JobInfo job = new JobInfo.Builder(jobId,
+ new ComponentName(context, ProvisionObserver.class))
+ .addTriggerContentUri(new JobInfo.TriggerContentUri(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), 0))
+ .setTriggerContentUpdateDelay(CONTENT_UPDATE_DELAY_MS)
+ .setTriggerContentMaxDelay(CONTENT_MAX_DELAY_MS)
+ .build();
+ jobScheduler.schedule(job);
+ return true;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index fbc6aa3..ae6ada2a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -21,6 +21,8 @@
import android.os.BatteryManager;
import android.os.UserManager;
import android.print.PrintManager;
+import android.view.View;
+
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.UserIconDrawable;
@@ -32,7 +34,7 @@
private static String sServicesSystemSharedLibPackageName;
private static String sSharedSystemSharedLibPackageName;
- static final int[] WIFI_PIE_FOR_BADGING = {
+ public static final int[] WIFI_PIE_FOR_BADGING = {
com.android.internal.R.drawable.ic_signal_wifi_badged_0_bars,
com.android.internal.R.drawable.ic_signal_wifi_badged_1_bar,
com.android.internal.R.drawable.ic_signal_wifi_badged_2_bars,
@@ -288,8 +290,15 @@
});
}
- private static int getWifiBadgeResource(int badge) {
+ /**
+ * Returns the resource id for the given badge or {@link View.NO_ID} if no badge is to be shown.
+ *
+ * @throws IllegalArgumentException if the given badge value is not supported.
+ */
+ public static int getWifiBadgeResource(int badge) {
switch (badge) {
+ case ScoredNetwork.BADGING_NONE:
+ return View.NO_ID;
case ScoredNetwork.BADGING_SD:
return com.android.internal.R.drawable.ic_signal_wifi_badged_sd;
case ScoredNetwork.BADGING_HD:
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 2b1582d..24a3aa9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1429,6 +1429,23 @@
}
};
+ public static final AppFilter FILTER_GAMES = new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(ApplicationsState.AppEntry info) {
+ // TODO: Update for the new game category.
+ boolean isGame;
+ synchronized (info.info) {
+ isGame = ((info.info.flags & ApplicationInfo.FLAG_IS_GAME) != 0)
+ || info.info.category == ApplicationInfo.CATEGORY_GAME;
+ }
+ return isGame;
+ }
+ };
+
public static class VolumeFilter implements AppFilter {
private final String mVolumeUuid;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index d34dd89..d4623d6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -22,13 +22,22 @@
public class InterestingConfigChanges {
private final Configuration mLastConfiguration = new Configuration();
+ private final int mFlags;
private int mLastDensity;
+ public InterestingConfigChanges() {
+ this(0);
+ }
+
+ public InterestingConfigChanges(int extraFlags) {
+ mFlags = extraFlags | ActivityInfo.CONFIG_LOCALE
+ | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ }
+
public boolean applyNewConfig(Resources res) {
int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
- if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
- |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
+ if (densityChanged || (configChanges & (mFlags)) != 0) {
mLastDensity = res.getDisplayMetrics().densityDpi;
return true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index fabae57..6f52dca 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -12,13 +12,17 @@
import android.content.Intent;
import android.net.NetworkInfo;
+import android.net.NetworkKey;
+import android.net.WifiKey;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.util.Log;
import java.util.List;
public class WifiStatusTracker {
+ private static final String TAG = "WifiStatusTracker";
private final WifiManager mWifiManager;
public boolean enabled;
@@ -26,6 +30,7 @@
public String ssid;
public int rssi;
public int level;
+ public NetworkKey networkKey;
public WifiStatusTracker(WifiManager wifiManager) {
mWifiManager = wifiManager;
@@ -40,19 +45,32 @@
final NetworkInfo networkInfo = (NetworkInfo)
intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
connected = networkInfo != null && networkInfo.isConnected();
+ WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
+ ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
+ : mWifiManager.getConnectionInfo();
+
// If Connected grab the signal strength and ssid.
- if (connected) {
- // try getting it out of the intent first
- WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
- ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
- : mWifiManager.getConnectionInfo();
- if (info != null) {
- ssid = getSsid(info);
+ if (connected && info != null) {
+ ssid = getSsid(info);
+ String bssid = info.getBSSID();
+ if ((ssid != null) && (bssid != null)) {
+ // Reuse existing network key object if possible.
+ if ((networkKey == null)
+ || !networkKey.wifiKey.ssid.equals(ssid)
+ || !networkKey.wifiKey.bssid.equals(bssid)) {
+ try {
+ networkKey = new NetworkKey(
+ new WifiKey(ssid, bssid));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Cannot create NetworkKey", e);
+ }
+ }
} else {
- ssid = null;
+ networkKey = null;
}
- } else if (!connected) {
+ } else {
ssid = null;
+ networkKey = null;
}
} else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
// Default to -200 as its below WifiManager.MIN_RSSI.
diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk
index 98bce0c..bd910dd 100644
--- a/packages/SettingsLib/tests/integ/Android.mk
+++ b/packages/SettingsLib/tests/integ/Android.mk
@@ -28,7 +28,8 @@
android-support-test \
espresso-core \
mockito-target-minus-junit4 \
- legacy-android-test
+ legacy-android-test \
+ truth-prebuilt
include frameworks/base/packages/SettingsLib/common.mk
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
new file mode 100644
index 0000000..4f2347d
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.pm.ApplicationInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ApplicationsStateTest {
+ private ApplicationsState.AppFilter mFilter;
+ private ApplicationsState.AppEntry mEntry;
+
+ @Before
+ public void setUp() {
+ mFilter = ApplicationsState.FILTER_GAMES;
+ mEntry = mock(ApplicationsState.AppEntry.class);
+ mEntry.info = mock(ApplicationInfo.class);
+ }
+
+ @Test
+ public void testGamesFilterAcceptsGameDeprecated() {
+ mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
+
+ assertThat(mFilter.filterApp(mEntry)).isTrue();
+ }
+
+ @Test
+ public void testGameFilterAcceptsCategorizedGame() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+ assertThat(mFilter.filterApp(mEntry)).isTrue();
+ }
+
+ @Test
+ public void testGameFilterAcceptsCategorizedGameAndDeprecatedIsGame() {
+ mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
+ mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+ assertThat(mFilter.filterApp(mEntry)).isTrue();
+ }
+
+ @Test
+ public void testGamesFilterRejectsNotGame() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
+
+ assertThat(mFilter.filterApp(mEntry)).isFalse();
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
new file mode 100644
index 0000000..19ce3d0
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -0,0 +1,1655 @@
+/*
+ * Copyright (C) 2007 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.providers.settings;
+
+import android.annotation.NonNull;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.providers.settings.GlobalSettingsProto;
+import android.providers.settings.SecureSettingsProto;
+import android.providers.settings.SettingProto;
+import android.providers.settings.SettingsServiceDumpProto;
+import android.providers.settings.SystemSettingsProto;
+import android.providers.settings.UserSettingsProto;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+/** @hide */
+class SettingsProtoDumpUtil {
+ private SettingsProtoDumpUtil() {}
+
+ static void dumpProtoLocked(SettingsProvider.SettingsRegistry settingsRegistry,
+ ProtoOutputStream proto) {
+ // Global settings
+ SettingsState globalSettings = settingsRegistry.getSettingsLocked(
+ SettingsProvider.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
+ long globalSettingsToken = proto.start(SettingsServiceDumpProto.GLOBAL_SETTINGS);
+ dumpProtoGlobalSettingsLocked(globalSettings, proto);
+ proto.end(globalSettingsToken);
+
+ // Per-user settings
+ SparseBooleanArray users = settingsRegistry.getKnownUsersLocked();
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ long userSettingsToken = proto.start(SettingsServiceDumpProto.USER_SETTINGS);
+ dumpProtoUserSettingsLocked(
+ settingsRegistry, UserHandle.of(users.keyAt(i)), proto);
+ proto.end(userSettingsToken);
+ }
+ }
+
+ /**
+ * Dump all settings of a user as a proto buf.
+ *
+ * @param settingsRegistry
+ * @param user The user the settings should be dumped for
+ * @param proto The proto buf stream to dump to
+ */
+ private static void dumpProtoUserSettingsLocked(
+ SettingsProvider.SettingsRegistry settingsRegistry,
+ @NonNull UserHandle user,
+ @NonNull ProtoOutputStream proto) {
+ proto.write(UserSettingsProto.USER_ID, user.getIdentifier());
+
+ SettingsState secureSettings = settingsRegistry.getSettingsLocked(
+ SettingsProvider.SETTINGS_TYPE_SECURE, user.getIdentifier());
+ long secureSettingsToken = proto.start(UserSettingsProto.SECURE_SETTINGS);
+ dumpProtoSecureSettingsLocked(secureSettings, proto);
+ proto.end(secureSettingsToken);
+
+ SettingsState systemSettings = settingsRegistry.getSettingsLocked(
+ SettingsProvider.SETTINGS_TYPE_SYSTEM, user.getIdentifier());
+ long systemSettingsToken = proto.start(UserSettingsProto.SYSTEM_SETTINGS);
+ dumpProtoSystemSettingsLocked(systemSettings, proto);
+ proto.end(systemSettingsToken);
+ }
+
+ private static void dumpProtoGlobalSettingsLocked(
+ @NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+ dumpSetting(s, p,
+ Settings.Global.ADD_USERS_WHEN_LOCKED,
+ GlobalSettingsProto.ADD_USERS_WHEN_LOCKED);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
+ GlobalSettingsProto.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.AIRPLANE_MODE_ON,
+ GlobalSettingsProto.AIRPLANE_MODE_ON);
+ dumpSetting(s, p,
+ Settings.Global.THEATER_MODE_ON,
+ GlobalSettingsProto.THEATER_MODE_ON);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_BLUETOOTH,
+ GlobalSettingsProto.RADIO_BLUETOOTH);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_WIFI,
+ GlobalSettingsProto.RADIO_WIFI);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_WIMAX,
+ GlobalSettingsProto.RADIO_WIMAX);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_CELL,
+ GlobalSettingsProto.RADIO_CELL);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_NFC,
+ GlobalSettingsProto.RADIO_NFC);
+ dumpSetting(s, p,
+ Settings.Global.AIRPLANE_MODE_RADIOS,
+ GlobalSettingsProto.AIRPLANE_MODE_RADIOS);
+ dumpSetting(s, p,
+ Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
+ GlobalSettingsProto.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_DISABLED_PROFILES,
+ GlobalSettingsProto.BLUETOOTH_DISABLED_PROFILES);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST,
+ GlobalSettingsProto.BLUETOOTH_INTEROPERABILITY_LIST);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SLEEP_POLICY,
+ GlobalSettingsProto.WIFI_SLEEP_POLICY);
+ dumpSetting(s, p,
+ Settings.Global.AUTO_TIME,
+ GlobalSettingsProto.AUTO_TIME);
+ dumpSetting(s, p,
+ Settings.Global.AUTO_TIME_ZONE,
+ GlobalSettingsProto.AUTO_TIME_ZONE);
+ dumpSetting(s, p,
+ Settings.Global.CAR_DOCK_SOUND,
+ GlobalSettingsProto.CAR_DOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.CAR_UNDOCK_SOUND,
+ GlobalSettingsProto.CAR_UNDOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.DESK_DOCK_SOUND,
+ GlobalSettingsProto.DESK_DOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.DESK_UNDOCK_SOUND,
+ GlobalSettingsProto.DESK_UNDOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.DOCK_SOUNDS_ENABLED,
+ GlobalSettingsProto.DOCK_SOUNDS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
+ GlobalSettingsProto.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY);
+ dumpSetting(s, p,
+ Settings.Global.LOCK_SOUND,
+ GlobalSettingsProto.LOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.UNLOCK_SOUND,
+ GlobalSettingsProto.UNLOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.TRUSTED_SOUND,
+ GlobalSettingsProto.TRUSTED_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.LOW_BATTERY_SOUND,
+ GlobalSettingsProto.LOW_BATTERY_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.POWER_SOUNDS_ENABLED,
+ GlobalSettingsProto.POWER_SOUNDS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
+ GlobalSettingsProto.WIRELESS_CHARGING_STARTED_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.CHARGING_SOUNDS_ENABLED,
+ GlobalSettingsProto.CHARGING_SOUNDS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
+ GlobalSettingsProto.STAY_ON_WHILE_PLUGGED_IN);
+ dumpSetting(s, p,
+ Settings.Global.BUGREPORT_IN_POWER_MENU,
+ GlobalSettingsProto.BUGREPORT_IN_POWER_MENU);
+ dumpSetting(s, p,
+ Settings.Global.ADB_ENABLED,
+ GlobalSettingsProto.ADB_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DEBUG_VIEW_ATTRIBUTES,
+ GlobalSettingsProto.DEBUG_VIEW_ATTRIBUTES);
+ dumpSetting(s, p,
+ Settings.Global.ASSISTED_GPS_ENABLED,
+ GlobalSettingsProto.ASSISTED_GPS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_ON,
+ GlobalSettingsProto.BLUETOOTH_ON);
+ dumpSetting(s, p,
+ Settings.Global.CDMA_CELL_BROADCAST_SMS,
+ GlobalSettingsProto.CDMA_CELL_BROADCAST_SMS);
+ dumpSetting(s, p,
+ Settings.Global.CDMA_ROAMING_MODE,
+ GlobalSettingsProto.CDMA_ROAMING_MODE);
+ dumpSetting(s, p,
+ Settings.Global.CDMA_SUBSCRIPTION_MODE,
+ GlobalSettingsProto.CDMA_SUBSCRIPTION_MODE);
+ dumpSetting(s, p,
+ Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
+ GlobalSettingsProto.DATA_ACTIVITY_TIMEOUT_MOBILE);
+ dumpSetting(s, p,
+ Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+ GlobalSettingsProto.DATA_ACTIVITY_TIMEOUT_WIFI);
+ dumpSetting(s, p,
+ Settings.Global.DATA_ROAMING,
+ GlobalSettingsProto.DATA_ROAMING);
+ dumpSetting(s, p,
+ Settings.Global.MDC_INITIAL_MAX_RETRY,
+ GlobalSettingsProto.MDC_INITIAL_MAX_RETRY);
+ dumpSetting(s, p,
+ Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
+ GlobalSettingsProto.FORCE_ALLOW_ON_EXTERNAL);
+ dumpSetting(s, p,
+ Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
+ GlobalSettingsProto.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
+ dumpSetting(s, p,
+ Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
+ GlobalSettingsProto.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT);
+ dumpSetting(s, p,
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
+ GlobalSettingsProto.DEVELOPMENT_SETTINGS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_PROVISIONED,
+ GlobalSettingsProto.DEVICE_PROVISIONED);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
+ GlobalSettingsProto.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DISPLAY_SIZE_FORCED,
+ GlobalSettingsProto.DISPLAY_SIZE_FORCED);
+ dumpSetting(s, p,
+ Settings.Global.DISPLAY_SCALING_FORCE,
+ GlobalSettingsProto.DISPLAY_SCALING_FORCE);
+ dumpSetting(s, p,
+ Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
+ GlobalSettingsProto.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
+ dumpSetting(s, p,
+ Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
+ GlobalSettingsProto.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
+ dumpSetting(s, p,
+ Settings.Global.HDMI_CONTROL_ENABLED,
+ GlobalSettingsProto.HDMI_CONTROL_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.HDMI_SYSTEM_AUDIO_ENABLED,
+ GlobalSettingsProto.HDMI_SYSTEM_AUDIO_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
+ GlobalSettingsProto.HDMI_CONTROL_AUTO_WAKEUP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
+ GlobalSettingsProto.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.MHL_INPUT_SWITCHING_ENABLED,
+ GlobalSettingsProto.MHL_INPUT_SWITCHING_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.MHL_POWER_CHARGE_ENABLED,
+ GlobalSettingsProto.MHL_POWER_CHARGE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.MOBILE_DATA,
+ GlobalSettingsProto.MOBILE_DATA);
+ dumpSetting(s, p,
+ Settings.Global.MOBILE_DATA_ALWAYS_ON,
+ GlobalSettingsProto.MOBILE_DATA_ALWAYS_ON);
+ dumpSetting(s, p,
+ Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE,
+ GlobalSettingsProto.CONNECTIVITY_METRICS_BUFFER_SIZE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_ENABLED,
+ GlobalSettingsProto.NETSTATS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_POLL_INTERVAL,
+ GlobalSettingsProto.NETSTATS_POLL_INTERVAL);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE,
+ GlobalSettingsProto.NETSTATS_TIME_CACHE_MAX_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES,
+ GlobalSettingsProto.NETSTATS_GLOBAL_ALERT_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_SAMPLE_ENABLED,
+ GlobalSettingsProto.NETSTATS_SAMPLE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_DEV_BUCKET_DURATION,
+ GlobalSettingsProto.NETSTATS_DEV_BUCKET_DURATION);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_DEV_PERSIST_BYTES,
+ GlobalSettingsProto.NETSTATS_DEV_PERSIST_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_DEV_ROTATE_AGE,
+ GlobalSettingsProto.NETSTATS_DEV_ROTATE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_DEV_DELETE_AGE,
+ GlobalSettingsProto.NETSTATS_DEV_DELETE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_BUCKET_DURATION,
+ GlobalSettingsProto.NETSTATS_UID_BUCKET_DURATION);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_PERSIST_BYTES,
+ GlobalSettingsProto.NETSTATS_UID_PERSIST_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_ROTATE_AGE,
+ GlobalSettingsProto.NETSTATS_UID_ROTATE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_DELETE_AGE,
+ GlobalSettingsProto.NETSTATS_UID_DELETE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION,
+ GlobalSettingsProto.NETSTATS_UID_TAG_BUCKET_DURATION);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES,
+ GlobalSettingsProto.NETSTATS_UID_TAG_PERSIST_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
+ GlobalSettingsProto.NETSTATS_UID_TAG_ROTATE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_TAG_DELETE_AGE,
+ GlobalSettingsProto.NETSTATS_UID_TAG_DELETE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_PREFERENCE,
+ GlobalSettingsProto.NETWORK_PREFERENCE);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_SCORER_APP,
+ GlobalSettingsProto.NETWORK_SCORER_APP);
+ dumpSetting(s, p,
+ Settings.Global.NITZ_UPDATE_DIFF,
+ GlobalSettingsProto.NITZ_UPDATE_DIFF);
+ dumpSetting(s, p,
+ Settings.Global.NITZ_UPDATE_SPACING,
+ GlobalSettingsProto.NITZ_UPDATE_SPACING);
+ dumpSetting(s, p,
+ Settings.Global.NTP_SERVER,
+ GlobalSettingsProto.NTP_SERVER);
+ dumpSetting(s, p,
+ Settings.Global.NTP_TIMEOUT,
+ GlobalSettingsProto.NTP_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Global.STORAGE_BENCHMARK_INTERVAL,
+ GlobalSettingsProto.STORAGE_BENCHMARK_INTERVAL);
+ dumpSetting(s, p,
+ Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
+ GlobalSettingsProto.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS);
+ dumpSetting(s, p,
+ Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
+ GlobalSettingsProto.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT);
+ dumpSetting(s, p,
+ Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
+ GlobalSettingsProto.DNS_RESOLVER_MIN_SAMPLES);
+ dumpSetting(s, p,
+ Settings.Global.DNS_RESOLVER_MAX_SAMPLES,
+ GlobalSettingsProto.DNS_RESOLVER_MAX_SAMPLES);
+ dumpSetting(s, p,
+ Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE,
+ GlobalSettingsProto.OTA_DISABLE_AUTOMATIC_UPDATE);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_ENABLE,
+ GlobalSettingsProto.PACKAGE_VERIFIER_ENABLE);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_TIMEOUT,
+ GlobalSettingsProto.PACKAGE_VERIFIER_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
+ GlobalSettingsProto.PACKAGE_VERIFIER_DEFAULT_RESPONSE);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE,
+ GlobalSettingsProto.PACKAGE_VERIFIER_SETTING_VISIBLE);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB,
+ GlobalSettingsProto.PACKAGE_VERIFIER_INCLUDE_ADB);
+ dumpSetting(s, p,
+ Settings.Global.FSTRIM_MANDATORY_INTERVAL,
+ GlobalSettingsProto.FSTRIM_MANDATORY_INTERVAL);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS,
+ GlobalSettingsProto.PDP_WATCHDOG_POLL_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
+ GlobalSettingsProto.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS,
+ GlobalSettingsProto.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
+ GlobalSettingsProto.PDP_WATCHDOG_TRIGGER_PACKET_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_ERROR_POLL_COUNT,
+ GlobalSettingsProto.PDP_WATCHDOG_ERROR_POLL_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT,
+ GlobalSettingsProto.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.SAMPLING_PROFILER_MS,
+ GlobalSettingsProto.SAMPLING_PROFILER_MS);
+ dumpSetting(s, p,
+ Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL,
+ GlobalSettingsProto.SETUP_PREPAID_DATA_SERVICE_URL);
+ dumpSetting(s, p,
+ Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL,
+ GlobalSettingsProto.SETUP_PREPAID_DETECTION_TARGET_URL);
+ dumpSetting(s, p,
+ Settings.Global.SETUP_PREPAID_DETECTION_REDIR_HOST,
+ GlobalSettingsProto.SETUP_PREPAID_DETECTION_REDIR_HOST);
+ dumpSetting(s, p,
+ Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS,
+ GlobalSettingsProto.SMS_OUTGOING_CHECK_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT,
+ GlobalSettingsProto.SMS_OUTGOING_CHECK_MAX_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.SMS_SHORT_CODE_CONFIRMATION,
+ GlobalSettingsProto.SMS_SHORT_CODE_CONFIRMATION);
+ dumpSetting(s, p,
+ Settings.Global.SMS_SHORT_CODE_RULE,
+ GlobalSettingsProto.SMS_SHORT_CODE_RULE);
+ dumpSetting(s, p,
+ Settings.Global.TCP_DEFAULT_INIT_RWND,
+ GlobalSettingsProto.TCP_DEFAULT_INIT_RWND);
+ dumpSetting(s, p,
+ Settings.Global.TETHER_SUPPORTED,
+ GlobalSettingsProto.TETHER_SUPPORTED);
+ dumpSetting(s, p,
+ Settings.Global.TETHER_DUN_REQUIRED,
+ GlobalSettingsProto.TETHER_DUN_REQUIRED);
+ dumpSetting(s, p,
+ Settings.Global.TETHER_DUN_APN,
+ GlobalSettingsProto.TETHER_DUN_APN);
+ dumpSetting(s, p,
+ Settings.Global.CARRIER_APP_WHITELIST,
+ GlobalSettingsProto.CARRIER_APP_WHITELIST);
+ dumpSetting(s, p,
+ Settings.Global.USB_MASS_STORAGE_ENABLED,
+ GlobalSettingsProto.USB_MASS_STORAGE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.USE_GOOGLE_MAIL,
+ GlobalSettingsProto.USE_GOOGLE_MAIL);
+ dumpSetting(s, p,
+ Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
+ GlobalSettingsProto.WEBVIEW_DATA_REDUCTION_PROXY_KEY);
+ dumpSetting(s, p,
+ Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
+ GlobalSettingsProto.WEBVIEW_FALLBACK_LOGIC_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WEBVIEW_PROVIDER,
+ GlobalSettingsProto.WEBVIEW_PROVIDER);
+ dumpSetting(s, p,
+ Settings.Global.WEBVIEW_MULTIPROCESS,
+ GlobalSettingsProto.WEBVIEW_MULTIPROCESS);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
+ GlobalSettingsProto.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
+ GlobalSettingsProto.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_AVOID_BAD_WIFI,
+ GlobalSettingsProto.NETWORK_AVOID_BAD_WIFI);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_DISPLAY_ON,
+ GlobalSettingsProto.WIFI_DISPLAY_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON,
+ GlobalSettingsProto.WIFI_DISPLAY_CERTIFICATION_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_DISPLAY_WPS_CONFIG,
+ GlobalSettingsProto.WIFI_DISPLAY_WPS_CONFIG);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ GlobalSettingsProto.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ GlobalSettingsProto.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+ GlobalSettingsProto.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_COUNTRY_CODE,
+ GlobalSettingsProto.WIFI_COUNTRY_CODE);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
+ GlobalSettingsProto.WIFI_FRAMEWORK_SCAN_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_IDLE_MS,
+ GlobalSettingsProto.WIFI_IDLE_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_NUM_OPEN_NETWORKS_KEPT,
+ GlobalSettingsProto.WIFI_NUM_OPEN_NETWORKS_KEPT);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_ON,
+ GlobalSettingsProto.WIFI_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
+ GlobalSettingsProto.WIFI_SCAN_ALWAYS_AVAILABLE);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_WAKEUP_ENABLED,
+ GlobalSettingsProto.WIFI_WAKEUP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+ GlobalSettingsProto.NETWORK_RECOMMENDATIONS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
+ GlobalSettingsProto.BLE_SCAN_ALWAYS_AVAILABLE);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SAVED_STATE,
+ GlobalSettingsProto.WIFI_SAVED_STATE);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
+ GlobalSettingsProto.WIFI_SUPPLICANT_SCAN_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_ENHANCED_AUTO_JOIN,
+ GlobalSettingsProto.WIFI_ENHANCED_AUTO_JOIN);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_NETWORK_SHOW_RSSI,
+ GlobalSettingsProto.WIFI_NETWORK_SHOW_RSSI);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
+ GlobalSettingsProto.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_WATCHDOG_ON,
+ GlobalSettingsProto.WIFI_WATCHDOG_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
+ GlobalSettingsProto.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
+ GlobalSettingsProto.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
+ GlobalSettingsProto.WIFI_VERBOSE_LOGGING_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
+ GlobalSettingsProto.WIFI_MAX_DHCP_RETRY_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
+ GlobalSettingsProto.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
+ GlobalSettingsProto.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_FREQUENCY_BAND,
+ GlobalSettingsProto.WIFI_FREQUENCY_BAND);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_P2P_DEVICE_NAME,
+ GlobalSettingsProto.WIFI_P2P_DEVICE_NAME);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_REENABLE_DELAY_MS,
+ GlobalSettingsProto.WIFI_REENABLE_DELAY_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS,
+ GlobalSettingsProto.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS);
+ dumpSetting(s, p,
+ Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
+ GlobalSettingsProto.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS);
+ dumpSetting(s, p,
+ Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
+ GlobalSettingsProto.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS);
+ dumpSetting(s, p,
+ Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
+ GlobalSettingsProto.PROVISIONING_APN_ALARM_DELAY_IN_MS);
+ dumpSetting(s, p,
+ Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
+ GlobalSettingsProto.GPRS_REGISTER_CHECK_PERIOD_MS);
+ dumpSetting(s, p,
+ Settings.Global.WTF_IS_FATAL,
+ GlobalSettingsProto.WTF_IS_FATAL);
+ dumpSetting(s, p,
+ Settings.Global.MODE_RINGER,
+ GlobalSettingsProto.MODE_RINGER);
+ dumpSetting(s, p,
+ Settings.Global.OVERLAY_DISPLAY_DEVICES,
+ GlobalSettingsProto.OVERLAY_DISPLAY_DEVICES);
+ dumpSetting(s, p,
+ Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
+ GlobalSettingsProto.BATTERY_DISCHARGE_DURATION_THRESHOLD);
+ dumpSetting(s, p,
+ Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
+ GlobalSettingsProto.BATTERY_DISCHARGE_THRESHOLD);
+ dumpSetting(s, p,
+ Settings.Global.SEND_ACTION_APP_ERROR,
+ GlobalSettingsProto.SEND_ACTION_APP_ERROR);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_AGE_SECONDS,
+ GlobalSettingsProto.DROPBOX_AGE_SECONDS);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_MAX_FILES,
+ GlobalSettingsProto.DROPBOX_MAX_FILES);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_QUOTA_KB,
+ GlobalSettingsProto.DROPBOX_QUOTA_KB);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_QUOTA_PERCENT,
+ GlobalSettingsProto.DROPBOX_QUOTA_PERCENT);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_RESERVE_PERCENT,
+ GlobalSettingsProto.DROPBOX_RESERVE_PERCENT);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_TAG_PREFIX,
+ GlobalSettingsProto.DROPBOX_TAG_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.ERROR_LOGCAT_PREFIX,
+ GlobalSettingsProto.ERROR_LOGCAT_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
+ GlobalSettingsProto.SYS_FREE_STORAGE_LOG_INTERVAL);
+ dumpSetting(s, p,
+ Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
+ GlobalSettingsProto.DISK_FREE_CHANGE_REPORTING_THRESHOLD);
+ dumpSetting(s, p,
+ Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
+ GlobalSettingsProto.SYS_STORAGE_THRESHOLD_PERCENTAGE);
+ dumpSetting(s, p,
+ Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
+ GlobalSettingsProto.SYS_STORAGE_THRESHOLD_MAX_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
+ GlobalSettingsProto.SYS_STORAGE_FULL_THRESHOLD_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+ GlobalSettingsProto.SYNC_MAX_RETRY_DELAY_IN_SECONDS);
+ dumpSetting(s, p,
+ Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+ GlobalSettingsProto.CONNECTIVITY_CHANGE_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
+ GlobalSettingsProto.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS);
+ dumpSetting(s, p,
+ Settings.Global.PAC_CHANGE_DELAY,
+ GlobalSettingsProto.PAC_CHANGE_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_MODE,
+ GlobalSettingsProto.CAPTIVE_PORTAL_MODE);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_SERVER,
+ GlobalSettingsProto.CAPTIVE_PORTAL_SERVER);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_HTTPS_URL,
+ GlobalSettingsProto.CAPTIVE_PORTAL_HTTPS_URL);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_HTTP_URL,
+ GlobalSettingsProto.CAPTIVE_PORTAL_HTTP_URL);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL,
+ GlobalSettingsProto.CAPTIVE_PORTAL_FALLBACK_URL);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_USE_HTTPS,
+ GlobalSettingsProto.CAPTIVE_PORTAL_USE_HTTPS);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_USER_AGENT,
+ GlobalSettingsProto.CAPTIVE_PORTAL_USER_AGENT);
+ dumpSetting(s, p,
+ Settings.Global.NSD_ON,
+ GlobalSettingsProto.NSD_ON);
+ dumpSetting(s, p,
+ Settings.Global.SET_INSTALL_LOCATION,
+ GlobalSettingsProto.SET_INSTALL_LOCATION);
+ dumpSetting(s, p,
+ Settings.Global.DEFAULT_INSTALL_LOCATION,
+ GlobalSettingsProto.DEFAULT_INSTALL_LOCATION);
+ dumpSetting(s, p,
+ Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY,
+ GlobalSettingsProto.INET_CONDITION_DEBOUNCE_UP_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY,
+ GlobalSettingsProto.INET_CONDITION_DEBOUNCE_DOWN_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
+ GlobalSettingsProto.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT);
+ dumpSetting(s, p,
+ Settings.Global.HTTP_PROXY,
+ GlobalSettingsProto.HTTP_PROXY);
+ dumpSetting(s, p,
+ Settings.Global.GLOBAL_HTTP_PROXY_HOST,
+ GlobalSettingsProto.GLOBAL_HTTP_PROXY_HOST);
+ dumpSetting(s, p,
+ Settings.Global.GLOBAL_HTTP_PROXY_PORT,
+ GlobalSettingsProto.GLOBAL_HTTP_PROXY_PORT);
+ dumpSetting(s, p,
+ Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ GlobalSettingsProto.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ dumpSetting(s, p,
+ Settings.Global.GLOBAL_HTTP_PROXY_PAC,
+ GlobalSettingsProto.GLOBAL_HTTP_PROXY_PAC);
+ dumpSetting(s, p,
+ Settings.Global.SET_GLOBAL_HTTP_PROXY,
+ GlobalSettingsProto.SET_GLOBAL_HTTP_PROXY);
+ dumpSetting(s, p,
+ Settings.Global.DEFAULT_DNS_SERVER,
+ GlobalSettingsProto.DEFAULT_DNS_SERVER);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_HEADSET_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_HEADSET_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_MAP_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_MAP_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_MAP_CLIENT_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_MAP_CLIENT_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_SAP_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_SAP_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_IDLE_CONSTANTS,
+ GlobalSettingsProto.DEVICE_IDLE_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH,
+ GlobalSettingsProto.DEVICE_IDLE_CONSTANTS_WATCH);
+ dumpSetting(s, p,
+ Settings.Global.APP_IDLE_CONSTANTS,
+ GlobalSettingsProto.APP_IDLE_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.ALARM_MANAGER_CONSTANTS,
+ GlobalSettingsProto.ALARM_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.JOB_SCHEDULER_CONSTANTS,
+ GlobalSettingsProto.JOB_SCHEDULER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
+ GlobalSettingsProto.SHORTCUT_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.WINDOW_ANIMATION_SCALE,
+ GlobalSettingsProto.WINDOW_ANIMATION_SCALE);
+ dumpSetting(s, p,
+ Settings.Global.TRANSITION_ANIMATION_SCALE,
+ GlobalSettingsProto.TRANSITION_ANIMATION_SCALE);
+ dumpSetting(s, p,
+ Settings.Global.ANIMATOR_DURATION_SCALE,
+ GlobalSettingsProto.ANIMATOR_DURATION_SCALE);
+ dumpSetting(s, p,
+ Settings.Global.FANCY_IME_ANIMATIONS,
+ GlobalSettingsProto.FANCY_IME_ANIMATIONS);
+ dumpSetting(s, p,
+ Settings.Global.COMPATIBILITY_MODE,
+ GlobalSettingsProto.COMPATIBILITY_MODE);
+ dumpSetting(s, p,
+ Settings.Global.EMERGENCY_TONE,
+ GlobalSettingsProto.EMERGENCY_TONE);
+ dumpSetting(s, p,
+ Settings.Global.CALL_AUTO_RETRY,
+ GlobalSettingsProto.CALL_AUTO_RETRY);
+ dumpSetting(s, p,
+ Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
+ GlobalSettingsProto.EMERGENCY_AFFORDANCE_NEEDED);
+ dumpSetting(s, p,
+ Settings.Global.PREFERRED_NETWORK_MODE,
+ GlobalSettingsProto.PREFERRED_NETWORK_MODE);
+ dumpSetting(s, p,
+ Settings.Global.DEBUG_APP,
+ GlobalSettingsProto.DEBUG_APP);
+ dumpSetting(s, p,
+ Settings.Global.WAIT_FOR_DEBUGGER,
+ GlobalSettingsProto.WAIT_FOR_DEBUGGER);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE,
+ GlobalSettingsProto.LOW_POWER_MODE);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL,
+ GlobalSettingsProto.LOW_POWER_MODE_TRIGGER_LEVEL);
+ dumpSetting(s, p,
+ Settings.Global.ALWAYS_FINISH_ACTIVITIES,
+ GlobalSettingsProto.ALWAYS_FINISH_ACTIVITIES);
+ dumpSetting(s, p,
+ Settings.Global.DOCK_AUDIO_MEDIA_ENABLED,
+ GlobalSettingsProto.DOCK_AUDIO_MEDIA_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.ENCODED_SURROUND_OUTPUT,
+ GlobalSettingsProto.ENCODED_SURROUND_OUTPUT);
+ dumpSetting(s, p,
+ Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+ GlobalSettingsProto.AUDIO_SAFE_VOLUME_STATE);
+ dumpSetting(s, p,
+ Settings.Global.TZINFO_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.TZINFO_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.TZINFO_UPDATE_METADATA_URL,
+ GlobalSettingsProto.TZINFO_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.SELINUX_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.SELINUX_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.SELINUX_UPDATE_METADATA_URL,
+ GlobalSettingsProto.SELINUX_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.SMS_SHORT_CODES_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.SMS_SHORT_CODES_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.SMS_SHORT_CODES_UPDATE_METADATA_URL,
+ GlobalSettingsProto.SMS_SHORT_CODES_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.APN_DB_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.APN_DB_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.APN_DB_UPDATE_METADATA_URL,
+ GlobalSettingsProto.APN_DB_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.CERT_PIN_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.CERT_PIN_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.CERT_PIN_UPDATE_METADATA_URL,
+ GlobalSettingsProto.CERT_PIN_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.INTENT_FIREWALL_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
+ GlobalSettingsProto.INTENT_FIREWALL_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.SELINUX_STATUS,
+ GlobalSettingsProto.SELINUX_STATUS);
+ dumpSetting(s, p,
+ Settings.Global.DEVELOPMENT_FORCE_RTL,
+ GlobalSettingsProto.DEVELOPMENT_FORCE_RTL);
+ dumpSetting(s, p,
+ Settings.Global.LOW_BATTERY_SOUND_TIMEOUT,
+ GlobalSettingsProto.LOW_BATTERY_SOUND_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
+ GlobalSettingsProto.WIFI_BOUNCE_DELAY_OVERRIDE_MS);
+ dumpSetting(s, p,
+ Settings.Global.POLICY_CONTROL,
+ GlobalSettingsProto.POLICY_CONTROL);
+ dumpSetting(s, p,
+ Settings.Global.ZEN_MODE,
+ GlobalSettingsProto.ZEN_MODE);
+ dumpSetting(s, p,
+ Settings.Global.ZEN_MODE_RINGER_LEVEL,
+ GlobalSettingsProto.ZEN_MODE_RINGER_LEVEL);
+ dumpSetting(s, p,
+ Settings.Global.ZEN_MODE_CONFIG_ETAG,
+ GlobalSettingsProto.ZEN_MODE_CONFIG_ETAG);
+ dumpSetting(s, p,
+ Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ GlobalSettingsProto.HEADS_UP_NOTIFICATIONS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_NAME,
+ GlobalSettingsProto.DEVICE_NAME);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_SCORING_PROVISIONED,
+ GlobalSettingsProto.NETWORK_SCORING_PROVISIONED);
+ dumpSetting(s, p,
+ Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
+ GlobalSettingsProto.REQUIRE_PASSWORD_TO_DECRYPT);
+ dumpSetting(s, p,
+ Settings.Global.ENHANCED_4G_MODE_ENABLED,
+ GlobalSettingsProto.ENHANCED_4G_MODE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.VT_IMS_ENABLED,
+ GlobalSettingsProto.VT_IMS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WFC_IMS_ENABLED,
+ GlobalSettingsProto.WFC_IMS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WFC_IMS_MODE,
+ GlobalSettingsProto.WFC_IMS_MODE);
+ dumpSetting(s, p,
+ Settings.Global.WFC_IMS_ROAMING_MODE,
+ GlobalSettingsProto.WFC_IMS_ROAMING_MODE);
+ dumpSetting(s, p,
+ Settings.Global.WFC_IMS_ROAMING_ENABLED,
+ GlobalSettingsProto.WFC_IMS_ROAMING_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.LTE_SERVICE_FORCED,
+ GlobalSettingsProto.LTE_SERVICE_FORCED);
+ dumpSetting(s, p,
+ Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
+ GlobalSettingsProto.EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_EPHEMERAL_FEATURE,
+ GlobalSettingsProto.ENABLE_EPHEMERAL_FEATURE);
+ dumpSetting(s, p,
+ Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS,
+ GlobalSettingsProto.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS);
+ dumpSetting(s, p,
+ Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
+ GlobalSettingsProto.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED);
+ dumpSetting(s, p,
+ Settings.Global.BOOT_COUNT,
+ GlobalSettingsProto.BOOT_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.SAFE_BOOT_DISALLOWED,
+ GlobalSettingsProto.SAFE_BOOT_DISALLOWED);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_DEMO_MODE,
+ GlobalSettingsProto.DEVICE_DEMO_MODE);
+ dumpSetting(s, p,
+ Settings.Global.RETAIL_DEMO_MODE_CONSTANTS,
+ GlobalSettingsProto.RETAIL_DEMO_MODE_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.DATABASE_DOWNGRADE_REASON,
+ GlobalSettingsProto.DATABASE_DOWNGRADE_REASON);
+ dumpSetting(s, p,
+ Settings.Global.CONTACTS_DATABASE_WAL_ENABLED,
+ GlobalSettingsProto.CONTACTS_DATABASE_WAL_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
+ GlobalSettingsProto.MULTI_SIM_VOICE_CALL_SUBSCRIPTION);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_VOICE_PROMPT,
+ GlobalSettingsProto.MULTI_SIM_VOICE_PROMPT);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
+ GlobalSettingsProto.MULTI_SIM_DATA_CALL_SUBSCRIPTION);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
+ GlobalSettingsProto.MULTI_SIM_SMS_SUBSCRIPTION);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_SMS_PROMPT,
+ GlobalSettingsProto.MULTI_SIM_SMS_PROMPT);
+ dumpSetting(s, p,
+ Settings.Global.NEW_CONTACT_AGGREGATOR,
+ GlobalSettingsProto.NEW_CONTACT_AGGREGATOR);
+ dumpSetting(s, p,
+ Settings.Global.CONTACT_METADATA_SYNC_ENABLED,
+ GlobalSettingsProto.CONTACT_METADATA_SYNC_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_CELLULAR_ON_BOOT,
+ GlobalSettingsProto.ENABLE_CELLULAR_ON_BOOT);
+ dumpSetting(s, p,
+ Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
+ GlobalSettingsProto.MAX_NOTIFICATION_ENQUEUE_RATE);
+ dumpSetting(s, p,
+ Settings.Global.CELL_ON,
+ GlobalSettingsProto.CELL_ON);
+ }
+
+ /** Dump a single {@link SettingsState.Setting} to a proto buf */
+ private static void dumpSetting(@NonNull SettingsState settings,
+ @NonNull ProtoOutputStream proto, String settingName, long fieldId) {
+ SettingsState.Setting setting = settings.getSettingLocked(settingName);
+ long settingsToken = proto.start(fieldId);
+ proto.write(SettingProto.ID, setting.getId());
+ proto.write(SettingProto.NAME, settingName);
+ if (setting.getPackageName() != null) {
+ proto.write(SettingProto.PKG, setting.getPackageName());
+ }
+ proto.write(SettingProto.VALUE, setting.getValue());
+ if (setting.getDefaultValue() != null) {
+ proto.write(SettingProto.DEFAULT_VALUE, setting.getDefaultValue());
+ proto.write(SettingProto.DEFAULT_FROM_SYSTEM, setting.isDefaultFromSystem());
+ }
+ proto.end(settingsToken);
+ }
+
+ static void dumpProtoSecureSettingsLocked(
+ @NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+ dumpSetting(s, p,
+ Settings.Secure.ANDROID_ID,
+ SecureSettingsProto.ANDROID_ID);
+ dumpSetting(s, p,
+ Settings.Secure.DEFAULT_INPUT_METHOD,
+ SecureSettingsProto.DEFAULT_INPUT_METHOD);
+ dumpSetting(s, p,
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+ SecureSettingsProto.SELECTED_INPUT_METHOD_SUBTYPE);
+ dumpSetting(s, p,
+ Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY,
+ SecureSettingsProto.INPUT_METHODS_SUBTYPE_HISTORY);
+ dumpSetting(s, p,
+ Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
+ SecureSettingsProto.INPUT_METHOD_SELECTOR_VISIBILITY);
+ dumpSetting(s, p,
+ Settings.Secure.VOICE_INTERACTION_SERVICE,
+ SecureSettingsProto.VOICE_INTERACTION_SERVICE);
+ dumpSetting(s, p,
+ Settings.Secure.AUTO_FILL_SERVICE,
+ SecureSettingsProto.AUTO_FILL_SERVICE);
+ dumpSetting(s, p,
+ Settings.Secure.BLUETOOTH_HCI_LOG,
+ SecureSettingsProto.BLUETOOTH_HCI_LOG);
+ dumpSetting(s, p,
+ Settings.Secure.USER_SETUP_COMPLETE,
+ SecureSettingsProto.USER_SETUP_COMPLETE);
+ dumpSetting(s, p,
+ Settings.Secure.COMPLETED_CATEGORY_PREFIX,
+ SecureSettingsProto.COMPLETED_CATEGORY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_INPUT_METHODS,
+ SecureSettingsProto.ENABLED_INPUT_METHODS);
+ dumpSetting(s, p,
+ Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
+ SecureSettingsProto.DISABLED_SYSTEM_INPUT_METHODS);
+ dumpSetting(s, p,
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ SecureSettingsProto.SHOW_IME_WITH_HARD_KEYBOARD);
+ dumpSetting(s, p,
+ Settings.Secure.ALWAYS_ON_VPN_APP,
+ SecureSettingsProto.ALWAYS_ON_VPN_APP);
+ dumpSetting(s, p,
+ Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
+ SecureSettingsProto.ALWAYS_ON_VPN_LOCKDOWN);
+ dumpSetting(s, p,
+ Settings.Secure.INSTALL_NON_MARKET_APPS,
+ SecureSettingsProto.INSTALL_NON_MARKET_APPS);
+ dumpSetting(s, p,
+ Settings.Secure.LOCATION_MODE,
+ SecureSettingsProto.LOCATION_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.LOCATION_PREVIOUS_MODE,
+ SecureSettingsProto.LOCATION_PREVIOUS_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
+ SecureSettingsProto.LOCK_TO_APP_EXIT_LOCKED);
+ dumpSetting(s, p,
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ SecureSettingsProto.LOCK_SCREEN_LOCK_AFTER_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
+ SecureSettingsProto.LOCK_SCREEN_ALLOW_REMOTE_INPUT);
+ dumpSetting(s, p,
+ Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING,
+ SecureSettingsProto.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING);
+ dumpSetting(s, p,
+ Settings.Secure.TRUST_AGENTS_INITIALIZED,
+ SecureSettingsProto.TRUST_AGENTS_INITIALIZED);
+ dumpSetting(s, p,
+ Settings.Secure.PARENTAL_CONTROL_ENABLED,
+ SecureSettingsProto.PARENTAL_CONTROL_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
+ SecureSettingsProto.PARENTAL_CONTROL_LAST_UPDATE);
+ dumpSetting(s, p,
+ Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL,
+ SecureSettingsProto.PARENTAL_CONTROL_REDIRECT_URL);
+ dumpSetting(s, p,
+ Settings.Secure.SETTINGS_CLASSNAME,
+ SecureSettingsProto.SETTINGS_CLASSNAME);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED,
+ SecureSettingsProto.TOUCH_EXPLORATION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ SecureSettingsProto.ENABLED_ACCESSIBILITY_SERVICES);
+ dumpSetting(s, p,
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ SecureSettingsProto.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
+ SecureSettingsProto.ACCESSIBILITY_SPEAK_PASSWORD);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+ SecureSettingsProto.ACCESSIBILITY_SCRIPT_INJECTION);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL,
+ SecureSettingsProto.ACCESSIBILITY_SCREEN_READER_URL);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
+ SecureSettingsProto.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SecureSettingsProto.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_LOCALE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_PRESET);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_EDGE_TYPE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_EDGE_COLOR);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_TYPEFACE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_FONT_SCALE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_DALTONIZER);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_AUTOCLICK_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+ SecureSettingsProto.ACCESSIBILITY_AUTOCLICK_DELAY);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
+ SecureSettingsProto.ACCESSIBILITY_LARGE_POINTER_ICON);
+ dumpSetting(s, p,
+ Settings.Secure.LONG_PRESS_TIMEOUT,
+ SecureSettingsProto.LONG_PRESS_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Secure.MULTI_PRESS_TIMEOUT,
+ SecureSettingsProto.MULTI_PRESS_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_PRINT_SERVICES,
+ SecureSettingsProto.ENABLED_PRINT_SERVICES);
+ dumpSetting(s, p,
+ Settings.Secure.DISABLED_PRINT_SERVICES,
+ SecureSettingsProto.DISABLED_PRINT_SERVICES);
+ dumpSetting(s, p,
+ Settings.Secure.DISPLAY_DENSITY_FORCED,
+ SecureSettingsProto.DISPLAY_DENSITY_FORCED);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_DEFAULT_RATE,
+ SecureSettingsProto.TTS_DEFAULT_RATE);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_DEFAULT_PITCH,
+ SecureSettingsProto.TTS_DEFAULT_PITCH);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_DEFAULT_SYNTH,
+ SecureSettingsProto.TTS_DEFAULT_SYNTH);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_DEFAULT_LOCALE,
+ SecureSettingsProto.TTS_DEFAULT_LOCALE);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_ENABLED_PLUGINS,
+ SecureSettingsProto.TTS_ENABLED_PLUGINS);
+ dumpSetting(s, p,
+ Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
+ SecureSettingsProto.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS);
+ dumpSetting(s, p,
+ Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
+ SecureSettingsProto.ALLOWED_GEOLOCATION_ORIGINS);
+ dumpSetting(s, p,
+ Settings.Secure.PREFERRED_TTY_MODE,
+ SecureSettingsProto.PREFERRED_TTY_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
+ SecureSettingsProto.ENHANCED_VOICE_PRIVACY_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.TTY_MODE_ENABLED,
+ SecureSettingsProto.TTY_MODE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.BACKUP_ENABLED,
+ SecureSettingsProto.BACKUP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.BACKUP_AUTO_RESTORE,
+ SecureSettingsProto.BACKUP_AUTO_RESTORE);
+ dumpSetting(s, p,
+ Settings.Secure.BACKUP_PROVISIONED,
+ SecureSettingsProto.BACKUP_PROVISIONED);
+ dumpSetting(s, p,
+ Settings.Secure.BACKUP_TRANSPORT,
+ SecureSettingsProto.BACKUP_TRANSPORT);
+ dumpSetting(s, p,
+ Settings.Secure.LAST_SETUP_SHOWN,
+ SecureSettingsProto.LAST_SETUP_SHOWN);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
+ SecureSettingsProto.SEARCH_GLOBAL_SEARCH_ACTIVITY);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_NUM_PROMOTED_SOURCES,
+ SecureSettingsProto.SEARCH_NUM_PROMOTED_SOURCES);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_RESULTS_TO_DISPLAY,
+ SecureSettingsProto.SEARCH_MAX_RESULTS_TO_DISPLAY);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE,
+ SecureSettingsProto.SEARCH_MAX_RESULTS_PER_SOURCE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_WEB_RESULTS_OVERRIDE_LIMIT,
+ SecureSettingsProto.SEARCH_WEB_RESULTS_OVERRIDE_LIMIT);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS,
+ SecureSettingsProto.SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_SOURCE_TIMEOUT_MILLIS,
+ SecureSettingsProto.SEARCH_SOURCE_TIMEOUT_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_PREFILL_MILLIS,
+ SecureSettingsProto.SEARCH_PREFILL_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_STAT_AGE_MILLIS,
+ SecureSettingsProto.SEARCH_MAX_STAT_AGE_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS,
+ SecureSettingsProto.SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING,
+ SecureSettingsProto.SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING,
+ SecureSettingsProto.SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_SHORTCUTS_RETURNED,
+ SecureSettingsProto.SEARCH_MAX_SHORTCUTS_RETURNED);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_QUERY_THREAD_CORE_POOL_SIZE,
+ SecureSettingsProto.SEARCH_QUERY_THREAD_CORE_POOL_SIZE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_QUERY_THREAD_MAX_POOL_SIZE,
+ SecureSettingsProto.SEARCH_QUERY_THREAD_MAX_POOL_SIZE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE,
+ SecureSettingsProto.SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE,
+ SecureSettingsProto.SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_THREAD_KEEPALIVE_SECONDS,
+ SecureSettingsProto.SEARCH_THREAD_KEEPALIVE_SECONDS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT,
+ SecureSettingsProto.SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT);
+ dumpSetting(s, p,
+ Settings.Secure.MOUNT_PLAY_NOTIFICATION_SND,
+ SecureSettingsProto.MOUNT_PLAY_NOTIFICATION_SND);
+ dumpSetting(s, p,
+ Settings.Secure.MOUNT_UMS_AUTOSTART,
+ SecureSettingsProto.MOUNT_UMS_AUTOSTART);
+ dumpSetting(s, p,
+ Settings.Secure.MOUNT_UMS_PROMPT,
+ SecureSettingsProto.MOUNT_UMS_PROMPT);
+ dumpSetting(s, p,
+ Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED,
+ SecureSettingsProto.MOUNT_UMS_NOTIFY_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ANR_SHOW_BACKGROUND,
+ SecureSettingsProto.ANR_SHOW_BACKGROUND);
+ dumpSetting(s, p,
+ Settings.Secure.VOICE_RECOGNITION_SERVICE,
+ SecureSettingsProto.VOICE_RECOGNITION_SERVICE);
+ dumpSetting(s, p,
+ Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
+ SecureSettingsProto.PACKAGE_VERIFIER_USER_CONSENT);
+ dumpSetting(s, p,
+ Settings.Secure.SELECTED_SPELL_CHECKER,
+ SecureSettingsProto.SELECTED_SPELL_CHECKER);
+ dumpSetting(s, p,
+ Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE,
+ SecureSettingsProto.SELECTED_SPELL_CHECKER_SUBTYPE);
+ dumpSetting(s, p,
+ Settings.Secure.SPELL_CHECKER_ENABLED,
+ SecureSettingsProto.SPELL_CHECKER_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+ SecureSettingsProto.INCALL_POWER_BUTTON_BEHAVIOR);
+ dumpSetting(s, p,
+ Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR,
+ SecureSettingsProto.INCALL_BACK_BUTTON_BEHAVIOR);
+ dumpSetting(s, p,
+ Settings.Secure.WAKE_GESTURE_ENABLED,
+ SecureSettingsProto.WAKE_GESTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DOZE_ENABLED,
+ SecureSettingsProto.DOZE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DOZE_ALWAYS_ON,
+ SecureSettingsProto.DOZE_ALWAYS_ON);
+ dumpSetting(s, p,
+ Settings.Secure.DOZE_PULSE_ON_PICK_UP,
+ SecureSettingsProto.DOZE_PULSE_ON_PICK_UP);
+ dumpSetting(s, p,
+ Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
+ SecureSettingsProto.DOZE_PULSE_ON_DOUBLE_TAP);
+ dumpSetting(s, p,
+ Settings.Secure.UI_NIGHT_MODE,
+ SecureSettingsProto.UI_NIGHT_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_ENABLED,
+ SecureSettingsProto.SCREENSAVER_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_COMPONENTS,
+ SecureSettingsProto.SCREENSAVER_COMPONENTS);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ SecureSettingsProto.SCREENSAVER_ACTIVATE_ON_DOCK);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ SecureSettingsProto.SCREENSAVER_ACTIVATE_ON_SLEEP);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
+ SecureSettingsProto.SCREENSAVER_DEFAULT_COMPONENT);
+ dumpSetting(s, p,
+ Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
+ SecureSettingsProto.NFC_PAYMENT_DEFAULT_COMPONENT);
+ dumpSetting(s, p,
+ Settings.Secure.NFC_PAYMENT_FOREGROUND,
+ SecureSettingsProto.NFC_PAYMENT_FOREGROUND);
+ dumpSetting(s, p,
+ Settings.Secure.SMS_DEFAULT_APPLICATION,
+ SecureSettingsProto.SMS_DEFAULT_APPLICATION);
+ dumpSetting(s, p,
+ Settings.Secure.DIALER_DEFAULT_APPLICATION,
+ SecureSettingsProto.DIALER_DEFAULT_APPLICATION);
+ dumpSetting(s, p,
+ Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+ SecureSettingsProto.EMERGENCY_ASSISTANCE_APPLICATION);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_STRUCTURE_ENABLED,
+ SecureSettingsProto.ASSIST_STRUCTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
+ SecureSettingsProto.ASSIST_SCREENSHOT_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_DISCLOSURE_ENABLED,
+ SecureSettingsProto.ASSIST_DISCLOSURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
+ SecureSettingsProto.ENABLED_NOTIFICATION_ASSISTANT);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+ SecureSettingsProto.ENABLED_NOTIFICATION_LISTENERS);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
+ SecureSettingsProto.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES);
+ dumpSetting(s, p,
+ Settings.Secure.SYNC_PARENT_SOUNDS,
+ SecureSettingsProto.SYNC_PARENT_SOUNDS);
+ dumpSetting(s, p,
+ Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+ SecureSettingsProto.IMMERSIVE_MODE_CONFIRMATIONS);
+ dumpSetting(s, p,
+ Settings.Secure.PRINT_SERVICE_SEARCH_URI,
+ SecureSettingsProto.PRINT_SERVICE_SEARCH_URI);
+ dumpSetting(s, p,
+ Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
+ SecureSettingsProto.PAYMENT_SERVICE_SEARCH_URI);
+ dumpSetting(s, p,
+ Settings.Secure.SKIP_FIRST_USE_HINTS,
+ SecureSettingsProto.SKIP_FIRST_USE_HINTS);
+ dumpSetting(s, p,
+ Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS,
+ SecureSettingsProto.UNSAFE_VOLUME_MUSIC_ACTIVE_MS);
+ dumpSetting(s, p,
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ SecureSettingsProto.LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ dumpSetting(s, p,
+ Settings.Secure.TV_INPUT_HIDDEN_INPUTS,
+ SecureSettingsProto.TV_INPUT_HIDDEN_INPUTS);
+ dumpSetting(s, p,
+ Settings.Secure.TV_INPUT_CUSTOM_LABELS,
+ SecureSettingsProto.TV_INPUT_CUSTOM_LABELS);
+ dumpSetting(s, p,
+ Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED,
+ SecureSettingsProto.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED);
+ dumpSetting(s, p,
+ Settings.Secure.SLEEP_TIMEOUT,
+ SecureSettingsProto.SLEEP_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Secure.DOUBLE_TAP_TO_WAKE,
+ SecureSettingsProto.DOUBLE_TAP_TO_WAKE);
+ dumpSetting(s, p,
+ Settings.Secure.ASSISTANT,
+ SecureSettingsProto.ASSISTANT);
+ dumpSetting(s, p,
+ Settings.Secure.CAMERA_GESTURE_DISABLED,
+ SecureSettingsProto.CAMERA_GESTURE_DISABLED);
+ dumpSetting(s, p,
+ Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
+ SecureSettingsProto.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED);
+ dumpSetting(s, p,
+ Settings.Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
+ SecureSettingsProto.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
+ SecureSettingsProto.NIGHT_DISPLAY_ACTIVATED);
+ dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_AUTO_MODE,
+ SecureSettingsProto.NIGHT_DISPLAY_AUTO_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
+ SecureSettingsProto.NIGHT_DISPLAY_CUSTOM_START_TIME);
+ dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME,
+ SecureSettingsProto.NIGHT_DISPLAY_CUSTOM_END_TIME);
+ dumpSetting(s, p,
+ Settings.Secure.BRIGHTNESS_USE_TWILIGHT,
+ SecureSettingsProto.BRIGHTNESS_USE_TWILIGHT);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_VR_LISTENERS,
+ SecureSettingsProto.ENABLED_VR_LISTENERS);
+ dumpSetting(s, p,
+ Settings.Secure.VR_DISPLAY_MODE,
+ SecureSettingsProto.VR_DISPLAY_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.CARRIER_APPS_HANDLED,
+ SecureSettingsProto.CARRIER_APPS_HANDLED);
+ dumpSetting(s, p,
+ Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
+ SecureSettingsProto.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_LAST_RUN);
+ dumpSetting(s, p,
+ Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
+ SecureSettingsProto.SYSTEM_NAVIGATION_KEYS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DOWNLOADS_BACKUP_ENABLED,
+ SecureSettingsProto.DOWNLOADS_BACKUP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DOWNLOADS_BACKUP_ALLOW_METERED,
+ SecureSettingsProto.DOWNLOADS_BACKUP_ALLOW_METERED);
+ dumpSetting(s, p,
+ Settings.Secure.DOWNLOADS_BACKUP_CHARGING_ONLY,
+ SecureSettingsProto.DOWNLOADS_BACKUP_CHARGING_ONLY);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DOWNLOADS_DAYS_TO_RETAIN,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_DOWNLOADS_DAYS_TO_RETAIN);
+ dumpSetting(s, p,
+ Settings.Secure.QS_TILES,
+ SecureSettingsProto.QS_TILES);
+ dumpSetting(s, p,
+ Settings.Secure.DEMO_USER_SETUP_COMPLETE,
+ SecureSettingsProto.DEMO_USER_SETUP_COMPLETE);
+ dumpSetting(s, p,
+ Settings.Secure.WEB_ACTION_ENABLED,
+ SecureSettingsProto.WEB_ACTION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DEVICE_PAIRED,
+ SecureSettingsProto.DEVICE_PAIRED);
+ }
+
+ private static void dumpProtoSystemSettingsLocked(
+ @NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+ dumpSetting(s, p,
+ Settings.System.END_BUTTON_BEHAVIOR,
+ SystemSettingsProto.END_BUTTON_BEHAVIOR);
+ dumpSetting(s, p,
+ Settings.System.ADVANCED_SETTINGS,
+ SystemSettingsProto.ADVANCED_SETTINGS);
+ dumpSetting(s, p,
+ Settings.System.BLUETOOTH_DISCOVERABILITY,
+ SystemSettingsProto.BLUETOOTH_DISCOVERABILITY);
+ dumpSetting(s, p,
+ Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ SystemSettingsProto.BLUETOOTH_DISCOVERABILITY_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.System.FONT_SCALE,
+ SystemSettingsProto.FONT_SCALE);
+ dumpSetting(s, p,
+ Settings.System.SYSTEM_LOCALES,
+ SystemSettingsProto.SYSTEM_LOCALES);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ SystemSettingsProto.SCREEN_OFF_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_BRIGHTNESS,
+ SystemSettingsProto.SCREEN_BRIGHTNESS);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
+ SystemSettingsProto.SCREEN_BRIGHTNESS_FOR_VR);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ SystemSettingsProto.SCREEN_BRIGHTNESS_MODE);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+ SystemSettingsProto.SCREEN_AUTO_BRIGHTNESS_ADJ);
+ dumpSetting(s, p,
+ Settings.System.MODE_RINGER_STREAMS_AFFECTED,
+ SystemSettingsProto.MODE_RINGER_STREAMS_AFFECTED);
+ dumpSetting(s, p,
+ Settings.System.MUTE_STREAMS_AFFECTED,
+ SystemSettingsProto.MUTE_STREAMS_AFFECTED);
+ dumpSetting(s, p,
+ Settings.System.VIBRATE_ON,
+ SystemSettingsProto.VIBRATE_ON);
+ dumpSetting(s, p,
+ Settings.System.VIBRATE_INPUT_DEVICES,
+ SystemSettingsProto.VIBRATE_INPUT_DEVICES);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_RING,
+ SystemSettingsProto.VOLUME_RING);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_SYSTEM,
+ SystemSettingsProto.VOLUME_SYSTEM);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_VOICE,
+ SystemSettingsProto.VOLUME_VOICE);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_MUSIC,
+ SystemSettingsProto.VOLUME_MUSIC);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_ALARM,
+ SystemSettingsProto.VOLUME_ALARM);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_NOTIFICATION,
+ SystemSettingsProto.VOLUME_NOTIFICATION);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_BLUETOOTH_SCO,
+ SystemSettingsProto.VOLUME_BLUETOOTH_SCO);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_MASTER,
+ SystemSettingsProto.VOLUME_MASTER);
+ dumpSetting(s, p,
+ Settings.System.MASTER_MONO,
+ SystemSettingsProto.MASTER_MONO);
+ dumpSetting(s, p,
+ Settings.System.VIBRATE_IN_SILENT,
+ SystemSettingsProto.VIBRATE_IN_SILENT);
+ dumpSetting(s, p,
+ Settings.System.APPEND_FOR_LAST_AUDIBLE,
+ SystemSettingsProto.APPEND_FOR_LAST_AUDIBLE);
+ dumpSetting(s, p,
+ Settings.System.RINGTONE,
+ SystemSettingsProto.RINGTONE);
+ dumpSetting(s, p,
+ Settings.System.RINGTONE_CACHE,
+ SystemSettingsProto.RINGTONE_CACHE);
+ dumpSetting(s, p,
+ Settings.System.NOTIFICATION_SOUND,
+ SystemSettingsProto.NOTIFICATION_SOUND);
+ dumpSetting(s, p,
+ Settings.System.NOTIFICATION_SOUND_CACHE,
+ SystemSettingsProto.NOTIFICATION_SOUND_CACHE);
+ dumpSetting(s, p,
+ Settings.System.ALARM_ALERT,
+ SystemSettingsProto.ALARM_ALERT);
+ dumpSetting(s, p,
+ Settings.System.ALARM_ALERT_CACHE,
+ SystemSettingsProto.ALARM_ALERT_CACHE);
+ dumpSetting(s, p,
+ Settings.System.MEDIA_BUTTON_RECEIVER,
+ SystemSettingsProto.MEDIA_BUTTON_RECEIVER);
+ dumpSetting(s, p,
+ Settings.System.TEXT_AUTO_REPLACE,
+ SystemSettingsProto.TEXT_AUTO_REPLACE);
+ dumpSetting(s, p,
+ Settings.System.TEXT_AUTO_CAPS,
+ SystemSettingsProto.TEXT_AUTO_CAPS);
+ dumpSetting(s, p,
+ Settings.System.TEXT_AUTO_PUNCTUATE,
+ SystemSettingsProto.TEXT_AUTO_PUNCTUATE);
+ dumpSetting(s, p,
+ Settings.System.TEXT_SHOW_PASSWORD,
+ SystemSettingsProto.TEXT_SHOW_PASSWORD);
+ dumpSetting(s, p,
+ Settings.System.SHOW_GTALK_SERVICE_STATUS,
+ SystemSettingsProto.SHOW_GTALK_SERVICE_STATUS);
+ dumpSetting(s, p,
+ Settings.System.TIME_12_24,
+ SystemSettingsProto.TIME_12_24);
+ dumpSetting(s, p,
+ Settings.System.DATE_FORMAT,
+ SystemSettingsProto.DATE_FORMAT);
+ dumpSetting(s, p,
+ Settings.System.SETUP_WIZARD_HAS_RUN,
+ SystemSettingsProto.SETUP_WIZARD_HAS_RUN);
+ dumpSetting(s, p,
+ Settings.System.ACCELEROMETER_ROTATION,
+ SystemSettingsProto.ACCELEROMETER_ROTATION);
+ dumpSetting(s, p,
+ Settings.System.USER_ROTATION,
+ SystemSettingsProto.USER_ROTATION);
+ dumpSetting(s, p,
+ Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
+ SystemSettingsProto.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY);
+ dumpSetting(s, p,
+ Settings.System.VIBRATE_WHEN_RINGING,
+ SystemSettingsProto.VIBRATE_WHEN_RINGING);
+ dumpSetting(s, p,
+ Settings.System.DTMF_TONE_WHEN_DIALING,
+ SystemSettingsProto.DTMF_TONE_WHEN_DIALING);
+ dumpSetting(s, p,
+ Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
+ SystemSettingsProto.DTMF_TONE_TYPE_WHEN_DIALING);
+ dumpSetting(s, p,
+ Settings.System.HEARING_AID,
+ SystemSettingsProto.HEARING_AID);
+ dumpSetting(s, p,
+ Settings.System.TTY_MODE,
+ SystemSettingsProto.TTY_MODE);
+ dumpSetting(s, p,
+ Settings.System.SOUND_EFFECTS_ENABLED,
+ SystemSettingsProto.SOUND_EFFECTS_ENABLED);
+ dumpSetting(s, p,
+ Settings.System.HAPTIC_FEEDBACK_ENABLED,
+ SystemSettingsProto.HAPTIC_FEEDBACK_ENABLED);
+ dumpSetting(s, p,
+ Settings.System.NOTIFICATION_LIGHT_PULSE,
+ SystemSettingsProto.NOTIFICATION_LIGHT_PULSE);
+ dumpSetting(s, p,
+ Settings.System.POINTER_LOCATION,
+ SystemSettingsProto.POINTER_LOCATION);
+ dumpSetting(s, p,
+ Settings.System.SHOW_TOUCHES,
+ SystemSettingsProto.SHOW_TOUCHES);
+ dumpSetting(s, p,
+ Settings.System.WINDOW_ORIENTATION_LISTENER_LOG,
+ SystemSettingsProto.WINDOW_ORIENTATION_LISTENER_LOG);
+ dumpSetting(s, p,
+ Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
+ SystemSettingsProto.LOCKSCREEN_SOUNDS_ENABLED);
+ dumpSetting(s, p,
+ Settings.System.LOCKSCREEN_DISABLED,
+ SystemSettingsProto.LOCKSCREEN_DISABLED);
+ dumpSetting(s, p,
+ Settings.System.SIP_RECEIVE_CALLS,
+ SystemSettingsProto.SIP_RECEIVE_CALLS);
+ dumpSetting(s, p,
+ Settings.System.SIP_CALL_OPTIONS,
+ SystemSettingsProto.SIP_CALL_OPTIONS);
+ dumpSetting(s, p,
+ Settings.System.SIP_ALWAYS,
+ SystemSettingsProto.SIP_ALWAYS);
+ dumpSetting(s, p,
+ Settings.System.SIP_ADDRESS_ONLY,
+ SystemSettingsProto.SIP_ADDRESS_ONLY);
+ dumpSetting(s, p,
+ Settings.System.POINTER_SPEED,
+ SystemSettingsProto.POINTER_SPEED);
+ dumpSetting(s, p,
+ Settings.System.LOCK_TO_APP_ENABLED,
+ SystemSettingsProto.LOCK_TO_APP_ENABLED);
+ dumpSetting(s, p,
+ Settings.System.EGG_MODE,
+ SystemSettingsProto.EGG_MODE);
+ dumpSetting(s, p,
+ Settings.System.WHEN_TO_MAKE_WIFI_CALLS,
+ SystemSettingsProto.WHEN_TO_MAKE_WIFI_CALLS);
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 527631e..6979995 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -17,6 +17,7 @@
package com.android.providers.settings;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.backup.BackupManager;
@@ -65,6 +66,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
@@ -90,8 +92,9 @@
import java.util.regex.Pattern;
import static android.os.Process.ROOT_UID;
-import static android.os.Process.SYSTEM_UID;
import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+
/**
* <p>
@@ -601,6 +604,22 @@
return cacheDir;
}
+ /**
+ * Dump all settings as a proto buf.
+ *
+ * @param fd The file to dump to
+ */
+ void dumpProto(@NonNull FileDescriptor fd) {
+ ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ synchronized (mLock) {
+ SettingsProtoDumpUtil.dumpProtoLocked(mSettingsRegistry, proto);
+
+ }
+
+ proto.flush();
+ }
+
public void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
final long identity = Binder.clearCallingIdentity();
@@ -663,7 +682,7 @@
pw.print(" value:"); pw.print(toDumpString(setting.getValue()));
if (setting.getDefaultValue() != null) {
pw.print(" default:"); pw.print(setting.getDefaultValue());
- pw.print(" defaultSystemSet:"); pw.print(setting.isDefaultSystemSet());
+ pw.print(" defaultSystemSet:"); pw.print(setting.isDefaultFromSystem());
}
if (setting.getTag() != null) {
pw.print(" tag:"); pw.print(setting.getTag());
@@ -2296,7 +2315,7 @@
Setting setting = settingsState.getSettingLocked(name);
if (!SettingsState.isSystemPackage(getContext(),
setting.getPackageName())) {
- if (setting.isDefaultSystemSet()) {
+ if (setting.isDefaultFromSystem()) {
if (settingsState.resetSettingLocked(name, packageName)) {
notifyForSettingsChange(key, name);
}
@@ -2310,7 +2329,7 @@
case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
for (String name : settingsState.getSettingNamesLocked()) {
Setting setting = settingsState.getSettingLocked(name);
- if (setting.isDefaultSystemSet()) {
+ if (setting.isDefaultFromSystem()) {
if (settingsState.resetSettingLocked(name, packageName)) {
notifyForSettingsChange(key, name);
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
index fecc938..2d59324 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
@@ -65,6 +65,7 @@
}
int opti = 0;
+ boolean dumpAsProto = false;
while (opti < args.length) {
String opt = args[opti];
if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
@@ -74,16 +75,22 @@
if ("-h".equals(opt)) {
MyShellCommand.dumpHelp(pw, true);
return;
+ } else if ("--proto".equals(opt)) {
+ dumpAsProto = true;
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
}
}
- long caller = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
- mProvider.dumpInternal(fd, pw, args);
+ if (dumpAsProto) {
+ mProvider.dumpProto(fd);
+ } else {
+ mProvider.dumpInternal(fd, pw, args);
+ }
} finally {
- Binder.restoreCallingIdentity(caller);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -449,8 +456,9 @@
static void dumpHelp(PrintWriter pw, boolean dumping) {
if (dumping) {
pw.println("Settings provider dump options:");
- pw.println(" [-h]");
+ pw.println(" [-h] [--proto]");
pw.println(" -h: print this help.");
+ pw.println(" --proto: dump as protobuf.");
} else {
pw.println("Settings provider (settings) commands:");
pw.println(" help");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 8f37b98..a74be35 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -16,6 +16,9 @@
package com.android.providers.settings;
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -30,6 +33,8 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.providers.settings.GlobalSettingsProto;
+import android.providers.settings.SettingsOperationProto;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
@@ -38,10 +43,14 @@
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
+
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
+
import libcore.io.IoUtils;
import libcore.util.Objects;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -56,8 +65,6 @@
import java.util.ArrayList;
import java.util.List;
-import static android.os.Process.FIRST_APPLICATION_UID;
-
/**
* This class contains the state for one type of settings. It is responsible
* for saving the state asynchronously to an XML file after a mutation and
@@ -404,6 +411,38 @@
}
}
+ /**
+ * Dump historical operations as a proto buf.
+ *
+ * @param proto The proto buf stream to dump to
+ */
+ void dumpProtoHistoricalOperations(@NonNull ProtoOutputStream proto) {
+ synchronized (mLock) {
+ if (mHistoricalOperations == null) {
+ return;
+ }
+
+ final int operationCount = mHistoricalOperations.size();
+ for (int i = 0; i < operationCount; i++) {
+ int index = mNextHistoricalOpIdx - 1 - i;
+ if (index < 0) {
+ index = operationCount + index;
+ }
+ HistoricalOperation operation = mHistoricalOperations.get(index);
+ long settingsOperationToken = proto.start(GlobalSettingsProto.HISTORICAL_OP);
+ proto.write(SettingsOperationProto.TIMESTAMP, operation.mTimestamp);
+ proto.write(SettingsOperationProto.OPERATION, operation.mOperation);
+ if (operation.mSetting != null) {
+ // Only add the name of the setting, since we don't know the historical package
+ // and values for it so they would be misleading to add here (all we could
+ // add is what the current data is).
+ proto.write(SettingsOperationProto.SETTING, operation.mSetting.getName());
+ }
+ proto.end(settingsOperationToken);
+ }
+ }
+ }
+
public void dumpHistoricalOperations(PrintWriter pw) {
synchronized (mLock) {
if (mHistoricalOperations == null) {
@@ -544,7 +583,7 @@
writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
- setting.getTag(), setting.isDefaultSystemSet());
+ setting.getTag(), setting.isDefaultFromSystem());
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue());
@@ -763,7 +802,7 @@
private String id;
private String tag;
// Whether the default is set by the system
- private boolean defaultSystemSet;
+ private boolean defaultFromSystem;
public Setting(Setting other) {
name = other.name;
@@ -771,7 +810,7 @@
defaultValue = other.defaultValue;
packageName = other.packageName;
id = other.id;
- defaultSystemSet = other.defaultSystemSet;
+ defaultFromSystem = other.defaultFromSystem;
tag = other.tag;
}
@@ -798,7 +837,7 @@
this.defaultValue = defaultValue;
this.packageName = packageName;
this.id = id;
- this.defaultSystemSet = fromSystem;
+ this.defaultFromSystem = fromSystem;
}
public String getName() {
@@ -825,8 +864,8 @@
return packageName;
}
- public boolean isDefaultSystemSet() {
- return defaultSystemSet;
+ public boolean isDefaultFromSystem() {
+ return defaultFromSystem;
}
public String getId() {
@@ -854,22 +893,22 @@
}
String defaultValue = this.defaultValue;
- boolean defaultSystemSet = this.defaultSystemSet;
+ boolean defaultFromSystem = this.defaultFromSystem;
if (setDefault) {
if (!Objects.equal(value, this.defaultValue)
- && (!defaultSystemSet || callerSystem)) {
+ && (!defaultFromSystem || callerSystem)) {
defaultValue = value;
// Default null means no default, so the tag is irrelevant
// since it is used to reset a settings subset their defaults.
// Also it is irrelevant if the system set the canonical default.
if (defaultValue == null) {
tag = null;
- defaultSystemSet = false;
+ defaultFromSystem = false;
}
}
- if (!defaultSystemSet && value != null) {
+ if (!defaultFromSystem && value != null) {
if (callerSystem) {
- defaultSystemSet = true;
+ defaultFromSystem = true;
}
}
}
@@ -879,11 +918,11 @@
&& Objects.equal(defaultValue, this.defaultValue)
&& Objects.equal(packageName, this.packageName)
&& Objects.equal(tag, this.tag)
- && defaultSystemSet == this.defaultSystemSet) {
+ && defaultFromSystem == this.defaultFromSystem) {
return false;
}
- init(name, value, tag, defaultValue, packageName, defaultSystemSet,
+ init(name, value, tag, defaultValue, packageName, defaultFromSystem,
String.valueOf(mNextId++));
return true;
}
@@ -892,7 +931,7 @@
return "Setting{name=" + name + " value=" + value
+ (defaultValue != null ? " default=" + defaultValue : "")
+ " packageName=" + packageName + " tag=" + tag
- + " defaultSystemSet=" + defaultSystemSet + "}";
+ + " defaultFromSystem=" + defaultFromSystem + "}";
}
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index fede34d..f72d091 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -131,6 +131,9 @@
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
+ <!-- Doze mode temp whitelisting for notification dispatching. -->
+ <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
+
<!-- Listen for keyboard attachment / detachment -->
<uses-permission android:name="android.permission.TABLET_MODE" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
index a9d1fa9..152dbc5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
@@ -14,17 +14,13 @@
package com.android.systemui.plugins;
-import android.annotation.Nullable;
import android.app.Fragment;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.view.LayoutInflater;
public abstract class PluginFragment extends Fragment implements Plugin {
- private static final String KEY_PLUGIN_PACKAGE = "plugin_package_name";
private Context mPluginContext;
@Override
@@ -33,45 +29,17 @@
}
@Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- Context sysuiContext = getContext();
- Context pluginContext = recreatePluginContext(sysuiContext, savedInstanceState);
- onCreate(sysuiContext, pluginContext);
- }
- if (mPluginContext == null) {
- throw new RuntimeException("PluginFragments must call super.onCreate("
- + "Context sysuiContext, Context pluginContext)");
- }
+ public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
+ return super.getLayoutInflater(savedInstanceState).cloneInContext(getContext());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putString(KEY_PLUGIN_PACKAGE, getContext().getPackageName());
}
- private Context recreatePluginContext(Context sysuiContext, Bundle savedInstanceState) {
- final String pkg = savedInstanceState.getString(KEY_PLUGIN_PACKAGE);
- try {
- ApplicationInfo appInfo = sysuiContext.getPackageManager().getApplicationInfo(pkg, 0);
- return PluginManager.getInstance(sysuiContext).getContext(appInfo, pkg);
- } catch (NameNotFoundException e) {
- throw new RuntimeException("Plugin with invalid package? " + pkg, e);
- }
- }
-
- @Override
- public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
- return super.getLayoutInflater(savedInstanceState).cloneInContext(mPluginContext);
- }
-
- /**
- * Should only be called after {@link Plugin#onCreate(Context, Context)}.
- */
@Override
public Context getContext() {
- return mPluginContext != null ? mPluginContext : super.getContext();
+ return mPluginContext;
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
index 47b97bd..9f44bd4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -177,8 +177,12 @@
if (DEBUG) Log.d(TAG, "onPluginConnected");
PluginPrefs.setHasPlugins(mContext);
PluginInfo<T> info = (PluginInfo<T>) msg.obj;
- info.mPlugin.onCreate(mContext, info.mPluginContext);
- mListener.onPluginConnected(info.mPlugin);
+ if (!(msg.obj instanceof PluginFragment)) {
+ // Only call onDestroy for plugins that aren't fragments, as fragments
+ // will get the onCreate as part of the fragment lifecycle.
+ info.mPlugin.onCreate(mContext, info.mPluginContext);
+ }
+ mListener.onPluginConnected(info.mPlugin, info.mPluginContext);
break;
case PLUGIN_DISCONNECTED:
if (DEBUG) Log.d(TAG, "onPluginDisconnected");
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
index b2f92d6..b488d2a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
@@ -14,6 +14,8 @@
package com.android.systemui.plugins;
+import android.content.Context;
+
/**
* Interface for listening to plugins being connected.
*/
@@ -24,7 +26,7 @@
* It may also be called in the future if the plugin package changes
* and needs to be reloaded.
*/
- void onPluginConnected(T plugin);
+ void onPluginConnected(T plugin, Context pluginContext);
/**
* Called when a plugin has been uninstalled/updated and should be removed
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 a9874fc..e21a282 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
@@ -37,7 +37,7 @@
// This should be incremented any time this class or ActivityStarter or BaseStatusBarHeader
// change in incompatible ways.
- public static final int VERSION = 4;
+ public static final int VERSION = 5;
String TAG = "QS";
@@ -105,24 +105,8 @@
public abstract void setExpansion(float headerExpansionFraction);
public abstract void setListening(boolean listening);
public abstract void updateEverything();
- public abstract void setActivityStarter(ActivityStarter activityStarter);
public abstract void setCallback(Callback qsPanelCallback);
public abstract View getExpandView();
}
- /**
- * An interface to start activities. This is used to as a callback from the views to
- * {@link PhoneStatusBar} to allow custom handling for starting the activity, i.e. dismissing the
- * Keyguard.
- */
- public static interface ActivityStarter {
-
- void startPendingIntentDismissingKeyguard(PendingIntent intent);
- void startActivity(Intent intent, boolean dismissShade);
- void startActivity(Intent intent, boolean dismissShade, Callback callback);
-
- interface Callback {
- void onActivityStarted(int resultCode);
- }
- }
}
diff --git a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
index 439ee3b..6264484 100644
--- a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
@@ -16,8 +16,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="17dp"
android:height="17.0dp"
- android:viewportWidth="20.0"
- android:viewportHeight="20.0">
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19.0,6.41L17.59,5.0 12.0,10.59 6.41,5.0 5.0,6.41 10.59,12.0 5.0,17.59 6.41,19.0 12.0,13.41 17.59,19.0 19.0,17.59 13.41,12.0z"/>
diff --git a/packages/SystemUI/res/drawable/ic_volume_accessibility.xml b/packages/SystemUI/res/drawable/ic_volume_accessibility.xml
new file mode 100644
index 0000000..657efaa
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_accessibility.xml
@@ -0,0 +1,25 @@
+<!--
+ 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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1s-5.89,-0.3 -8.5,-1L3,8c1.86,0.5 4,0.83 6,1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1l-0.5,-2zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml b/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml
index 694019e9..ea794d4 100644
--- a/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml
@@ -14,9 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="8.5dp"
+ android:autoMirrored="true"
+ android:width="17.0dp"
android:height="17.0dp"
- android:viewportWidth="20.0"
+ android:viewportWidth="40.0"
android:viewportHeight="40.0">
<path
android:fillColor="#FFFFFFFF"
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 4122707..174776c 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -148,7 +148,7 @@
android:layout_alignParentTop="true"
android:focusable="true"
android:gravity="center_vertical"
- android:paddingEnd="16dp"
+ android:paddingStart="16dp"
android:paddingTop="6dp"
android:singleLine="true"
android:text="@*android:string/emergency_calls_only"
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 5ee242d..789b765 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -23,7 +23,6 @@
android:layout_gravity="top|center_horizontal">
<com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/icon"
- android:contentDescription="@string/recents_app_info_button_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b18b6ac..3040814 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -75,6 +75,9 @@
<!-- The color of the material notification background when dimmed -->
<color name="notification_material_background_dimmed_color">#ccffffff</color>
+ <!-- The color of the material notification background when dark -->
+ <color name="notification_material_background_dark_color">#ff333333</color>
+
<!-- The color of the material notification background when low priority -->
<color name="notification_material_background_low_priority_color">#fff5f5f5</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ac86439..d6ed9d8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -291,7 +291,7 @@
<bool name="quick_settings_show_full_alarm">false</bool>
<!-- Whether to show a warning notification when the device reaches a certain temperature. -->
- <bool name="config_showTemperatureWarning">false</bool>
+ <integer name="config_showTemperatureWarning">0</integer>
<!-- Temp at which to show a warning notification if config_showTemperatureWarning is true.
If < 0, uses the value from HardwarePropertiesManager#getDeviceTemperatures. -->
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarter.java b/packages/SystemUI/src/com/android/systemui/ActivityStarter.java
new file mode 100644
index 0000000..a4d8a10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarter.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.systemui;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+
+/**
+ * An interface to start activities. This is used as a callback from the views to
+ * {@link PhoneStatusBar} to allow custom handling for starting the activity, i.e. dismissing the
+ * Keyguard.
+ */
+public interface ActivityStarter {
+
+ void startPendingIntentDismissingKeyguard(PendingIntent intent);
+ void startActivity(Intent intent, boolean dismissShade);
+ void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade);
+ void startActivity(Intent intent, boolean dismissShade, Callback callback);
+ void postStartActivityDismissingKeyguard(Intent intent, int delay);
+ void postStartActivityDismissingKeyguard(PendingIntent intent);
+ void postQSRunnableDismissingKeyguard(Runnable runnable);
+
+ interface Callback {
+ void onActivityStarted(int resultCode);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
new file mode 100644
index 0000000..4ae81a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+
+/**
+ * Single common instance of ActivityStarter that can be gotten and referenced from anywhere, but
+ * delegates to an actual implementation such as PhoneStatusBar, assuming it exists.
+ */
+public class ActivityStarterDelegate implements ActivityStarter {
+
+ private ActivityStarter mActualStarter;
+
+ @Override
+ public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
+ if (mActualStarter == null) return;
+ mActualStarter.startPendingIntentDismissingKeyguard(intent);
+ }
+
+ @Override
+ public void startActivity(Intent intent, boolean dismissShade) {
+ if (mActualStarter == null) return;
+ mActualStarter.startActivity(intent, dismissShade);
+ }
+
+ @Override
+ public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) {
+ if (mActualStarter == null) return;
+ mActualStarter.startActivity(intent, onlyProvisioned, dismissShade);
+ }
+
+ @Override
+ public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
+ if (mActualStarter == null) return;
+ mActualStarter.startActivity(intent, dismissShade, callback);
+ }
+
+ @Override
+ public void postStartActivityDismissingKeyguard(Intent intent, int delay) {
+ if (mActualStarter == null) return;
+ mActualStarter.postStartActivityDismissingKeyguard(intent, delay);
+ }
+
+ @Override
+ public void postStartActivityDismissingKeyguard(PendingIntent intent) {
+ if (mActualStarter == null) return;
+ mActualStarter.postStartActivityDismissingKeyguard(intent);
+ }
+
+ @Override
+ public void postQSRunnableDismissingKeyguard(Runnable runnable) {
+ if (mActualStarter == null) return;
+ mActualStarter.postQSRunnableDismissingKeyguard(runnable);
+ }
+
+ public void setActivityStarterImpl(ActivityStarter starter) {
+ mActualStarter = starter;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 030250a..b30b596 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -72,6 +72,10 @@
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
+ mBatteryController = Dependency.get(BatteryController.class);
+ mDrawable.setBatteryController(mBatteryController);
+ mBatteryController.addCallback(this);
+ mDrawable.startListening();
TunerService.get(getContext()).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
}
@@ -95,13 +99,6 @@
}
- public void setBatteryController(BatteryController mBatteryController) {
- this.mBatteryController = mBatteryController;
- mDrawable.setBatteryController(mBatteryController);
- mBatteryController.addCallback(this);
- mDrawable.startListening();
- }
-
public void setDarkIntensity(float f) {
mDrawable.setDarkIntensity(f);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ConfigurationChangedReceiver.java b/packages/SystemUI/src/com/android/systemui/ConfigurationChangedReceiver.java
new file mode 100644
index 0000000..4fba640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ConfigurationChangedReceiver.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.res.Configuration;
+
+public interface ConfigurationChangedReceiver {
+ void onConfigurationChanged(Configuration newConfiguration);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
new file mode 100644
index 0000000..135b129
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -0,0 +1,230 @@
+/*
+ * 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;
+
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
+import com.android.systemui.statusbar.policy.AccessibilityController;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.CastControllerImpl;
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.HotspotControllerImpl;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.LocationControllerImpl;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Class to handle ugly dependencies throughout sysui until we determine the
+ * long-term dependency injection solution.
+ *
+ * Classes added here should be things that are expected to live the lifetime of sysui,
+ * and are generally applicable to many parts of sysui. They will be lazily
+ * initialized to ensure they aren't created on form factors that don't need them
+ * (e.g. HotspotController on TV). Despite being lazily initialized, it is expected
+ * that all dependencies will be gotten during sysui startup, and not during runtime
+ * to avoid jank.
+ *
+ * All classes used here are expected to manage their own lifecycle, meaning if
+ * they have no clients they should not have any registered resources like bound
+ * services, registered receivers, etc.
+ */
+public class Dependency extends SystemUI {
+
+ /**
+ * Key for getting a background Looper for background work.
+ */
+ public static final String BG_LOOPER = "background_loooper";
+ /**
+ * Key for getting a Handler for receiving time tick broadcasts on.
+ */
+ public static final String TIME_TICK_HANDLER = "time_tick_handler";
+ /**
+ * Generic handler on the main thread.
+ */
+ public static final String MAIN_HANDLER = "main_handler";
+
+ private final ArrayMap<String, Object> mDependencies = new ArrayMap<>();
+ private final ArrayMap<String, DependencyProvider> mProviders = new ArrayMap<>();
+
+ @Override
+ public void start() {
+ sDependency = this;
+ // TODO: Think about ways to push these creation rules out of Dependency to cut down
+ // on imports.
+ mProviders.put(TIME_TICK_HANDLER, () -> {
+ HandlerThread thread = new HandlerThread("TimeTick");
+ thread.start();
+ return new Handler(thread.getLooper());
+ });
+ mProviders.put(BG_LOOPER, () -> {
+ HandlerThread thread = new HandlerThread("SysUiBg",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ return thread.getLooper();
+ });
+ mProviders.put(MAIN_HANDLER, () -> new Handler(Looper.getMainLooper()));
+ mProviders.put(ActivityStarter.class.getName(), () -> new ActivityStarterDelegate());
+ mProviders.put(ActivityStarterDelegate.class.getName(), () ->
+ getDependency(ActivityStarter.class));
+
+ mProviders.put(BluetoothController.class.getName(), () ->
+ new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER)));
+
+ mProviders.put(LocationController.class.getName(), () ->
+ new LocationControllerImpl(mContext, getDependency(BG_LOOPER)));
+
+ mProviders.put(RotationLockController.class.getName(), () ->
+ new RotationLockControllerImpl(mContext));
+
+ mProviders.put(NetworkController.class.getName(), () ->
+ new NetworkControllerImpl(mContext, getDependency(BG_LOOPER),
+ getDependency(DeviceProvisionedController.class)));
+
+ mProviders.put(ZenModeController.class.getName(), () ->
+ new ZenModeControllerImpl(mContext, getDependency(MAIN_HANDLER)));
+
+ mProviders.put(HotspotController.class.getName(), () ->
+ new HotspotControllerImpl(mContext));
+
+ mProviders.put(CastController.class.getName(), () ->
+ new CastControllerImpl(mContext));
+
+ mProviders.put(FlashlightController.class.getName(), () ->
+ new FlashlightControllerImpl(mContext));
+
+ mProviders.put(KeyguardMonitor.class.getName(), () ->
+ new KeyguardMonitorImpl(mContext));
+
+ mProviders.put(UserSwitcherController.class.getName(), () ->
+ new UserSwitcherController(mContext, getDependency(KeyguardMonitor.class),
+ getDependency(MAIN_HANDLER), getDependency(ActivityStarter.class)));
+
+ mProviders.put(UserInfoController.class.getName(), () ->
+ new UserInfoControllerImpl(mContext));
+
+ mProviders.put(BatteryController.class.getName(), () ->
+ new BatteryControllerImpl(mContext));
+
+ mProviders.put(ManagedProfileController.class.getName(), () ->
+ new ManagedProfileControllerImpl(mContext));
+
+ mProviders.put(NextAlarmController.class.getName(), () ->
+ new NextAlarmControllerImpl(mContext));
+
+ mProviders.put(DataSaverController.class.getName(), () ->
+ get(NetworkController.class).getDataSaverController());
+
+ mProviders.put(AccessibilityController.class.getName(), () ->
+ new AccessibilityController(mContext));
+
+ mProviders.put(DeviceProvisionedController.class.getName(), () ->
+ new DeviceProvisionedControllerImpl(mContext));
+
+ mProviders.put(AssistManager.class.getName(), () ->
+ new AssistManager(getDependency(DeviceProvisionedController.class), mContext));
+
+ mProviders.put(SecurityController.class.getName(), () ->
+ new SecurityControllerImpl(mContext));
+
+ // Put all dependencies above here so the factory can override them if it wants.
+ SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println("Dumping existing controllers:");
+ mDependencies.values().stream().filter(obj -> obj instanceof Dumpable)
+ .forEach(o -> ((Dumpable) o).dump(fd, pw, args));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDependencies.values().stream().filter(obj -> obj instanceof ConfigurationChangedReceiver)
+ .forEach(o -> ((ConfigurationChangedReceiver) o).onConfigurationChanged(newConfig));
+ }
+
+ protected final <T> T getDependency(Class<T> cls) {
+ return getDependency(cls.getName());
+ }
+
+ protected final <T> T getDependency(String cls) {
+ T obj = (T) mDependencies.get(cls);
+ if (obj == null) {
+ obj = createDependency(cls);
+ mDependencies.put(cls, obj);
+ }
+ return obj;
+ }
+
+ @VisibleForTesting
+ protected <T> T createDependency(String cls) {
+ DependencyProvider<T> provider = mProviders.get(cls);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unsupported dependency " + cls);
+ }
+ return provider.createDependency();
+ }
+
+ private static Dependency sDependency;
+
+ public interface DependencyProvider<T> {
+ T createDependency();
+ }
+
+ public static <T> T get(Class<T> cls) {
+ return sDependency.getDependency(cls.getName());
+ }
+
+ public static <T> T get(String cls) {
+ return sDependency.getDependency(cls);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dumpable.java b/packages/SystemUI/src/com/android/systemui/Dumpable.java
new file mode 100644
index 0000000..65a6844
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/Dumpable.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public interface Dumpable {
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index efa7cae..efddf20 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -98,7 +98,7 @@
}
@Override
- public void onPluginConnected(ViewProvider plugin) {
+ public void onPluginConnected(ViewProvider plugin, Context context) {
mPluginView = plugin.getView();
inflateLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUiServiceProvider.java b/packages/SystemUI/src/com/android/systemui/SysUiServiceProvider.java
index c4470cd..ff4b7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUiServiceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUiServiceProvider.java
@@ -14,10 +14,16 @@
package com.android.systemui;
+import android.content.Context;
+
/**
* The interface for getting core components of SysUI. Exists for Testability
* since tests don't have SystemUIApplication as their ApplicationContext.
*/
public interface SysUiServiceProvider {
<T> T getComponent(Class<T> interfaceType);
+
+ public static <T> T getComponent(Context context, Class<T> interfaceType) {
+ return ((SysUiServiceProvider) context.getApplicationContext()).getComponent(interfaceType);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index bd4e3dc..f2aaec1 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -29,7 +29,6 @@
import android.os.UserHandle;
import android.util.Log;
-import com.android.systemui.doze.DozeFactory;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyboard.KeyboardUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -64,6 +63,7 @@
* The classes of the stuff to start.
*/
private final Class<?>[] SERVICES = new Class[] {
+ Dependency.class,
FragmentService.class,
TunerService.class,
CommandQueue.CommandQueueStart.class,
@@ -207,7 +207,7 @@
PluginManager.getInstance(this).addPluginListener(OverlayPlugin.ACTION,
new PluginListener<OverlayPlugin>() {
@Override
- public void onPluginConnected(OverlayPlugin plugin) {
+ public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
if (phoneStatusBar != null) {
plugin.setup(phoneStatusBar.getStatusBarWindow(),
@@ -239,8 +239,4 @@
public SystemUI[] getServices() {
return mServices;
}
-
- public static <T> T getComponent(Context context, Class<T> interfaceType) {
- return ((SysUiServiceProvider) context.getApplicationContext()).getComponent(interfaceType);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 10328a4..ec11812 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -18,19 +18,23 @@
import android.content.ComponentName;
import android.content.Context;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -113,25 +117,20 @@
return new NotificationIconAreaController(context, phoneStatusBar);
}
+ public KeyguardIndicationController createKeyguardIndicationController(Context context,
+ ViewGroup indicationArea, LockIcon lockIcon) {
+ return new KeyguardIndicationController(context, indicationArea, lockIcon);
+ }
+
public QSTileHost createQSTileHost(Context context, PhoneStatusBar statusBar,
- BluetoothController bluetooth, LocationController location,
- RotationLockController rotation, NetworkController network,
- ZenModeController zen, HotspotController hotspot,
- CastController cast, FlashlightController flashlight,
- UserSwitcherController userSwitcher, UserInfoController userInfo,
- KeyguardMonitor keyguard, SecurityController security,
- BatteryController battery, StatusBarIconController iconController,
- NextAlarmController nextAlarmController) {
- return new QSTileHost(context, statusBar, bluetooth, location, rotation, network, zen,
- hotspot, cast, flashlight, userSwitcher, userInfo, keyguard, security, battery,
- iconController, nextAlarmController);
+ StatusBarIconController iconController) {
+ return new QSTileHost(context, statusBar, iconController);
}
public <T> T createInstance(Class<T> classType) {
return null;
}
- public AssistManager createAssistManager(BaseStatusBar bar, Context context) {
- return new AssistManager(bar, context);
- }
+ public void injectDependencies(ArrayMap<String, DependencyProvider> providers,
+ Context context) { }
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 2576bb7..09fdf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -9,14 +9,15 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
@@ -33,14 +34,17 @@
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.ConfigurationChangedReceiver;
import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
/**
* Class to manage everything related to assist in SystemUI.
*/
-public class AssistManager {
+public class AssistManager implements ConfigurationChangedReceiver {
private static final String TAG = "AssistManager";
private static final String ASSIST_ICON_METADATA_NAME =
@@ -52,9 +56,10 @@
protected final Context mContext;
private final WindowManager mWindowManager;
private final AssistDisclosure mAssistDisclosure;
+ private final InterestingConfigChanges mInterestingConfigChanges;
private AssistOrbContainer mView;
- private final BaseStatusBar mBar;
+ private final DeviceProvisionedController mDeviceProvisionedController;
protected final AssistUtils mAssistUtils;
private IVoiceInteractionSessionShowCallback mShowCallback =
@@ -79,14 +84,16 @@
}
};
- public AssistManager(BaseStatusBar bar, Context context) {
+ public AssistManager(DeviceProvisionedController controller, Context context) {
mContext = context;
- mBar = bar;
+ mDeviceProvisionedController = controller;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mAssistUtils = new AssistUtils(context);
mAssistDisclosure = new AssistDisclosure(context, new Handler());
registerVoiceInteractionSessionListener();
+ mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION);
+ onConfigurationChanged(context.getResources().getConfiguration());
}
protected void registerVoiceInteractionSessionListener() {
@@ -104,7 +111,10 @@
});
}
- public void onConfigurationChanged() {
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
+ return;
+ }
boolean visible = false;
if (mView != null) {
visible = mView.isShowing();
@@ -183,13 +193,13 @@
}
private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) {
- if (!mBar.isDeviceProvisioned()) {
+ if (!mDeviceProvisionedController.isDeviceProvisioned()) {
return;
}
// Close Recent Apps if needed
- mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL |
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
+ SysUiServiceProvider.getComponent(mContext, CommandQueue.class).animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 57857cc..50506a9 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -27,11 +27,13 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
+import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginManager;
import java.io.FileDescriptor;
@@ -47,6 +49,7 @@
private final View mRootView;
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges();
private final FragmentService mManager;
+ private final PluginFragmentManager mPlugins = new PluginFragmentManager();
private FragmentController mFragments;
private FragmentLifecycleCallbacks mLifecycleCallbacks;
@@ -163,6 +166,10 @@
return mFragments.getFragmentManager();
}
+ PluginFragmentManager getPluginManager() {
+ return mPlugins;
+ }
+
public interface FragmentListener {
void onFragmentViewCreated(String tag, Fragment fragment);
@@ -198,6 +205,11 @@
}
@Override
+ public Fragment instantiate(Context context, String className, Bundle arguments) {
+ return mPlugins.instantiate(context, className, arguments);
+ }
+
+ @Override
public boolean onShouldSaveFragmentState(Fragment fragment) {
return true; // True for now.
}
@@ -237,4 +249,57 @@
return true;
}
}
+
+ class PluginFragmentManager {
+ private final ArrayMap<String, Context> mPluginLookup = new ArrayMap<>();
+
+ public void removePlugin(String tag, String currentClass, String defaultClass) {
+ Fragment fragment = getFragmentManager().findFragmentByTag(tag);
+ mPluginLookup.remove(currentClass);
+ getFragmentManager().beginTransaction()
+ .replace(((View) fragment.getView().getParent()).getId(),
+ instantiate(mContext, defaultClass, null), tag)
+ .commit();
+ reloadFragments();
+ }
+
+ public void setCurrentPlugin(String tag, String currentClass, Context context) {
+ Fragment fragment = getFragmentManager().findFragmentByTag(tag);
+ mPluginLookup.put(currentClass, context);
+ getFragmentManager().beginTransaction()
+ .replace(((View) fragment.getView().getParent()).getId(),
+ instantiate(context, currentClass, null), tag)
+ .commit();
+ reloadFragments();
+ }
+
+ private void reloadFragments() {
+ // Save the old state.
+ Parcelable p = destroyFragmentHost();
+ // Generate a new fragment host and restore its state.
+ createFragmentHost(p);
+ }
+
+ Fragment instantiate(Context context, String className, Bundle arguments) {
+ Context pluginContext = mPluginLookup.get(className);
+ if (pluginContext != null) {
+ Fragment f = Fragment.instantiate(pluginContext, className, arguments);
+ if (f instanceof Plugin) {
+ ((Plugin) f).onCreate(mContext, pluginContext);
+ }
+ return f;
+ }
+ return Fragment.instantiate(context, className, arguments);
+ }
+ }
+
+ private static class PluginState {
+ Context mContext;
+ String mCls;
+
+ private PluginState(String cls, Context context) {
+ mCls = cls;
+ mContext = context;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
index e107fcd..2e6de4a 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
@@ -15,6 +15,7 @@
package com.android.systemui.fragments;
import android.app.Fragment;
+import android.content.Context;
import android.util.Log;
import android.view.View;
@@ -30,27 +31,19 @@
private final FragmentHostManager mFragmentHostManager;
private final PluginManager mPluginManager;
private final Class<? extends Fragment> mDefaultClass;
- private final int mId;
- private final String mTag;
private final Class<? extends FragmentBase> mExpectedInterface;
+ private final String mTag;
- public PluginFragmentListener(View view, String tag, int id,
- Class<? extends Fragment> defaultFragment,
+ public PluginFragmentListener(View view, String tag, Class<? extends Fragment> defaultFragment,
Class<? extends FragmentBase> expectedInterface) {
+ mTag = tag;
mFragmentHostManager = FragmentHostManager.get(view);
mPluginManager = PluginManager.getInstance(view.getContext());
mExpectedInterface = expectedInterface;
- mTag = tag;
mDefaultClass = defaultFragment;
- mId = id;
}
public void startListening(String action, int version) {
- try {
- setFragment(mDefaultClass.newInstance());
- } catch (InstantiationException | IllegalAccessException e) {
- Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
- }
mPluginManager.addPluginListener(action, this, version, false /* Only allow one */);
}
@@ -58,17 +51,13 @@
mPluginManager.removePluginListener(this);
}
- private void setFragment(Fragment fragment) {
- mFragmentHostManager.getFragmentManager().beginTransaction()
- .replace(mId, fragment, mTag)
- .commit();
- }
-
@Override
- public void onPluginConnected(Plugin plugin) {
+ public void onPluginConnected(Plugin plugin, Context pluginContext) {
try {
mExpectedInterface.cast(plugin);
- setFragment((Fragment) plugin);
+ Fragment.class.cast(plugin);
+ mFragmentHostManager.getPluginManager().setCurrentPlugin(mTag,
+ plugin.getClass().getName(), pluginContext);
} catch (ClassCastException e) {
Log.e(TAG, plugin.getClass().getName() + " must be a Fragment and implement "
+ mExpectedInterface.getName(), e);
@@ -77,10 +66,7 @@
@Override
public void onPluginDisconnected(Plugin plugin) {
- try {
- setFragment(mDefaultClass.newInstance());
- } catch (InstantiationException | IllegalAccessException e) {
- Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
- }
+ mFragmentHostManager.getPluginManager().removePlugin(mTag,
+ plugin.getClass().getName(), mDefaultClass.getName());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 3103267..3df557d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -86,9 +86,12 @@
// another package than the top activity in the stack
boolean expandPipToFullscreen = true;
if (sourceComponent != null) {
- ComponentName topActivity = PipUtils.getTopPinnedActivity(mActivityManager);
- expandPipToFullscreen = topActivity != null && topActivity.getPackageName().equals(
- sourceComponent.getPackageName());
+ ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
+ mActivityManager);
+ if (topActivity != null && topActivity.getPackageName().equals(
+ sourceComponent.getPackageName())) {
+ expandPipToFullscreen = false;
+ }
}
if (expandPipToFullscreen) {
mTouchHandler.expandPinnedStackToFullscreen();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
index 2284013..d96baa6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
@@ -148,7 +148,8 @@
*/
private void resolveActiveMediaController(List<MediaController> controllers) {
if (controllers != null) {
- final ComponentName topActivity = PipUtils.getTopPinnedActivity(mActivityManager);
+ final ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
+ mActivityManager);
if (topActivity != null) {
for (int i = 0; i < controllers.size(); i++) {
final MediaController controller = controllers.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
index 9c03830..a8cdd1b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
@@ -21,6 +21,7 @@
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
import android.content.ComponentName;
+import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
@@ -29,14 +30,23 @@
private static final String TAG = "PipUtils";
/**
- * @return the ComponentName of the top activity in the pinned stack, or null if none exists.
+ * @return the ComponentName of the top non-SystemUI activity in the pinned stack, or null if
+ * none exists.
*/
- public static ComponentName getTopPinnedActivity(IActivityManager activityManager) {
+ public static ComponentName getTopPinnedActivity(Context context,
+ IActivityManager activityManager) {
try {
- StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
+ final String sysUiPackageName = context.getPackageName();
+ final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
pinnedStackInfo.taskIds.length > 0) {
- return pinnedStackInfo.topActivity;
+ for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) {
+ ComponentName cn = ComponentName.unflattenFromString(
+ pinnedStackInfo.taskNames[i]);
+ if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) {
+ return cn;
+ }
+ }
}
} catch (RemoteException e) {
Log.w(TAG, "Unable to get pinned stack.");
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 71dda2d..2fe9e77 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -217,8 +217,16 @@
return;
}
mTempWarning = false;
- mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP,
- UserHandle.ALL);
+ dismissTemperatureWarningInternal();
+ }
+
+ /**
+ * Internal only version of {@link #dismissTemperatureWarning()} that simply dismisses
+ * the notification. As such, the notification will not show again until
+ * {@link #dismissTemperatureWarning()} is called.
+ */
+ private void dismissTemperatureWarningInternal() {
+ mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, UserHandle.ALL);
}
@Override
@@ -390,10 +398,10 @@
} else if (action.equals(ACTION_DISMISSED_WARNING)) {
dismissLowBatteryWarning();
} else if (ACTION_CLICKED_TEMP_WARNING.equals(action)) {
- dismissTemperatureWarning();
+ dismissTemperatureWarningInternal();
showTemperatureDialog();
} else if (ACTION_DISMISSED_TEMP_WARNING.equals(action)) {
- dismissTemperatureWarning();
+ dismissTemperatureWarningInternal();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 28ca6a3..1d4a5c7 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.BatteryManager;
import android.os.Handler;
@@ -221,11 +222,15 @@
};
private void initTemperatureWarning() {
- if (!mContext.getResources().getBoolean(R.bool.config_showTemperatureWarning)) {
+ ContentResolver resolver = mContext.getContentResolver();
+ Resources resources = mContext.getResources();
+ if (Settings.Global.getInt(resolver, Settings.Global.SHOW_TEMPERATURE_WARNING,
+ resources.getInteger(R.integer.config_showTemperatureWarning)) == 0) {
return;
}
- mThrottlingTemp = mContext.getResources().getInteger(R.integer.config_warningTemperature);
+ mThrottlingTemp = Settings.Global.getFloat(resolver, Settings.Global.WARNING_TEMPERATURE,
+ resources.getInteger(R.integer.config_warningTemperature));
if (mThrottlingTemp < 0f) {
// Get the throttling temperature. No need to check if we're not throttling.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 5027144..a20b7ba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -33,11 +33,15 @@
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
import com.android.systemui.plugins.qs.QS.Callback;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.QSTileHost;
public class QSDetail extends LinearLayout {
@@ -160,7 +164,8 @@
setupDetailHeader(adapter);
if (toggleQs && !mFullyExpanded) {
mTriggeredExpand = true;
- mHost.animateToggleQSExpansion();
+ SysUiServiceProvider.getComponent(mContext, CommandQueue.class)
+ .animateExpandSettingsPanel(null);
} else {
mTriggeredExpand = false;
}
@@ -171,7 +176,8 @@
x = mOpenX;
y = mOpenY;
if (toggleQs && mTriggeredExpand) {
- mHost.animateToggleQSExpansion();
+ SysUiServiceProvider.getComponent(mContext, CommandQueue.class)
+ .animateCollapsePanels();
mTriggeredExpand = false;
}
}
@@ -231,12 +237,8 @@
protected void setupDetailFooter(DetailAdapter adapter) {
final Intent settingsIntent = adapter.getSettingsIntent();
mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
- mDetailSettingsButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mHost.startActivityDismissingKeyguard(settingsIntent);
- }
- });
+ mDetailSettingsButton.setOnClickListener(v -> Dependency.get(ActivityStarter.class)
+ .postStartActivityDismissingKeyguard(settingsIntent, 0));
}
protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index fb3b1d9..0bf3f15 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -19,11 +19,9 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.UserHandle;
import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
@@ -33,12 +31,13 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
-import android.view.Window;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -55,12 +54,13 @@
private final ImageView mFooterIcon2;
private final Context mContext;
private final Callback mCallback = new Callback();
+ private final SecurityController mSecurityController;
+ private final ActivityStarter mActivityStarter;
+ private final Handler mMainHandler;
- private SecurityController mSecurityController;
private AlertDialog mDialog;
private QSTileHost mHost;
protected Handler mHandler;
- private final Handler mMainHandler;
private boolean mIsVisible;
private boolean mIsIconVisible;
@@ -81,13 +81,13 @@
mFooterIcon2Id = R.drawable.ic_qs_network_logging;
mContext = context;
mMainHandler = new Handler(Looper.getMainLooper());
+ mActivityStarter = Dependency.get(ActivityStarter.class);
+ mSecurityController = Dependency.get(SecurityController.class);
+ mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
}
- public void setHostEnvironment(QSTileHost host, SecurityController securityController,
- Looper looper) {
+ public void setHostEnvironment(QSTileHost host) {
mHost = host;
- mSecurityController = securityController;
- mHandler = new H(looper);
}
public void setListening(boolean listening) {
@@ -173,7 +173,7 @@
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
final Intent settingsIntent = new Intent(ACTION_VPN_SETTINGS);
- mHost.startActivityDismissingKeyguard(settingsIntent);
+ mActivityStarter.postStartActivityDismissingKeyguard(settingsIntent, 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 2de32cc..e004828 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -190,12 +190,11 @@
mHost = host;
mHost.addCallback(this);
setTiles(mHost.getTiles());
- mFooter.setHostEnvironment(host, host.getSecurityController(), host.getLooper());
+ mFooter.setHostEnvironment(host);
mCustomizePanel = customizer;
if (mCustomizePanel != null) {
mCustomizePanel.setHost(mHost);
}
- mBrightnessController.setBackgroundLooper(host.getLooper());
}
public QSTileHost getHost() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index dad37b0..e18654e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs;
import android.app.ActivityManager;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -32,22 +31,11 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSTile.State;
import com.android.systemui.qs.external.TileServices;
-import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
import java.util.ArrayList;
import java.util.Collection;
@@ -100,7 +88,7 @@
protected QSTile(Host host) {
mHost = host;
mContext = host.getContext();
- mHandler = new H(host.getLooper());
+ mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
}
/**
@@ -242,7 +230,8 @@
protected void handleLongClick() {
MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
- mHost.startActivityDismissingKeyguard(getLongClickIntent());
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
+ getLongClickIntent(), 0);
}
public abstract Intent getLongClickIntent();
@@ -381,7 +370,8 @@
if (mState.disabledByPolicy) {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
mContext, mState.enforcedAdmin);
- mHost.startActivityDismissingKeyguard(intent);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
+ intent, 0);
} else {
mAnnounceNextStateChange = true;
handleClick();
@@ -436,36 +426,17 @@
}
public interface Host {
- void startActivityDismissingKeyguard(Intent intent);
- void startActivityDismissingKeyguard(PendingIntent intent);
- void startRunnableDismissingKeyguard(Runnable runnable);
void warn(String message, Throwable t);
void collapsePanels();
- void animateToggleQSExpansion();
void openPanels();
- Looper getLooper();
Context getContext();
Collection<QSTile<?>> getTiles();
void addCallback(Callback callback);
void removeCallback(Callback callback);
- BluetoothController getBluetoothController();
- LocationController getLocationController();
- RotationLockController getRotationLockController();
- NetworkController getNetworkController();
- ZenModeController getZenModeController();
- HotspotController getHotspotController();
- CastController getCastController();
- FlashlightController getFlashlightController();
- KeyguardMonitor getKeyguardMonitor();
- UserSwitcherController getUserSwitcherController();
- UserInfoController getUserInfoController();
- BatteryController getBatteryController();
TileServices getTileServices();
void removeTile(String tileSpec);
- ManagedProfileController getManagedProfileController();
-
- public interface Callback {
+ interface Callback {
void onTilesChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 0be53b4..730b55d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -36,13 +36,14 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSDetailClipper;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
import java.util.ArrayList;
@@ -140,7 +141,7 @@
mNotifQsContainer.setCustomizerShowing(true);
announceForAccessibility(mContext.getString(
R.string.accessibility_desc_quick_settings_edit));
- mHost.getKeyguardMonitor().addCallback(mKeyguardCallback);
+ Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
}
}
@@ -156,7 +157,7 @@
mNotifQsContainer.setCustomizerShowing(false);
announceForAccessibility(mContext.getString(
R.string.accessibility_desc_quick_settings));
- mHost.getKeyguardMonitor().removeCallback(mKeyguardCallback);
+ Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
}
}
@@ -206,12 +207,9 @@
mTileAdapter.saveSpecs(mHost);
}
- private final Callback mKeyguardCallback = new Callback() {
- @Override
- public void onKeyguardChanged() {
- if (mHost.getKeyguardMonitor().isShowing()) {
- hide(0, 0);
- }
+ private final Callback mKeyguardCallback = () -> {
+ if (Dependency.get(KeyguardMonitor.class).isShowing()) {
+ hide(0, 0);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 0cd6490..72e6fcc0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -30,6 +30,7 @@
import android.service.quicksettings.TileService;
import android.widget.Button;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTile.DrawableIcon;
@@ -59,7 +60,7 @@
private void addSystemTiles(final QSTileHost host) {
String possible = mContext.getString(R.string.quick_settings_tiles_stock);
String[] possibleTiles = possible.split(",");
- final Handler qsHandler = new Handler(host.getLooper());
+ final Handler qsHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
final Handler mainHandler = new Handler(Looper.getMainLooper());
for (int i = 0; i < possibleTiles.length; i++) {
final String spec = possibleTiles[i];
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index cff4846..3afbc35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -39,6 +39,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
+import com.android.systemui.Dependency;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
import com.android.systemui.statusbar.phone.QSTileHost;
@@ -306,13 +308,10 @@
}
public void startUnlockAndRun() {
- mHost.startRunnableDismissingKeyguard(new Runnable() {
- @Override
- public void run() {
- try {
- mService.onUnlockComplete();
- } catch (RemoteException e) {
- }
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
+ try {
+ mService.onUnlockComplete();
+ } catch (RemoteException e) {
}
});
}
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 015a4c0..5c23eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -30,13 +30,13 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSService;
-import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -284,13 +284,13 @@
@Override
public boolean isLocked() {
- KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
+ KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
return keyguardMonitor.isShowing();
}
@Override
public boolean isSecure() {
- KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
+ KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
return keyguardMonitor.isSecure() && keyguardMonitor.isShowing();
}
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 a82f550..7e04b67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -38,10 +38,12 @@
import com.android.settingslib.BatteryInfo;
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;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.qs.external.TileColorPicker;
import com.android.systemui.statusbar.policy.BatteryController;
import java.text.NumberFormat;
@@ -59,7 +61,7 @@
public BatteryTile(Host host) {
super(host);
- mBatteryController = host.getBatteryController();
+ mBatteryController = Dependency.get(BatteryController.class);
}
@Override
@@ -273,7 +275,7 @@
mDetailShown = true;
v.getContext().registerReceiver(mReceiver,
new IntentFilter(Intent.ACTION_TIME_TICK), null,
- PhoneStatusBar.getTimeTickHandler(v.getContext()));
+ Dependency.get(Dependency.TIME_TICK_HANDLER));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 15f3c90..91e76ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -32,7 +32,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
@@ -48,10 +50,12 @@
private final BluetoothController mController;
private final BluetoothDetailAdapter mDetailAdapter;
+ private final ActivityStarter mActivityStarter;
public BluetoothTile(Host host) {
super(host);
- mController = host.getBluetoothController();
+ mController = Dependency.get(BluetoothController.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
mDetailAdapter = (BluetoothDetailAdapter) createDetailAdapter();
}
@@ -90,7 +94,8 @@
@Override
protected void handleSecondaryClick() {
if (!mController.canConfigBluetooth()) {
- mHost.startActivityDismissingKeyguard(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ new Intent(Settings.ACTION_BLUETOOTH_SETTINGS), 0);
return;
}
showDetail(true);
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 d61e2f2..7415765 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -28,7 +28,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
@@ -49,12 +51,14 @@
private final CastDetailAdapter mDetailAdapter;
private final KeyguardMonitor mKeyguard;
private final Callback mCallback = new Callback();
+ private final ActivityStarter mActivityStarter;
public CastTile(Host host) {
super(host);
- mController = host.getCastController();
+ mController = Dependency.get(CastController.class);
mDetailAdapter = new CastDetailAdapter();
- mKeyguard = host.getKeyguardMonitor();
+ mKeyguard = Dependency.get(KeyguardMonitor.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
}
@Override
@@ -101,13 +105,10 @@
@Override
protected void handleClick() {
if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
- mHost.startRunnableDismissingKeyguard(new Runnable() {
- @Override
- public void run() {
- MetricsLogger.action(mContext, getMetricsCategory());
- showDetail(true);
- mHost.openPanels();
- }
+ mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+ MetricsLogger.action(mContext, getMetricsCategory());
+ showDetail(true);
+ mHost.openPanels();
});
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 5f7c803..75c4a75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -29,7 +29,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSIconView;
import com.android.systemui.qs.QSTile;
@@ -48,10 +50,12 @@
private final CellularDetailAdapter mDetailAdapter;
private final CellSignalCallback mSignalCallback = new CellSignalCallback();
+ private final ActivityStarter mActivityStarter;
public CellularTile(Host host) {
super(host);
- mController = host.getNetworkController();
+ mController = Dependency.get(NetworkController.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
mDataController = mController.getMobileDataController();
mDetailAdapter = new CellularDetailAdapter();
}
@@ -96,7 +100,7 @@
if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
- mHost.startActivityDismissingKeyguard(CELLULAR_SETTINGS);
+ mActivityStarter.postStartActivityDismissingKeyguard(CELLULAR_SETTINGS, 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index aadc8e7..412fe3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -21,11 +21,13 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.NetworkController;
public class DataSaverTile extends QSTile<QSTile.BooleanState> implements
DataSaverController.Listener{
@@ -34,7 +36,7 @@
public DataSaverTile(Host host) {
super(host);
- mDataSaverController = host.getNetworkController().getDataSaverController();
+ mDataSaverController = Dependency.get(NetworkController.class).getDataSaverController();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index a25b7ea..3c1f504 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -35,9 +35,11 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysUIToast;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -74,7 +76,7 @@
public DndTile(Host host) {
super(host);
- mController = host.getZenModeController();
+ mController = Dependency.get(ZenModeController.class);
mDetailAdapter = new DndDetailAdapter();
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
mReceiverRegistered = true;
@@ -313,7 +315,8 @@
private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() {
@Override
public void onPrioritySettings() {
- mHost.startActivityDismissingKeyguard(ZEN_PRIORITY_SETTINGS);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
+ ZEN_PRIORITY_SETTINGS, 0);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 4bbc458..ac82753 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -27,6 +27,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -45,14 +46,12 @@
public FlashlightTile(Host host) {
super(host);
- mFlashlightController = host.getFlashlightController();
- mFlashlightController.addCallback(this);
+ mFlashlightController = Dependency.get(FlashlightController.class);
}
@Override
protected void handleDestroy() {
super.handleDestroy();
- mFlashlightController.removeCallback(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 9d495ce..70f8109 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -29,6 +29,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.GlobalSetting;
import com.android.systemui.qs.QSTile;
@@ -52,7 +53,7 @@
public HotspotTile(Host host) {
super(host);
- mController = host.getHotspotController();
+ mController = Dependency.get(HotspotController.class);
mAirplaneMode = new GlobalSetting(mContext, mHandler, Global.AIRPLANE_MODE_ON) {
@Override
protected void handleValueChanged(int value) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index f968816..fcc9596 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -31,6 +31,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.qs.QSTile;
import java.util.Arrays;
@@ -105,7 +107,7 @@
try {
if (pi != null) {
if (pi.isActivity()) {
- getHost().startActivityDismissingKeyguard(pi);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(pi);
} else {
pi.send();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 002e2a6..5374f18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -18,14 +18,15 @@
import android.content.Intent;
import android.os.UserManager;
-
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.widget.Switch;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
@@ -47,8 +48,8 @@
public LocationTile(Host host) {
super(host);
- mController = host.getLocationController();
- mKeyguard = host.getKeyguardMonitor();
+ mController = Dependency.get(LocationController.class);
+ mKeyguard = Dependency.get(KeyguardMonitor.class);
}
@Override
@@ -75,18 +76,15 @@
@Override
protected void handleClick() {
if (mKeyguard.isSecure() && mKeyguard.isShowing()) {
- mHost.startRunnableDismissingKeyguard(new Runnable() {
- @Override
- public void run() {
- final boolean wasEnabled = (Boolean) mState.value;
- mHost.openPanels();
- MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
- mController.setLocationEnabled(!wasEnabled);
- }
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
+ final boolean wasEnabled = mState.value;
+ mHost.openPanels();
+ MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
+ mController.setLocationEnabled(!wasEnabled);
});
return;
}
- final boolean wasEnabled = (Boolean) mState.value;
+ final boolean wasEnabled = mState.value;
MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
mController.setLocationEnabled(!wasEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 9be67da..2c0af17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -26,6 +26,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -51,7 +52,7 @@
public RotationLockTile(Host host) {
super(host);
- mController = host.getRotationLockController();
+ mController = Dependency.get(RotationLockController.class);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index 246c23e..c20c6bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -22,6 +22,7 @@
import android.util.Pair;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -35,8 +36,8 @@
public UserTile(Host host) {
super(host);
- mUserSwitcherController = host.getUserSwitcherController();
- mUserInfoController = host.getUserInfoController();
+ mUserSwitcherController = Dependency.get(UserSwitcherController.class);
+ mUserInfoController = Dependency.get(UserInfoController.class);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 7d99041..54b41ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -31,7 +31,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.wifi.AccessPoint;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
@@ -55,12 +57,14 @@
private final QSTile.SignalState mStateBeforeClick = newTileState();
protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback();
+ private final ActivityStarter mActivityStarter;
public WifiTile(Host host) {
super(host);
- mController = host.getNetworkController();
+ mController = Dependency.get(NetworkController.class);
mWifiController = mController.getAccessPointController();
mDetailAdapter = (WifiDetailAdapter) createDetailAdapter();
+ mActivityStarter = Dependency.get(ActivityStarter.class);
}
@Override
@@ -115,7 +119,8 @@
@Override
protected void handleSecondaryClick() {
if (!mWifiController.canConfigWifi()) {
- mHost.startActivityDismissingKeyguard(new Intent(Settings.ACTION_WIFI_SETTINGS));
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ new Intent(Settings.ACTION_WIFI_SETTINGS), 0);
return;
}
showDetail(true);
@@ -329,7 +334,7 @@
@Override
public void onSettingsActivityTriggered(Intent settingsIntent) {
- mHost.startActivityDismissingKeyguard(settingsIntent);
+ mActivityStarter.postStartActivityDismissingKeyguard(settingsIntent, 0);
}
@Override
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 207deff..ae4d6c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -23,6 +23,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -41,7 +42,7 @@
public WorkModeTile(Host host) {
super(host);
- mProfileController = host.getManagedProfileController();
+ mProfileController = Dependency.get(ManagedProfileController.class);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 0265c9e..8d18a75 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -438,7 +438,7 @@
ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId);
if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
- if (runningTask.isDockable) {
+ if (runningTask.supportsSplitScreenMultiWindow) {
if (metricsDockAction != -1) {
MetricsLogger.action(mContext, metricsDockAction,
runningTask.topActivity.flattenToShortString());
@@ -486,7 +486,6 @@
case ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE:
return COUNTER_WINDOW_UNSUPPORTED;
case ActivityInfo.RESIZE_MODE_RESIZEABLE:
- case ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE:
case ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION:
return COUNTER_WINDOW_SUPPORTED;
default:
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 5c7496d..11b5984 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -197,7 +197,7 @@
Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
- t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
+ t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
isLocked);
allTasks.add(task);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 0777163..b318ea7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -462,7 +462,6 @@
mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
mLightDismissDrawable : mDarkDismissDrawable);
- mDismissButton.setContentDescription(t.dismissDescription);
mDismissButton.setOnClickListener(this);
mDismissButton.setClickable(false);
((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
@@ -499,7 +498,6 @@
// In accessibility, a single click on the focused app info button will show it
if (touchExplorationEnabled) {
- mIconView.setContentDescription(t.appInfoDescription);
mIconView.setOnClickListener(this);
mIconView.setClickable(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index 3059a05..7825e9e 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -37,6 +37,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import java.util.ArrayList;
@@ -70,7 +71,7 @@
private final CurrentUserTracker mUserTracker;
private final IVrManager mVrManager;
- private Handler mBackgroundHandler;
+ private final Handler mBackgroundHandler;
private final BrightnessObserver mBrightnessObserver;
private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks =
@@ -276,7 +277,7 @@
mContext = context;
mIcon = icon;
mControl = control;
- mBackgroundHandler = new Handler(Looper.getMainLooper());
+ mBackgroundHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
mUserTracker = new CurrentUserTracker(mContext) {
@Override
public void onUserSwitched(int newUserId) {
@@ -298,10 +299,6 @@
mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService("vrmanager"));
}
- public void setBackgroundLooper(Looper backgroundLooper) {
- mBackgroundHandler = new Handler(backgroundLooper);
- }
-
public void addStateChangedCallback(BrightnessStateChangeCallback cb) {
mChangeCallbacks.add(cb);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index b5358bf..b9ed725 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -463,6 +463,7 @@
}
mDark = dark;
updateBackground();
+ updateBackgroundTint(fade);
if (!dark && fade && !shouldHideBackground()) {
fadeInFromDark(delay);
}
@@ -700,8 +701,8 @@
protected void updateBackground() {
cancelFadeAnimations();
if (shouldHideBackground()) {
- mBackgroundDimmed.setVisibility(View.INVISIBLE);
- mBackgroundNormal.setVisibility(View.INVISIBLE);
+ mBackgroundDimmed.setVisibility(INVISIBLE);
+ mBackgroundNormal.setVisibility(mActivated ? VISIBLE : INVISIBLE);
} else if (mDimmed) {
// When groups are animating to the expanded state from the lockscreen, show the
// normal background instead of the dimmed background
@@ -940,6 +941,9 @@
* @return the calculated background color
*/
private int calculateBgColor(boolean withTint, boolean withOverRide) {
+ if (mDark) {
+ return getContext().getColor(R.color.notification_material_background_dark_color);
+ }
if (withOverRide && mOverrideTint != NO_COLOR) {
int defaultTint = calculateBgColor(withTint, false);
return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index faf143e..d9298ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -24,6 +24,7 @@
import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
@@ -41,6 +42,7 @@
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Build;
@@ -51,6 +53,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -71,7 +74,6 @@
import android.view.Display;
import android.view.IWindowManager;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
@@ -92,6 +94,7 @@
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
@@ -100,11 +103,11 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationGuts.OnGutsClosedListener;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -223,6 +226,7 @@
protected KeyguardManager mKeyguardManager;
private LockPatternUtils mLockPatternUtils;
+ private DeviceProvisionedController mDeviceProvisionedController;
// UI-specific methods
@@ -237,8 +241,6 @@
protected Display mDisplay;
- private boolean mDeviceProvisioned = false;
-
protected RecentsComponent mRecents;
protected int mZenMode;
@@ -270,7 +272,7 @@
@Override // NotificationData.Environment
public boolean isDeviceProvisioned() {
- return mDeviceProvisioned;
+ return mDeviceProvisionedController.isDeviceProvisioned();
}
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@@ -284,15 +286,17 @@
return mVrMode;
}
+ private final DeviceProvisionedListener mDeviceProvisionedListener =
+ new DeviceProvisionedListener() {
+ @Override
+ public void onDeviceProvisionedChanged() {
+ updateNotifications();
+ }
+ };
+
protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- final boolean provisioned = 0 != Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
- if (provisioned != mDeviceProvisioned) {
- mDeviceProvisioned = provisioned;
- updateNotifications();
- }
final int mode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
setZenMode(mode);
@@ -315,9 +319,16 @@
};
private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
+ private final int[] mTmpInt2 = new int[2];
+
@Override
public boolean onClickHandler(
final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
+ view.getLocationInWindow(mTmpInt2);
+ wakeUpIfDozing(SystemClock.uptimeMillis(), new PointF(
+ mTmpInt2[0] + view.getWidth() / 2, mTmpInt2[1] + view.getHeight() / 2));
+
+
if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
return true;
}
@@ -707,9 +718,8 @@
ServiceManager.checkService(DreamService.DREAM_SERVICE));
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
- mSettingsObserver);
+ mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
mSettingsObserver);
@@ -1038,6 +1048,7 @@
private void bindGuts(final ExpandableNotificationRow row) {
row.inflateGuts();
final StatusBarNotification sbn = row.getStatusBarNotification();
+ final NotificationChannel channel = row.getEntry().channel;
PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier());
row.setTag(sbn.getPackageName());
final NotificationGuts guts = row.getGuts();
@@ -1077,8 +1088,8 @@
closeControls(row, guts, v);
}
};
- guts.bindNotification(pmUser, iNotificationManager, sbn, onSettingsClick, onDoneClick,
- mNonBlockablePkgs);
+ guts.bindNotification(pmUser, iNotificationManager, sbn, channel,
+ onSettingsClick, onDoneClick, mNonBlockablePkgs);
}
private void closeControls(
@@ -1785,13 +1796,22 @@
return false;
}
+ public void wakeUpIfDozing(long time, PointF where) {
+ }
+
private final class NotificationClicker implements View.OnClickListener {
+ private final int[] mTmpInt2 = new int[2];
+
public void onClick(final View v) {
if (!(v instanceof ExpandableNotificationRow)) {
Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
return;
}
+ v.getLocationInWindow(mTmpInt2);
+ wakeUpIfDozing(SystemClock.uptimeMillis(),
+ new PointF(mTmpInt2[0] + v.getWidth() / 2, mTmpInt2[1] + v.getHeight() / 2));
+
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
final StatusBarNotification sbn = row.getStatusBarNotification();
if (sbn == null) {
@@ -2470,6 +2490,7 @@
} catch (RemoteException e) {
// Ignore.
}
+ mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index fed28e3..477701c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -185,7 +185,14 @@
public void animateCollapsePanels() {
synchronized (mLock) {
mHandler.removeMessages(MSG_COLLAPSE_PANELS);
- mHandler.sendEmptyMessage(MSG_COLLAPSE_PANELS);
+ mHandler.obtainMessage(MSG_COLLAPSE_PANELS, 0, 0).sendToTarget();
+ }
+ }
+
+ public void animateCollapsePanels(int flags) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_COLLAPSE_PANELS);
+ mHandler.obtainMessage(MSG_COLLAPSE_PANELS, flags, 0).sendToTarget();
}
}
@@ -450,7 +457,7 @@
break;
case MSG_COLLAPSE_PANELS:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).animateCollapsePanels(0);
+ mCallbacks.get(i).animateCollapsePanels(msg.arg1);
}
break;
case MSG_EXPAND_SETTINGS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index f451aef..d599ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -42,11 +42,12 @@
import com.android.internal.app.IBatteryStats;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.LockIcon;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.UserInfoController;
/**
* Controls the indications and error messages shown on the Keyguard
@@ -83,6 +84,8 @@
private int mChargingWattage;
private String mMessageToShowOnScreenOn;
+ private KeyguardUpdateMonitorCallback mUpdateMonitor;
+
private final DevicePolicyManager mDevicePolicyManager;
public KeyguardIndicationController(Context context, ViewGroup indicationArea,
@@ -106,14 +109,31 @@
mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(
Context.DEVICE_POLICY_SERVICE);
- KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitor);
+ KeyguardUpdateMonitor.getInstance(context).registerCallback(getKeyguardCallback());
context.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM,
new IntentFilter(Intent.ACTION_TIME_TICK), null,
- PhoneStatusBar.getTimeTickHandler(mContext));
+ Dependency.get(Dependency.TIME_TICK_HANDLER));
updateDisclosure();
}
+ /**
+ * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this
+ * {@link KeyguardIndicationController}.
+ *
+ * <p>Subclasses may override this method to extend or change the callback behavior by extending
+ * the {@link BaseKeyguardCallback}.
+ *
+ * @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the
+ * same instance.
+ */
+ protected KeyguardUpdateMonitorCallback getKeyguardCallback() {
+ if (mUpdateMonitor == null) {
+ mUpdateMonitor = new BaseKeyguardCallback();
+ }
+ return mUpdateMonitor;
+ }
+
private void updateDisclosure() {
if (mDevicePolicyManager == null) {
return;
@@ -152,6 +172,12 @@
}
/**
+ * Sets the active controller managing changes and callbacks to user information.
+ */
+ public void setUserInfoController(UserInfoController userInfoController) {
+ }
+
+ /**
* Hides transient indication in {@param delayMs}.
*/
public void hideTransientIndicationDelayed(long delayMs) {
@@ -264,8 +290,37 @@
}
}
- KeyguardUpdateMonitorCallback mUpdateMonitor = new KeyguardUpdateMonitorCallback() {
- public int mLastSuccessiveErrorMessage = -1;
+ public void setStatusBarKeyguardViewManager(
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ }
+
+ BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mHandler.post(() -> {
+ if (mVisible) {
+ updateIndication();
+ }
+ });
+ }
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) {
+ mTransientIndication = null;
+ updateIndication();
+ } else if (msg.what == MSG_CLEAR_FP_MSG) {
+ mLockIcon.setTransientFpError(false);
+ hideTransientIndication();
+ }
+ }
+ };
+
+ protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
+ private int mLastSuccessiveErrorMessage = -1;
@Override
public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
@@ -372,34 +427,4 @@
}
}
};
-
- BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> {
- if (mVisible) {
- updateIndication();
- }
- });
- }
- };
-
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) {
- mTransientIndication = null;
- updateIndication();
- } else if (msg.what == MSG_CLEAR_FP_MSG) {
- mLockIcon.setTransientFpError(false);
- hideTransientIndication();
- }
- }
- };
-
- public void setStatusBarKeyguardViewManager(
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 458daf1..3a89186 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -55,6 +56,7 @@
private static final int COLOR_INVALID = 1;
public String key;
public StatusBarNotification notification;
+ public NotificationChannel channel;
public StatusBarIconView icon;
public StatusBarIconView expandedIcon;
public ExpandableNotificationRow row; // the outer expanded view
@@ -429,6 +431,14 @@
return null;
}
+ public NotificationChannel getChannel(String key) {
+ if (mRankingMap != null) {
+ mRankingMap.getRanking(key, mTmpRanking);
+ return mTmpRanking.getChannel();
+ }
+ return null;
+ }
+
private void updateRankingAndSort(RankingMap ranking) {
if (ranking != null) {
mRankingMap = ranking;
@@ -442,6 +452,7 @@
entry.notification.setOverrideGroupKey(overrideGroupKey);
mGroupManager.onEntryUpdated(entry, oldSbn);
}
+ entry.channel = getChannel(entry.key);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index c7adb60..83104e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -72,11 +72,7 @@
private INotificationManager mINotificationManager;
private int mStartingUserImportance;
private StatusBarNotification mStatusBarNotification;
-
- private ImageView mAutoButton;
- private TextView mImportanceSummary;
- private TextView mImportanceTitle;
- private boolean mAuto;
+ private NotificationChannel mNotificationChannel;
private View mImportanceGroup;
private View mChannelDisabled;
@@ -170,11 +166,12 @@
}
void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
- final StatusBarNotification sbn, OnSettingsClickListener onSettingsClick,
+ final StatusBarNotification sbn, final NotificationChannel channel,
+ OnSettingsClickListener onSettingsClick,
OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
mINotificationManager = iNotificationManager;
+ mNotificationChannel = channel;
mStatusBarNotification = sbn;
- final NotificationChannel channel = sbn.getNotificationChannel();
mStartingUserImportance = channel.getImportance();
final String pkg = sbn.getPackageName();
@@ -288,14 +285,13 @@
if (selectedImportance == mStartingUserImportance) {
return;
}
- final NotificationChannel channel = mStatusBarNotification.getNotificationChannel();
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
selectedImportance - mStartingUserImportance);
- channel.setImportance(selectedImportance);
+ mNotificationChannel.setImportance(selectedImportance);
try {
mINotificationManager.updateNotificationChannelForPackage(
mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
- channel);
+ mNotificationChannel);
} catch (RemoteException e) {
// :(
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index d77e9ed..c8e8973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar;
+import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.telephony.SubscriptionInfo;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -37,8 +40,10 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -62,8 +67,8 @@
private static final String SLOT_WIFI = "wifi";
private static final String SLOT_ETHERNET = "ethernet";
- NetworkControllerImpl mNC;
- SecurityController mSC;
+ private final NetworkController mNetworkController;
+ private final SecurityController mSecurityController;
private boolean mNoSimsVisible = false;
private boolean mVpnVisible = false;
@@ -72,8 +77,10 @@
private boolean mEthernetVisible = false;
private int mEthernetIconId = 0;
private int mLastEthernetIconId = -1;
+ private int mWifiBadgeId = -1;
private boolean mWifiVisible = false;
private int mWifiStrengthId = 0;
+ private int mLastWifiBadgeId = -1;
private int mLastWifiStrengthId = -1;
private boolean mIsAirplaneMode = false;
private int mAirplaneIconId = 0;
@@ -131,6 +138,8 @@
TypedValue typedValue = new TypedValue();
res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
mIconScaleFactor = typedValue.getFloat();
+ mNetworkController = Dependency.get(NetworkController.class);
+ mSecurityController = Dependency.get(SecurityController.class);
}
@Override
@@ -151,25 +160,11 @@
mBlockEthernet = blockEthernet;
mBlockWifi = blockWifi;
// Re-register to get new callbacks.
- mNC.removeCallback(this);
- mNC.addCallback(this);
+ mNetworkController.removeCallback(this);
+ mNetworkController.addCallback(this);
}
}
- public void setNetworkController(NetworkControllerImpl nc) {
- if (DEBUG) Log.d(TAG, "NetworkController=" + nc);
- mNC = nc;
- mNC.addCallback(this);
- }
-
- public void setSecurityController(SecurityController sc) {
- if (DEBUG) Log.d(TAG, "SecurityController=" + sc);
- mSC = sc;
- mSC.addCallback(this);
- mVpnVisible = mSC.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSC.isVpnBranded());
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -213,6 +208,8 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mVpnVisible = mSecurityController.isVpnEnabled();
+ mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
for (PhoneState state : mPhoneStates) {
if (state.mMobileGroup.getParent() == null) {
@@ -227,14 +224,16 @@
apply();
applyIconTint();
+ mNetworkController.addCallback(this);
+ mSecurityController.addCallback(this);
}
@Override
protected void onDetachedFromWindow() {
mMobileSignalGroup.removeAllViews();
TunerService.get(mContext).removeTunable(this);
- mSC.removeCallback(this);
- mNC.removeCallback(this);
+ mSecurityController.removeCallback(this);
+ mNetworkController.removeCallback(this);
super.onDetachedFromWindow();
}
@@ -253,8 +252,8 @@
post(new Runnable() {
@Override
public void run() {
- mVpnVisible = mSC.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSC.isVpnBranded());
+ mVpnVisible = mSecurityController.isVpnEnabled();
+ mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
apply();
}
});
@@ -265,6 +264,7 @@
boolean activityIn, boolean activityOut, String description) {
mWifiVisible = statusIcon.visible && !mBlockWifi;
mWifiStrengthId = statusIcon.icon;
+ mWifiBadgeId = statusIcon.iconOverlay;
mWifiDescription = statusIcon.contentDescription;
apply();
@@ -405,6 +405,7 @@
mWifi.setImageDrawable(null);
mWifiDark.setImageDrawable(null);
mLastWifiStrengthId = -1;
+ mLastWifiBadgeId = -1;
}
for (PhoneState state : mPhoneStates) {
@@ -470,10 +471,16 @@
(mEthernetVisible ? "VISIBLE" : "GONE")));
if (mWifiVisible) {
- if (mWifiStrengthId != mLastWifiStrengthId) {
- setIconForView(mWifi, mWifiStrengthId);
- setIconForView(mWifiDark, mWifiStrengthId);
+ if (mWifiStrengthId != mLastWifiStrengthId || mWifiBadgeId != mLastWifiBadgeId) {
+ if (mWifiBadgeId == -1) {
+ setIconForView(mWifi, mWifiStrengthId);
+ setIconForView(mWifiDark, mWifiStrengthId);
+ } else {
+ setBadgedWifiIconForView(mWifi, mWifiStrengthId, mWifiBadgeId);
+ setBadgedWifiIconForView(mWifiDark, mWifiStrengthId, mWifiBadgeId);
+ }
mLastWifiStrengthId = mWifiStrengthId;
+ mLastWifiBadgeId = mWifiBadgeId;
}
mWifiGroup.setContentDescription(mWifiDescription);
mWifiGroup.setVisibility(View.VISIBLE);
@@ -535,6 +542,10 @@
// Using the imageView's context to retrieve the Drawable so that theme is preserved.
Drawable icon = imageView.getContext().getDrawable(iconId);
+ setScaledIcon(imageView, icon);
+ }
+
+ private void setScaledIcon(ImageView imageView, Drawable icon) {
if (mIconScaleFactor == 1.f) {
imageView.setImageDrawable(icon);
} else {
@@ -542,6 +553,33 @@
}
}
+ /**
+ * Creates and sets a LayerDrawable from the given ids on the given view.
+ *
+ * <p>This method will also scale the icon by {@link #mIconScaleFactor} if appropriate.
+ */
+ private void setBadgedWifiIconForView(ImageView imageView, @DrawableRes int wifiPieId,
+ @DrawableRes int badgeId) {
+ // Using the imageView's context to retrieve the Drawable so that theme is preserved.;
+ LayerDrawable icon = new LayerDrawable(new Drawable[] {
+ imageView.getContext().getDrawable(wifiPieId),
+ imageView.getContext().getDrawable(badgeId)});
+
+ // The LayerDrawable shares an underlying state so we must mutate the object to change the
+ // color between the light and dark themes.
+ icon.mutate().setTint(getColorAttr(imageView.getContext(), R.attr.singleToneColor));
+
+ setScaledIcon(imageView, icon);
+ }
+
+ /** Returns the given color attribute value, or white if not defined. */
+ @ColorInt private static int getColorAttr(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
+ @ColorInt int colorAccent = ta.getColor(0, Color.WHITE);
+ ta.recycle();
+ return colorAccent;
+ }
+
public void setIconTint(int tint, float darkIntensity, Rect tintArea) {
boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity
|| !mTintArea.equals(tintArea);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index 1c8c317..3bbda4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -30,7 +30,7 @@
import android.widget.LinearLayout;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.ActivityStarter;
import java.net.URISyntaxException;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 7adb36d..f24e40b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -24,8 +24,6 @@
import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -35,15 +33,16 @@
import android.view.WindowManager;
import android.widget.LinearLayout;
import com.android.systemui.BatteryMeterView;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
/**
* A status bar (and navigation bar) tailored for the automotive use case.
@@ -73,6 +72,7 @@
SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
registerPackageChangeReceivers();
+ createBatteryController();
mCarBatteryController.startListening();
mConnectedDeviceSignalController.startListening();
}
@@ -105,7 +105,7 @@
R.dimen.status_bar_connected_device_signal_margin_end));
mConnectedDeviceSignalController = new ConnectedDeviceSignalController(mContext,
- mSignalsView, mBluetoothController);
+ mSignalsView);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "makeStatusBarView(). mBatteryMeterView: " + mBatteryMeterView);
@@ -114,8 +114,7 @@
return statusBarView;
}
- @Override
- protected BatteryController createBatteryController() {
+ private BatteryController createBatteryController() {
mCarBatteryController = new CarBatteryController(mContext);
mCarBatteryController.addBatteryViewHandler(this);
return mCarBatteryController;
@@ -211,8 +210,11 @@
@Override
protected void createUserSwitcher() {
- if (mUserSwitcherController.useFullscreenUserSwitcher()) {
- mFullscreenUserSwitcher = new FullscreenUserSwitcher(this, mUserSwitcherController,
+ UserSwitcherController userSwitcherController =
+ Dependency.get(UserSwitcherController.class);
+ if (userSwitcherController.useFullscreenUserSwitcher()) {
+ mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
+ userSwitcherController,
(ViewStub) mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub));
} else {
super.createUserSwitcher();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
index a3e1b3a..c308930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
@@ -15,6 +15,7 @@
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.ScalingDrawableWrapper;
import com.android.systemui.statusbar.policy.BluetoothController;
@@ -67,10 +68,9 @@
private BluetoothHeadsetClient mBluetoothHeadsetClient;
- public ConnectedDeviceSignalController(Context context, View signalsView,
- BluetoothController controller) {
+ public ConnectedDeviceSignalController(Context context, View signalsView) {
mContext = context;
- mController = controller;
+ mController = Dependency.get(BluetoothController.class);
mSignalsView = signalsView;
mNetworkSignalView = (ImageView)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index a011162..31cfa66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -16,7 +16,10 @@
import android.content.Context;
import android.os.Handler;
+import android.os.Looper;
import android.provider.Settings.Secure;
+
+import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.Prefs.Key;
import com.android.systemui.qs.SecureSetting;
@@ -37,12 +40,12 @@
public AutoTileManager(Context context, QSTileHost host) {
mContext = context;
mHost = host;
- mHandler = new Handler(mHost.getLooper());
+ mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
if (!Prefs.getBoolean(context, Key.QS_HOTSPOT_ADDED, false)) {
- host.getHotspotController().addCallback(mHotspotCallback);
+ Dependency.get(HotspotController.class).addCallback(mHotspotCallback);
}
if (!Prefs.getBoolean(context, Key.QS_DATA_SAVER_ADDED, false)) {
- host.getNetworkController().getDataSaverController().addCallback(mDataSaverListener);
+ Dependency.get(DataSaverController.class).addCallback(mDataSaverListener);
}
if (!Prefs.getBoolean(context, Key.QS_INVERT_COLORS_ADDED, false)) {
mColorsSetting = new SecureSetting(mContext, mHandler,
@@ -52,43 +55,33 @@
if (value != 0) {
mHost.addTile("inversion");
Prefs.putBoolean(mContext, Key.QS_INVERT_COLORS_ADDED, true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mColorsSetting.setListening(false);
- }
- });
+ mHandler.post(() -> mColorsSetting.setListening(false));
}
}
};
mColorsSetting.setListening(true);
}
if (!Prefs.getBoolean(context, Key.QS_WORK_ADDED, false)) {
- host.getManagedProfileController().addCallback(mProfileCallback);
+ Dependency.get(ManagedProfileController.class).addCallback(mProfileCallback);
}
}
public void destroy() {
mColorsSetting.setListening(false);
- mHost.getHotspotController().removeCallback(mHotspotCallback);
- mHost.getNetworkController().getDataSaverController().removeCallback(mDataSaverListener);
- mHost.getManagedProfileController().removeCallback(mProfileCallback);
+ Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
+ Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
+ Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
}
private final ManagedProfileController.Callback mProfileCallback =
new ManagedProfileController.Callback() {
@Override
public void onManagedProfileChanged() {
- if (mHost.getManagedProfileController().hasActiveProfile()) {
+ if (Dependency.get(ManagedProfileController.class).hasActiveProfile()) {
mHost.addTile("work");
Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mHost.getManagedProfileController().removeCallback(
- mProfileCallback);
- }
- });
+ mHandler.post(() -> Dependency.get(ManagedProfileController.class)
+ .removeCallback(mProfileCallback));
}
}
@@ -105,13 +98,8 @@
if (isDataSaving) {
mHost.addTile("saver");
Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mHost.getNetworkController().getDataSaverController().removeCallback(
- mDataSaverListener);
- }
- });
+ mHandler.post(() -> Dependency.get(DataSaverController.class).removeCallback(
+ mDataSaverListener));
}
}
};
@@ -122,12 +110,8 @@
if (enabled) {
mHost.addTile("hotspot");
Prefs.putBoolean(mContext, Key.QS_HOTSPOT_ADDED, true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mHost.getHotspotController().removeCallback(mHotspotCallback);
- }
- });
+ mHandler.post(() -> Dependency.get(HotspotController.class)
+ .removeCallback(mHotspotCallback));
}
}
};
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 a2c106a..79120d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -58,6 +58,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
@@ -66,7 +67,7 @@
import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -230,6 +231,11 @@
mRightAffordanceView.setOnClickListener(this);
mLeftAffordanceView.setOnClickListener(this);
initAccessibility();
+ mActivityStarter = Dependency.get(ActivityStarter.class);
+ mFlashlightController = Dependency.get(FlashlightController.class);
+ mAccessibilityController = Dependency.get(AccessibilityController.class);
+ mAssistManager = Dependency.get(AssistManager.class);
+ updateLeftAffordance();
}
@Override
@@ -299,20 +305,6 @@
mRightAffordanceView.setContentDescription(state.contentDescription);
}
- public void setActivityStarter(ActivityStarter activityStarter) {
- mActivityStarter = activityStarter;
- }
-
- public void setFlashlightController(FlashlightController flashlightController) {
- mFlashlightController = flashlightController;
- }
-
- public void setAccessibilityController(AccessibilityController accessibilityController) {
- mAccessibilityController = accessibilityController;
- mLockIcon.setAccessibilityController(accessibilityController);
- accessibilityController.addStateChangedCallback(this);
- }
-
public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
mPhoneStatusBar = phoneStatusBar;
updateCameraVisibility(); // in case onFinishInflate() was called too early
@@ -761,11 +753,6 @@
mIndicationController = keyguardIndicationController;
}
- public void setAssistManager(AssistManager assistManager) {
- mAssistManager = assistManager;
- updateLeftAffordance();
- }
-
public void updateLeftAffordance() {
updateLeftAffordanceIcon();
updateLeftPreview();
@@ -792,7 +779,7 @@
private final PluginListener<IntentButtonProvider> mRightListener =
new PluginListener<IntentButtonProvider>() {
@Override
- public void onPluginConnected(IntentButtonProvider plugin) {
+ public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
setRightButton(plugin.getIntentButton());
}
@@ -805,7 +792,7 @@
private final PluginListener<IntentButtonProvider> mLeftListener =
new PluginListener<IntentButtonProvider>() {
@Override
- public void onPluginConnected(IntentButtonProvider plugin) {
+ public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
setLeftButton(plugin.getIntentButton());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index e4c778c..ff58e54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -28,13 +28,15 @@
import android.widget.RelativeLayout;
import android.widget.TextView;
-import com.android.systemui.BatteryMeterView;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import java.text.NumberFormat;
@@ -43,7 +45,7 @@
* The header group on Keyguard.
*/
public class KeyguardStatusBarView extends RelativeLayout
- implements BatteryController.BatteryStateChangeCallback {
+ implements BatteryStateChangeCallback, OnUserInfoChangedListener {
private boolean mBatteryCharging;
private boolean mKeyguardUserSwitcherShowing;
@@ -78,6 +80,7 @@
mCarrierLabel = (TextView) findViewById(R.id.keyguard_carrier_text);
loadDimens();
updateUserSwitcher();
+ mBatteryController = Dependency.get(BatteryController.class);
}
@Override
@@ -203,23 +206,25 @@
mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
}
- public void setBatteryController(BatteryController batteryController) {
- mBatteryController = batteryController;
- ((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController);
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ UserInfoController userInfoController = Dependency.get(UserInfoController.class);
+ userInfoController.addCallback(this);
+ mUserSwitcherController = Dependency.get(UserSwitcherController.class);
+ mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController);
+ userInfoController.reloadUserInfo();
}
- public void setUserSwitcherController(UserSwitcherController controller) {
- mUserSwitcherController = controller;
- mMultiUserSwitch.setUserSwitcherController(controller);
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ Dependency.get(UserInfoController.class).removeCallback(this);
}
- public void setUserInfoController(UserInfoController userInfoController) {
- userInfoController.addCallback(new UserInfoController.OnUserInfoChangedListener() {
- @Override
- public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
- mMultiUserAvatar.setImageDrawable(picture);
- }
- });
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+ mMultiUserAvatar.setImageDrawable(picture);
}
public void setQSPanel(QSPanel qsp) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 26b0d53..4535992 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.view.View;
+import com.android.systemui.Dependency;
import com.android.systemui.statusbar.policy.BatteryController;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -60,11 +61,10 @@
private final Rect mLastFullscreenBounds = new Rect();
private final Rect mLastDockedBounds = new Rect();
- public LightBarController(StatusBarIconController statusBarIconController,
- BatteryController batteryController) {
+ public LightBarController(StatusBarIconController statusBarIconController) {
mStatusBarIconController = statusBarIconController;
- mBatteryController = batteryController;
- batteryController.addCallback(this);
+ mBatteryController = Dependency.get(BatteryController.class);
+ mBatteryController.addCallback(this);
}
public void setNavigationBar(LightBarTransitionsController navigationBar) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 695b500..ef42b2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -29,11 +29,12 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.policy.AccessibilityController;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
/**
* Manages the different states and animations of the unlock icon.
*/
-public class LockIcon extends KeyguardAffordanceView {
+public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChangedListener {
private static final int FP_DRAW_OFF_TIMEOUT = 800;
@@ -49,6 +50,7 @@
private boolean mDeviceInteractive;
private boolean mScreenOn;
private boolean mLastScreenOn;
+ private Drawable mUserAvatarIcon;
private TrustDrawable mTrustDrawable;
private final UnlockMethodCache mUnlockMethodCache;
private AccessibilityController mAccessibilityController;
@@ -80,6 +82,12 @@
mTrustDrawable.stop();
}
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+ mUserAvatarIcon = picture;
+ update();
+ }
+
public void setTransientFpError(boolean transientFpError) {
mTransientFpError = transientFpError;
update();
@@ -126,27 +134,33 @@
boolean trustHidden = anyFingerprintIcon;
if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive
|| mScreenOn != mLastScreenOn || force) {
- boolean isAnim = true;
- int iconRes = getAnimationResForTransition(mLastState, state, mLastDeviceInteractive,
+ int iconAnimRes =
+ getAnimationResForTransition(mLastState, state, mLastDeviceInteractive,
mDeviceInteractive, mLastScreenOn, mScreenOn);
- if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
+ boolean isAnim = iconAnimRes != -1;
+ if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
anyFingerprintIcon = true;
useAdditionalPadding = true;
trustHidden = true;
- } else if (iconRes == R.drawable.trusted_state_to_error_animation) {
+ } else if (iconAnimRes == R.drawable.trusted_state_to_error_animation) {
anyFingerprintIcon = true;
useAdditionalPadding = false;
trustHidden = true;
- } else if (iconRes == R.drawable.error_to_trustedstate_animation) {
+ } else if (iconAnimRes == R.drawable.error_to_trustedstate_animation) {
anyFingerprintIcon = true;
useAdditionalPadding = false;
trustHidden = false;
}
- if (iconRes == -1) {
- iconRes = getIconForState(state, mScreenOn, mDeviceInteractive);
- isAnim = false;
+
+ Drawable icon;
+ if (isAnim) {
+ // Load the animation resource.
+ icon = mContext.getDrawable(iconAnimRes);
+ } else {
+ // Load the static icon resource based on the current state.
+ icon = getIconForState(state, mScreenOn, mDeviceInteractive);
}
- Drawable icon = mContext.getDrawable(iconRes);
+
final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
? (AnimatedVectorDrawable) icon
: null;
@@ -175,7 +189,7 @@
animation.start();
}
- if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
+ if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
removeCallbacks(mDrawOffTimeout);
postDelayed(mDrawOffTimeout, FP_DRAW_OFF_TIMEOUT);
} else {
@@ -225,25 +239,38 @@
mAccessibilityController = accessibilityController;
}
- private int getIconForState(int state, boolean screenOn, boolean deviceInteractive) {
+ private Drawable getIconForState(int state, boolean screenOn, boolean deviceInteractive) {
+ int iconRes;
switch (state) {
case STATE_LOCKED:
- return R.drawable.ic_lock_24dp;
+ iconRes = R.drawable.ic_lock_24dp;
+ break;
case STATE_LOCK_OPEN:
- return R.drawable.ic_lock_open_24dp;
+ if (mUnlockMethodCache.isTrustManaged() && mUnlockMethodCache.isTrusted()
+ && mUserAvatarIcon != null) {
+ return mUserAvatarIcon;
+ } else {
+ iconRes = R.drawable.ic_lock_open_24dp;
+ }
+ break;
case STATE_FACE_UNLOCK:
- return com.android.internal.R.drawable.ic_account_circle;
+ iconRes = com.android.internal.R.drawable.ic_account_circle;
+ break;
case STATE_FINGERPRINT:
// If screen is off and device asleep, use the draw on animation so the first frame
// gets drawn.
- return screenOn && deviceInteractive
+ iconRes = screenOn && deviceInteractive
? R.drawable.ic_fingerprint
: R.drawable.lockscreen_fingerprint_draw_on_animation;
+ break;
case STATE_FINGERPRINT_ERROR:
- return R.drawable.ic_fingerprint_error;
+ iconRes = R.drawable.ic_fingerprint_error;
+ break;
default:
throw new IllegalArgumentException();
}
+
+ return mContext.getDrawable(iconRes);
}
private int getAnimationResForTransition(int oldState, int newState,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index fc33ace..316bd5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -38,8 +38,8 @@
private boolean mListening;
private int mCurrentUser;
- public ManagedProfileControllerImpl(QSTileHost host) {
- mContext = host.getContext();
+ public ManagedProfileControllerImpl(Context context) {
+ mContext = context;
mUserManager = UserManager.get(mContext);
mProfiles = new LinkedList<UserInfo>();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 4d4f9d2..3cbac17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -29,7 +29,9 @@
import android.widget.Button;
import android.widget.FrameLayout;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -65,7 +67,7 @@
public void setQsPanel(QSPanel qsPanel) {
mQsPanel = qsPanel;
- setUserSwitcherController(qsPanel.getHost().getUserSwitcherController());
+ setUserSwitcherController(Dependency.get(UserSwitcherController.class));
}
public boolean hasMultipleUsers() {
@@ -134,7 +136,7 @@
Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
getContext(), v, ContactsContract.Profile.CONTENT_URI,
ContactsContract.QuickContact.MODE_LARGE, null);
- mQsPanel.getHost().startActivityDismissingKeyguard(intent);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 3423a3c..d40326a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -64,7 +64,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.LatencyTracker;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.fragments.FragmentHostManager;
@@ -124,16 +126,17 @@
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mCommandQueue = SystemUIApplication.getComponent(getContext(), CommandQueue.class);
+ mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class);
mCommandQueue.addCallbacks(this);
- mPhoneStatusBar = SystemUIApplication.getComponent(getContext(), PhoneStatusBar.class);
- mRecents = SystemUIApplication.getComponent(getContext(), Recents.class);
- mDivider = SystemUIApplication.getComponent(getContext(), Divider.class);
+ mPhoneStatusBar = SysUiServiceProvider.getComponent(getContext(), PhoneStatusBar.class);
+ mRecents = SysUiServiceProvider.getComponent(getContext(), Recents.class);
+ mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
mWindowManager = getContext().getSystemService(WindowManager.class);
mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
if (savedInstanceState != null) {
mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
}
+ mAssistManager = Dependency.get(AssistManager.class);
try {
WindowManagerGlobal.getWindowManagerService()
@@ -400,10 +403,6 @@
ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
homeButton.setOnLongClickListener(this::onHomeLongClick);
-
- if (mAssistManager != null) {
- mAssistManager.onConfigurationChanged();
- }
}
private boolean onHomeTouch(View v, MotionEvent event) {
@@ -436,10 +435,6 @@
}
private void onVerticalChanged(boolean isVertical) {
- if (mAssistManager != null) {
- // TODO: Clean this up.
- mAssistManager.onConfigurationChanged();
- }
mPhoneStatusBar.setQsScrimEnabled(!isVertical);
}
@@ -551,8 +546,7 @@
private boolean onLongPressRecents() {
if (mRecents == null || !ActivityManager.supportsMultiWindow()
- || !mDivider.getView().getSnapAlgorithm()
- .isSplitScreenFeasible()) {
+ || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()) {
return false;
}
@@ -562,11 +556,6 @@
// ----- Methods that PhoneStatusBar talks to (should be minimized) -----
- public void setAssistManager(AssistManager assistManager) {
- mAssistManager = assistManager;
- mAssistManager.onConfigurationChanged();
- }
-
public void setLightBarController(LightBarController lightBarController) {
mLightBarController = lightBarController;
mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
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 b6feb0e..f04a9ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -365,7 +365,7 @@
}
@Override
- public void onPluginConnected(NavBarButtonProvider plugin) {
+ public void onPluginConnected(NavBarButtonProvider plugin, Context context) {
mPlugins.add(plugin);
clearViews();
inflateLayout(mCurrentLayout);
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 31c78c8..319f124 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -765,7 +765,7 @@
}
@Override
- public void onPluginConnected(NavGesture plugin) {
+ public void onPluginConnected(NavGesture plugin, Context context) {
mGestureHelper = plugin.getGestureHelper();
updateTaskSwitchHelper();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 001edb3..8dcc693 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -20,6 +20,7 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
+
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -71,13 +72,11 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -120,8 +119,9 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.BatteryMeterView;
+import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
@@ -130,6 +130,7 @@
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.SystemUIFactory;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeHost;
@@ -138,7 +139,7 @@
import com.android.systemui.fragments.PluginFragmentListener;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
@@ -168,27 +169,22 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
-import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.BatteryControllerImpl;
-import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
-import com.android.systemui.statusbar.policy.CastControllerImpl;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.EncryptionHelper;
-import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HotspotControllerImpl;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
-import com.android.systemui.statusbar.policy.LocationControllerImpl;
import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
-import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
-import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -305,25 +301,8 @@
PhoneStatusBarPolicy mIconPolicy;
- // These are no longer handled by the policy, because we need custom strategies for them
- protected BluetoothControllerImpl mBluetoothController;
- SecurityControllerImpl mSecurityController;
- protected BatteryController mBatteryController;
- LocationControllerImpl mLocationController;
- NetworkControllerImpl mNetworkController;
- HotspotControllerImpl mHotspotController;
- RotationLockControllerImpl mRotationLockController;
- UserInfoControllerImpl mUserInfoController;
- protected ZenModeController mZenModeController;
- CastControllerImpl mCastController;
VolumeComponent mVolumeComponent;
- KeyguardUserSwitcher mKeyguardUserSwitcher;
- FlashlightControllerImpl mFlashlightController;
- protected UserSwitcherController mUserSwitcherController;
- NextAlarmControllerImpl mNextAlarmController;
- protected KeyguardMonitorImpl mKeyguardMonitor;
BrightnessMirrorController mBrightnessMirrorController;
- AccessibilityController mAccessibilityController;
protected FingerprintUnlockController mFingerprintUnlockController;
LightBarController mLightBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
@@ -410,23 +389,16 @@
: null;
private ScreenPinningRequest mScreenPinningRequest;
- private HandlerThread mHandlerThread;
- private HandlerThread mTimeTickThread;
- private Handler mTimeTickHandler;
// ensure quick settings is disabled until the current user makes it through the setup wizard
private boolean mUserSetup = false;
- private ContentObserver mUserSetupObserver = new ContentObserver(new Handler()) {
+ private DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
@Override
- public void onChange(boolean selfChange) {
- final boolean userSetup = 0 != Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE,
- 0 /*default */,
- mCurrentUserId);
+ public void onUserSetupChanged() {
+ final boolean userSetup = mDeviceProvisionedController.isUserSetup(
+ mDeviceProvisionedController.getCurrentUser());
if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " +
- "selfChange=%s userSetup=%s mUserSetup=%s",
- selfChange, userSetup, mUserSetup));
+ "userSetup=%s mUserSetup=%s", userSetup, mUserSetup));
if (userSetup != mUserSetup) {
mUserSetup = userSetup;
@@ -435,9 +407,7 @@
if (mKeyguardBottomArea != null) {
mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
}
- if (mNetworkController != null) {
- mNetworkController.setUserSetupComplete(mUserSetup);
- }
+ updateQsExpansionEnabled();
}
if (mIconPolicy != null) {
mIconPolicy.setCurrentUserSetup(mUserSetup);
@@ -638,6 +608,13 @@
}
};
+ private KeyguardUserSwitcher mKeyguardUserSwitcher;
+ private UserSwitcherController mUserSwitcherController;
+ private NetworkController mNetworkController;
+ private KeyguardMonitorImpl mKeyguardMonitor;
+ private BatteryController mBatteryController;
+ private DeviceProvisionedController mDeviceProvisionedController;
+
private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
final int N = array.size();
for (int i = 0 ; i < N; i++) {
@@ -671,19 +648,20 @@
@Override
public void start() {
+ mNetworkController = Dependency.get(NetworkController.class);
+ mUserSwitcherController = Dependency.get(UserSwitcherController.class);
+ mKeyguardMonitor = (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class);
+ mBatteryController = Dependency.get(BatteryController.class);
+ mAssistManager = Dependency.get(AssistManager.class);
+ mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+
mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
updateDisplaySize();
mScrimSrcModeEnabled = mContext.getResources().getBoolean(
R.bool.config_status_bar_scrim_behind_use_src);
- // Background thread for any controllers that need it.
- mHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
- mHandlerThread.start();
- mTimeTickThread = new HandlerThread("TimeTick");
- mTimeTickThread.start();
- mTimeTickHandler = new Handler(mTimeTickThread.getLooper());
- DateTimeView.setReceiverHandler(mTimeTickHandler);
+ DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
putComponent(PhoneStatusBar.class, this);
super.start(); // calls createAndAddWindows()
@@ -694,9 +672,7 @@
// in session state
// Lastly, call to the icon policy to install/update all the icons.
- mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController, mCastController,
- mHotspotController, mUserInfoController, mBluetoothController,
- mRotationLockController, mNetworkController.getDataSaverController(), mNextAlarmController);
+ mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
mIconPolicy.setCurrentUserSetup(mUserSetup);
mSettingsObserver.onChange(false); // set up
@@ -717,12 +693,11 @@
mDozeServiceHost = new DozeServiceHost();
putComponent(DozeHost.class, mDozeServiceHost);
- setControllerUsers();
-
notifyUserAboutHiddenNotifications();
mScreenPinningRequest = new ScreenPinningRequest(mContext);
mFalsingManager = FalsingManager.getInstance(mContext);
+ Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
}
protected void createIconController() {
@@ -796,8 +771,6 @@
// no window manager? good luck with that
}
- mAssistManager = SystemUIFactory.getInstance().createAssistManager(this, context);
-
// figure out which pixel-format to use for the status bar.
mPixelFormat = PixelFormat.OPAQUE;
@@ -828,9 +801,8 @@
(KeyguardStatusView) mStatusBarWindow.findViewById(R.id.keyguard_status_view);
mKeyguardBottomArea =
(KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
- mKeyguardBottomArea.setActivityStarter(this);
- mKeyguardBottomArea.setAssistManager(mAssistManager);
- mKeyguardIndicationController = new KeyguardIndicationController(mContext,
+ mKeyguardIndicationController =
+ SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
(ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
mKeyguardBottomArea.getLockIcon());
mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController);
@@ -840,7 +812,7 @@
createIconController();
- mBatteryController = createBatteryController();
+ // TODO: Find better place for this callback.
mBatteryController.addCallback(new BatteryStateChangeCallback() {
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
@@ -856,7 +828,7 @@
}
});
- mLightBarController = new LightBarController(mIconController, mBatteryController);
+ mLightBarController = new LightBarController(mIconController);
if (mNavigationBar != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
@@ -885,36 +857,12 @@
mNotificationPanel);
// Other icons
- mLocationController = new LocationControllerImpl(mContext,
- mHandlerThread.getLooper()); // will post a notification
- mNetworkController = new NetworkControllerImpl(mContext, mHandlerThread.getLooper());
- mNetworkController.setUserSetupComplete(mUserSetup);
- mHotspotController = new HotspotControllerImpl(mContext);
- mBluetoothController = new BluetoothControllerImpl(mContext, mHandlerThread.getLooper());
- mSecurityController = new SecurityControllerImpl(mContext);
- if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
- mRotationLockController = new RotationLockControllerImpl(mContext);
- }
- mUserInfoController = new UserInfoControllerImpl(mContext);
mVolumeComponent = getComponent(VolumeComponent.class);
- if (mVolumeComponent != null) {
- mZenModeController = mVolumeComponent.getZenController();
- }
- mCastController = new CastControllerImpl(mContext);
- initSignalCluster(mStatusBarView);
- initSignalCluster(mKeyguardStatusBar);
initEmergencyCryptkeeperText();
- mFlashlightController = new FlashlightControllerImpl(mContext);
- mKeyguardBottomArea.setFlashlightController(mFlashlightController);
mKeyguardBottomArea.setPhoneStatusBar(this);
mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
- mAccessibilityController = new AccessibilityController(mContext);
- mKeyguardBottomArea.setAccessibilityController(mAccessibilityController);
- mNextAlarmController = new NextAlarmControllerImpl(mContext);
- mKeyguardMonitor = new KeyguardMonitorImpl(mContext);
- mUserSwitcherController = createUserSwitcherController();
if (UserManager.get(mContext).isUserSwitcherEnabled()) {
createUserSwitcher();
}
@@ -923,15 +871,13 @@
View container = mStatusBarWindow.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
- new PluginFragmentListener(container, QS.TAG, R.id.qs_frame, QSFragment.class, QS.class)
+ fragmentHostManager.getFragmentManager().beginTransaction()
+ .replace(R.id.qs_frame, new QSFragment(), QS.TAG)
+ .commit();
+ new PluginFragmentListener(container, QS.TAG, QSFragment.class, QS.class)
.startListening(QS.ACTION, QS.VERSION);
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
- mBluetoothController, mLocationController, mRotationLockController,
- mNetworkController, mZenModeController, mHotspotController,
- mCastController, mFlashlightController,
- mUserSwitcherController, mUserInfoController, mKeyguardMonitor,
- mSecurityController, mBatteryController, mIconController,
- mNextAlarmController);
+ mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
@@ -941,21 +887,9 @@
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mKeyguardStatusBar.setQSPanel(mQSPanel);
}
- mHeader = qs.getHeader();
- initSignalCluster(mHeader);
- mHeader.setActivityStarter(PhoneStatusBar.this);
});
}
- // User info. Trigger first load.
- mKeyguardStatusBar.setUserInfoController(mUserInfoController);
- mKeyguardStatusBar.setUserSwitcherController(mUserSwitcherController);
- mUserInfoController.reloadUserInfo();
-
- ((BatteryMeterView) mStatusBarView.findViewById(R.id.battery)).setBatteryController(
- mBatteryController);
- mKeyguardStatusBar.setBatteryController(mBatteryController);
-
mReportRejectedTouch = mStatusBarWindow.findViewById(R.id.report_rejected_touch);
if (mReportRejectedTouch != null) {
updateReportRejectedTouchVisibility();
@@ -1016,7 +950,8 @@
android.Manifest.permission.DUMP, null);
// listen for USER_SETUP_COMPLETE setting (per-user)
- resetUserSetupObserver();
+ mDeviceProvisionedController.addCallback(mUserSetupObserver);
+ mUserSetupObserver.onUserSetupChanged();
// disable profiling bars, since they overlap and clutter the output on app windows
ThreadedRenderer.overrideProperty("disableProfileBars", "true");
@@ -1027,21 +962,9 @@
return mStatusBarView;
}
- public Handler getTimeTickHandler() {
- return mTimeTickHandler;
- }
-
- public static Handler getTimeTickHandler(Context context) {
- PhoneStatusBar statusBar = ((SysUiServiceProvider) context.getApplicationContext())
- .getComponent(PhoneStatusBar.class);
- return statusBar != null ? statusBar.getTimeTickHandler() :
- new Handler(Looper.getMainLooper());
- }
-
protected void createNavigationBar() {
mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
mNavigationBar = (NavigationBarFragment) fragment;
- mNavigationBar.setAssistManager(mAssistManager);
if (mLightBarController != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
@@ -1067,10 +990,6 @@
}
}
- protected BatteryController createBatteryController() {
- return new BatteryControllerImpl(mContext);
- }
-
private void inflateShelf() {
mNotificationShelf =
(NotificationShelf) LayoutInflater.from(mContext).inflate(
@@ -1096,10 +1015,10 @@
inflateEmptyShadeView();
updateEmptyShadeView();
mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged();
- mUserInfoController.onDensityOrFontScaleChanged();
- if (mUserSwitcherController != null) {
- mUserSwitcherController.onDensityOrFontScaleChanged();
- }
+ // TODO: Bring these out of PhoneStatusBar.
+ ((UserInfoControllerImpl) Dependency.get(UserInfoController.class))
+ .onDensityOrFontScaleChanged();
+ Dependency.get(UserSwitcherController.class).onDensityOrFontScaleChanged();
if (mKeyguardUserSwitcher != null) {
mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
}
@@ -1129,8 +1048,6 @@
R.dimen.signal_cluster_margin_start),
0, 0, 0);
newCluster.setLayoutParams(layoutParams);
- newCluster.setSecurityController(mSecurityController);
- newCluster.setNetworkController(mNetworkController);
viewParent.addView(newCluster, index);
return newCluster;
}
@@ -1161,11 +1078,7 @@
protected void createUserSwitcher() {
mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
(ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
- mKeyguardStatusBar, mNotificationPanel, mUserSwitcherController);
- }
-
- protected UserSwitcherController createUserSwitcherController() {
- return new UserSwitcherController(mContext, mKeyguardMonitor, mHandler, this);
+ mKeyguardStatusBar, mNotificationPanel);
}
protected void inflateStatusBarWindow(Context context) {
@@ -1173,15 +1086,6 @@
R.layout.super_status_bar, null);
}
- protected void initSignalCluster(View containerView) {
- SignalClusterView signalCluster =
- (SignalClusterView) containerView.findViewById(R.id.signal_cluster);
- if (signalCluster != null) {
- signalCluster.setSecurityController(mSecurityController);
- signalCluster.setNetworkController(mNetworkController);
- }
- }
-
public void clearAllNotifications() {
// animate-swipe all dismissable notifications, then animate the shade closed
@@ -1276,6 +1180,8 @@
mFingerprintUnlockController);
mKeyguardIndicationController.setStatusBarKeyguardViewManager(
mStatusBarKeyguardViewManager);
+ mKeyguardIndicationController.setUserInfoController(
+ Dependency.get(UserInfoController.class));
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mIconPolicy.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
@@ -1485,7 +1391,7 @@
newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
- sbn.getOpPkg(), sbn.getNotificationChannel(),
+ sbn.getOpPkg(),
sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
@@ -2383,6 +2289,11 @@
}
@Override
+ public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) {
+ startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade);
+ }
+
+ @Override
public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
startActivityDismissingKeyguard(intent, false, dismissShade, callback);
}
@@ -3229,30 +3140,7 @@
if (mStatusBarWindowManager != null) {
mStatusBarWindowManager.dump(fd, pw, args);
}
- if (mNetworkController != null) {
- mNetworkController.dump(fd, pw, args);
- }
- if (mBluetoothController != null) {
- mBluetoothController.dump(fd, pw, args);
- }
- if (mHotspotController != null) {
- mHotspotController.dump(fd, pw, args);
- }
- if (mCastController != null) {
- mCastController.dump(fd, pw, args);
- }
- if (mUserSwitcherController != null) {
- mUserSwitcherController.dump(fd, pw, args);
- }
- if (mBatteryController != null) {
- mBatteryController.dump(fd, pw, args);
- }
- if (mNextAlarmController != null) {
- mNextAlarmController.dump(fd, pw, args);
- }
- if (mSecurityController != null) {
- mSecurityController.dump(fd, pw, args);
- }
+
if (mHeadsUpManager != null) {
mHeadsUpManager.dump(fd, pw, args);
} else {
@@ -3266,9 +3154,6 @@
if (KeyguardUpdateMonitor.getInstance(mContext) != null) {
KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
}
- if (mFlashlightController != null) {
- mFlashlightController.dump(fd, pw, args);
- }
FalsingManager.getInstance(mContext).dump(pw);
FalsingLog.dump(pw);
@@ -3495,7 +3380,6 @@
updateRowStates();
mScreenPinningRequest.onConfigurationChanged();
- mNetworkController.onConfigurationChanged();
}
@Override
@@ -3505,8 +3389,6 @@
animateCollapsePanels();
updatePublicMode();
updateNotifications();
- resetUserSetupObserver();
- setControllerUsers();
clearCurrentMediaNotification();
setLockscreenUser(newUserId);
}
@@ -3517,26 +3399,6 @@
updateMediaMetaData(true, false);
}
- private void setControllerUsers() {
- if (mZenModeController != null) {
- mZenModeController.setUserId(mCurrentUserId);
- }
- if (mSecurityController != null) {
- mSecurityController.onUserSwitched(mCurrentUserId);
- }
- if (mNetworkController != null) {
- mNetworkController.onUserSwitched(mCurrentUserId);
- }
- }
-
- private void resetUserSetupObserver() {
- mContext.getContentResolver().unregisterContentObserver(mUserSetupObserver);
- mUserSetupObserver.onChange(false);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), true,
- mUserSetupObserver, mCurrentUserId);
- }
-
/**
* Reload some of our resources when the configuration changes.
*
@@ -3717,32 +3579,24 @@
}
};
+ @Override
public void postQSRunnableDismissingKeyguard(final Runnable runnable) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mLeaveOpenOnKeyguardHide = true;
- executeRunnableDismissingKeyguard(runnable, null, false, false, false);
- }
+ mHandler.post(() -> {
+ mLeaveOpenOnKeyguardHide = true;
+ executeRunnableDismissingKeyguard(() -> mHandler.post(runnable), null, false, false,
+ false);
});
}
+ @Override
public void postStartActivityDismissingKeyguard(final PendingIntent intent) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- startPendingIntentDismissingKeyguard(intent);
- }
- });
+ mHandler.post(() -> startPendingIntentDismissingKeyguard(intent));
}
+ @Override
public void postStartActivityDismissingKeyguard(final Intent intent, int delay) {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- handleStartActivityDismissingKeyguard(intent, true /*onlyProvisioned*/);
- }
- }, delay);
+ mHandler.postDelayed(() ->
+ handleStartActivityDismissingKeyguard(intent, true /*onlyProvisioned*/), delay);
}
private void handleStartActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned) {
@@ -3794,10 +3648,6 @@
mWindowManager.removeViewImmediate(mNavigationBarView);
mNavigationBarView = null;
}
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- mHandlerThread = null;
- }
mContext.unregisterReceiver(mBroadcastReceiver);
mContext.unregisterReceiver(mDemoReceiver);
mAssistManager.destroy();
@@ -3808,12 +3658,11 @@
(SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
final SignalClusterView signalClusterQs =
(SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
- mNetworkController.removeCallback(signalCluster);
- mNetworkController.removeCallback(signalClusterKeyguard);
- mNetworkController.removeCallback(signalClusterQs);
if (mQSPanel != null && mQSPanel.getHost() != null) {
mQSPanel.getHost().destroy();
}
+ Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null);
+ mDeviceProvisionedController.removeCallback(mUserSetupObserver);
}
private boolean mDemoModeAllowed;
@@ -4759,12 +4608,13 @@
return !mNotificationData.getActiveNotifications().isEmpty();
}
- public void wakeUpIfDozing(long time, MotionEvent event) {
+ @Override
+ public void wakeUpIfDozing(long time, PointF where) {
if (mDozing && mDozeScrimController.isPulsing()) {
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
pm.wakeUp(time, "com.android.systemui:NODOZE");
mWakeUpComingFromTouch = true;
- mWakeUpTouchLocation = new PointF(event.getX(), event.getY());
+ mWakeUpTouchLocation = where;
mNotificationPanel.setTouchDisabled(false);
mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
mFalsingManager.onScreenOnFromTouch();
@@ -4988,7 +4838,7 @@
@Override
public boolean isPowerSaveActive() {
- return mBatteryController != null && mBatteryController.isPowerSave();
+ return mBatteryController.isPowerSave();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 9ee1e8f..1044ecf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -36,6 +36,7 @@
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
@@ -99,22 +100,19 @@
private BluetoothController mBluetooth;
- public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController,
- CastController cast, HotspotController hotspot, UserInfoController userInfoController,
- BluetoothController bluetooth, RotationLockController rotationLockController,
- DataSaverController dataSaver, NextAlarmController nextAlarm) {
+ public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController) {
mContext = context;
mIconController = iconController;
- mCast = cast;
- mHotspot = hotspot;
- mBluetooth = bluetooth;
+ mCast = Dependency.get(CastController.class);
+ mHotspot = Dependency.get(HotspotController.class);
+ mBluetooth = Dependency.get(BluetoothController.class);
mBluetooth.addCallback(this);
- mNextAlarm = nextAlarm;
+ mNextAlarm = Dependency.get(NextAlarmController.class);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- mUserInfoController = userInfoController;
+ mUserInfoController = Dependency.get(UserInfoController.class);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mRotationLockController = rotationLockController;
- mDataSaver = dataSaver;
+ mRotationLockController = Dependency.get(RotationLockController.class);
+ mDataSaver = Dependency.get(DataSaverController.class);
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index d4cf533..2f76cb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -17,15 +17,11 @@
package com.android.systemui.statusbar.phone;
import android.app.ActivityManager;
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -33,8 +29,8 @@
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.external.CustomTile;
@@ -58,20 +54,6 @@
import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.WorkModeTile;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -80,7 +62,6 @@
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
/** Platform implementation of the quick settings tile host **/
public class QSTileHost implements QSTile.Host, Tunable {
@@ -93,81 +74,31 @@
private final PhoneStatusBar mStatusBar;
private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
protected final ArrayList<String> mTileSpecs = new ArrayList<>();
- private final BluetoothController mBluetooth;
- private final LocationController mLocation;
- private final RotationLockController mRotation;
- private final NetworkController mNetwork;
- private final ZenModeController mZen;
- private final HotspotController mHotspot;
- private final CastController mCast;
- private final Looper mLooper;
- private final FlashlightController mFlashlight;
- private final UserSwitcherController mUserSwitcherController;
- private final UserInfoController mUserInfoController;
- private final KeyguardMonitor mKeyguard;
- private final SecurityController mSecurity;
- private final BatteryController mBattery;
- private final StatusBarIconController mIconController;
private final TileServices mServices;
private final List<Callback> mCallbacks = new ArrayList<>();
private final AutoTileManager mAutoTiles;
- private final ManagedProfileController mProfileController;
- private final NextAlarmController mNextAlarmController;
- private final HandlerThread mHandlerThread;
- private View mHeader;
+ private final StatusBarIconController mIconController;
private int mCurrentUser;
public QSTileHost(Context context, PhoneStatusBar statusBar,
- BluetoothController bluetooth, LocationController location,
- RotationLockController rotation, NetworkController network,
- ZenModeController zen, HotspotController hotspot,
- CastController cast, FlashlightController flashlight,
- UserSwitcherController userSwitcher, UserInfoController userInfo,
- KeyguardMonitor keyguard, SecurityController security,
- BatteryController battery, StatusBarIconController iconController,
- NextAlarmController nextAlarmController) {
+ StatusBarIconController iconController) {
+ mIconController = iconController;
mContext = context;
mStatusBar = statusBar;
- mBluetooth = bluetooth;
- mLocation = location;
- mRotation = rotation;
- mNetwork = network;
- mZen = zen;
- mHotspot = hotspot;
- mCast = cast;
- mFlashlight = flashlight;
- mUserSwitcherController = userSwitcher;
- mUserInfoController = userInfo;
- mKeyguard = keyguard;
- mSecurity = security;
- mBattery = battery;
- mIconController = iconController;
- mNextAlarmController = nextAlarmController;
- mProfileController = new ManagedProfileControllerImpl(this);
- mHandlerThread = new HandlerThread(QSTileHost.class.getSimpleName(),
- Process.THREAD_PRIORITY_BACKGROUND);
- mHandlerThread.start();
- mLooper = mHandlerThread.getLooper();
-
- mServices = new TileServices(this, mLooper);
+ mServices = new TileServices(this, Dependency.get(Dependency.BG_LOOPER));
TunerService.get(mContext).addTunable(this, TILES_SETTING);
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
mAutoTiles = new AutoTileManager(context, this);
}
- public NextAlarmController getNextAlarmController() {
- return mNextAlarmController;
- }
-
- public void setHeaderView(View view) {
- mHeader = view;
+ public StatusBarIconController getIconController() {
+ return mIconController;
}
public void destroy() {
- mHandlerThread.quitSafely();
mTiles.values().forEach(tile -> tile.destroy());
mAutoTiles.destroy();
TunerService.get(mContext).removeTunable(this);
@@ -190,30 +121,10 @@
}
@Override
- public void startActivityDismissingKeyguard(final Intent intent) {
- mStatusBar.postStartActivityDismissingKeyguard(intent, 0);
- }
-
- @Override
- public void startActivityDismissingKeyguard(PendingIntent intent) {
- mStatusBar.postStartActivityDismissingKeyguard(intent);
- }
-
- @Override
- public void startRunnableDismissingKeyguard(Runnable runnable) {
- mStatusBar.postQSRunnableDismissingKeyguard(runnable);
- }
-
- @Override
public void warn(String message, Throwable t) {
// already logged
}
- public void animateToggleQSExpansion() {
- // TODO: Better path to animated panel expansion.
- mHeader.callOnClick();
- }
-
@Override
public void collapsePanels() {
mStatusBar.postAnimateCollapsePanels();
@@ -225,91 +136,15 @@
}
@Override
- public Looper getLooper() {
- return mLooper;
- }
-
- @Override
public Context getContext() {
return mContext;
}
- @Override
- public BluetoothController getBluetoothController() {
- return mBluetooth;
- }
-
- @Override
- public LocationController getLocationController() {
- return mLocation;
- }
-
- @Override
- public RotationLockController getRotationLockController() {
- return mRotation;
- }
-
- @Override
- public NetworkController getNetworkController() {
- return mNetwork;
- }
-
- @Override
- public ZenModeController getZenModeController() {
- return mZen;
- }
-
- @Override
- public HotspotController getHotspotController() {
- return mHotspot;
- }
-
- @Override
- public CastController getCastController() {
- return mCast;
- }
-
- @Override
- public FlashlightController getFlashlightController() {
- return mFlashlight;
- }
-
- @Override
- public KeyguardMonitor getKeyguardMonitor() {
- return mKeyguard;
- }
-
- @Override
- public UserSwitcherController getUserSwitcherController() {
- return mUserSwitcherController;
- }
-
- @Override
- public UserInfoController getUserInfoController() {
- return mUserInfoController;
- }
-
- @Override
- public BatteryController getBatteryController() {
- return mBattery;
- }
-
- public SecurityController getSecurityController() {
- return mSecurity;
- }
public TileServices getTileServices() {
return mServices;
}
- public StatusBarIconController getIconController() {
- return mIconController;
- }
-
- public ManagedProfileController getManagedProfileController() {
- return mProfileController;
- }
-
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
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 2fa961d..a706408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -40,10 +40,11 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.keyguard.KeyguardStatusView;
import com.android.settingslib.Utils;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.BatteryMeterView;
+import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
import com.android.systemui.plugins.qs.QS.Callback;
import com.android.systemui.qs.QSPanel;
@@ -51,10 +52,9 @@
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;
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.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -71,6 +71,7 @@
private ActivityStarter mActivityStarter;
private NextAlarmController mNextAlarmController;
+ private UserInfoController mUserInfoController;
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
@@ -118,7 +119,8 @@
mEdit = findViewById(android.R.id.edit);
findViewById(android.R.id.edit).setOnClickListener(view ->
- mHost.startRunnableDismissingKeyguard(() -> mQsPanel.showEdit(view)));
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
+ mQsPanel.showEdit(view)));
mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group);
mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
@@ -151,6 +153,15 @@
((RippleDrawable) mExpandIndicator.getBackground()).setForceSoftware(true);
updateResources();
+
+ // Set the light/dark theming on the header status UI to match the current theme.
+ SignalClusterView cluster = (SignalClusterView) findViewById(R.id.signal_cluster);
+ int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
+ float intensity = colorForeground == Color.WHITE ? 0 : 1;
+ cluster.setIconTint(colorForeground, intensity, new Rect(0, 0, 0, 0));
+ BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery);
+ int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary);
+ battery.setRawColors(colorForeground, colorSecondary);
}
@Override
@@ -248,11 +259,18 @@
mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mNextAlarmController = Dependency.get(NextAlarmController.class);
+ mUserInfoController = Dependency.get(UserInfoController.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
+ }
+
+ @Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
- mHost.getUserInfoController().removeCallback(this);
- mHost.getNetworkController().removeEmergencyListener(this);
super.onDetachedFromWindow();
}
@@ -304,16 +322,17 @@
private void updateListeners() {
if (mListening) {
mNextAlarmController.addCallback(this);
+ mUserInfoController.addCallback(this);
+ if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
+ Dependency.get(NetworkController.class).addEmergencyListener(this);
+ }
} else {
mNextAlarmController.removeCallback(this);
+ mUserInfoController.removeCallback(this);
+ Dependency.get(NetworkController.class).removeEmergencyListener(this);
}
}
- @Override
- public void setActivityStarter(ActivityStarter activityStarter) {
- mActivityStarter = activityStarter;
- }
-
public void setQSPanel(final QSPanel qsPanel) {
mQsPanel = qsPanel;
setupHost(qsPanel.getHost());
@@ -324,30 +343,9 @@
public void setupHost(final QSTileHost host) {
mHost = host;
- host.setHeaderView(mExpandIndicator);
+ //host.setHeaderView(mExpandIndicator);
mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
mHeaderQsPanel.setHost(host, null /* No customization in header */);
- setUserInfoController(host.getUserInfoController());
- setBatteryController(host.getBatteryController());
- setNextAlarmController(host.getNextAlarmController());
-
- final boolean isAPhone = mHost.getNetworkController().hasVoiceCallingFeature();
- if (isAPhone) {
- mHost.getNetworkController().addEmergencyListener(this);
- }
-
- // Set the light/dark theming on the header status UI to match the current theme.
- SignalClusterView cluster = (SignalClusterView) findViewById(R.id.signal_cluster);
- cluster.setNetworkController((NetworkControllerImpl) host.getNetworkController());
- cluster.setSecurityController(host.getSecurityController());
- int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
- float intensity = colorForeground / (float) Color.WHITE;
- cluster.setIconTint(colorForeground, intensity,
- new Rect(0, 0, 0, 0));
- BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery);
- battery.setBatteryController(host.getBatteryController());
- int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary);
- battery.setRawColors(colorForeground, colorSecondary);
}
@Override
@@ -357,7 +355,7 @@
mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
: MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
if (mSettingsButton.isTunerClick()) {
- mHost.startRunnableDismissingKeyguard(() -> post(() -> {
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
if (TunerService.isTunerEnabled(mContext)) {
TunerService.showResetRequest(mContext, () -> {
// Relaunch settings so that the tuner disappears.
@@ -370,7 +368,7 @@
}
startSettingsActivity();
- }));
+ });
} else {
startSettingsActivity();
}
@@ -385,18 +383,6 @@
true /* dismissShade */);
}
- public void setNextAlarmController(NextAlarmController nextAlarmController) {
- mNextAlarmController = nextAlarmController;
- }
-
- public void setBatteryController(BatteryController batteryController) {
- batteryController.addCallback(this);
- }
-
- public void setUserInfoController(UserInfoController userInfoController) {
- userInfoController.addCallback(this);
- }
-
@Override
public void setCallback(Callback qsPanelCallback) {
mHeaderQsPanel.setCallback(qsPanelCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 1b73a3f..ccd6357 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -29,6 +29,7 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
import android.media.session.MediaSessionLegacyHelper;
import android.net.Uri;
import android.os.Bundle;
@@ -223,7 +224,8 @@
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
if (mService.isDozing()) {
- MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, true);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
return true;
}
break;
@@ -263,12 +265,9 @@
if (mNotificationPanel.isFullyExpanded()
&& mStackScrollLayout.getVisibility() == View.VISIBLE
&& mService.getBarState() == StatusBarState.KEYGUARD
- && !mService.isBouncerShowing()) {
+ && !mService.isBouncerShowing()
+ && !mService.isDozing()) {
intercept = mDragDownHelper.onInterceptTouchEvent(ev);
- // wake up on a touch down event, if dozing
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mService.wakeUpIfDozing(ev.getEventTime(), ev);
- }
}
if (!intercept) {
super.onInterceptTouchEvent(ev);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 19dcf03..48ff1c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -17,12 +17,13 @@
package com.android.systemui.statusbar.policy;
import com.android.systemui.DemoMode;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-public interface BatteryController extends DemoMode,
+public interface BatteryController extends DemoMode, Dumpable,
CallbackController<BatteryStateChangeCallback> {
/**
* Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 4c1c378..df30e20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -17,11 +17,12 @@
package com.android.systemui.statusbar.policy;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.BluetoothController.Callback;
import java.util.Collection;
-public interface BluetoothController extends CallbackController<Callback> {
+public interface BluetoothController extends CallbackController<Callback>, Dumpable {
boolean isBluetoothSupported();
boolean isBluetoothEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index 3142ddf..e7056a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -47,7 +47,7 @@
private final ArrayList<SignalCallback> mSignalCallbacks = new ArrayList<>();
public CallbackHandler() {
- super();
+ super(Looper.getMainLooper());
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index 6988af7..97be6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -16,11 +16,12 @@
package com.android.systemui.statusbar.policy;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.CastController.Callback;
import java.util.Set;
-public interface CastController extends CallbackController<Callback> {
+public interface CastController extends CallbackController<Callback>, Dumpable {
void setDiscovering(boolean request);
void setCurrentUserId(int currentUserId);
Set<CastDevice> getCastDevices();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 1dbc664..9cc9749 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -38,8 +38,8 @@
import android.widget.TextView;
import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -107,7 +107,7 @@
filter.addAction(Intent.ACTION_USER_SWITCHED);
getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter,
- null, PhoneStatusBar.getTimeTickHandler(getContext()));
+ null, Dependency.get(Dependency.TIME_TICK_HANDLER));
TunerService.get(getContext()).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_BLACKLIST);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index 5544c70..dc33633 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -23,12 +23,11 @@
import android.content.res.TypedArray;
import android.icu.text.DateFormat;
import android.icu.text.DisplayContext;
-import android.os.Handler;
import android.util.AttributeSet;
import android.widget.TextView;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.Date;
import java.util.Locale;
@@ -87,7 +86,7 @@
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
getContext().registerReceiver(mIntentReceiver, filter, null,
- PhoneStatusBar.getTimeTickHandler(getContext()));
+ Dependency.get(Dependency.TIME_TICK_HANDLER));
updateClock();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
new file mode 100644
index 0000000..aa4eaa7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.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.statusbar.policy;
+
+import android.content.Context;
+
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
+public interface DeviceProvisionedController extends CallbackController<DeviceProvisionedListener> {
+
+ boolean isDeviceProvisioned();
+ boolean isUserSetup(int currentUser);
+ int getCurrentUser();
+
+ interface DeviceProvisionedListener {
+ default void onDeviceProvisionedChanged() { }
+ default void onUserSwitched() { }
+ default void onUserSetupChanged() { }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
new file mode 100644
index 0000000..528fefe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.settings.CurrentUserTracker;
+
+import java.util.ArrayList;
+
+public class DeviceProvisionedControllerImpl extends CurrentUserTracker implements
+ DeviceProvisionedController {
+
+ private final ArrayList<DeviceProvisionedListener> mListeners = new ArrayList<>();
+ private final ContentResolver mContentResolver;
+ private final Context mContext;
+ private final Uri mDeviceProvisionedUri;
+ private final Uri mUserSetupUri;
+
+ public DeviceProvisionedControllerImpl(Context context) {
+ super(context);
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mDeviceProvisionedUri = Global.getUriFor(Global.DEVICE_PROVISIONED);
+ mUserSetupUri = Secure.getUriFor(Secure.USER_SETUP_COMPLETE);
+ }
+
+ @Override
+ public boolean isDeviceProvisioned() {
+ return Global.getInt(mContentResolver, Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+
+ @Override
+ public boolean isUserSetup(int currentUser) {
+ return Secure.getIntForUser(mContentResolver, Secure.USER_SETUP_COMPLETE, 0, currentUser)
+ != 0;
+ }
+
+ @Override
+ public int getCurrentUser() {
+ return ActivityManager.getCurrentUser();
+ }
+
+ @Override
+ public void addCallback(DeviceProvisionedListener listener) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ startListening(getCurrentUser());
+ }
+ }
+
+ @Override
+ public void removeCallback(DeviceProvisionedListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ stopListening();
+ }
+ }
+
+ private void startListening(int user) {
+ mContentResolver.registerContentObserver(mDeviceProvisionedUri, true,
+ mSettingsObserver, 0);
+ mContentResolver.registerContentObserver(mUserSetupUri, true,
+ mSettingsObserver, user);
+ startTracking();
+ }
+
+ private void stopListening() {
+ stopTracking();
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
+ }
+
+ @Override
+ public void onUserSwitched(int newUserId) {
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
+ mContentResolver.registerContentObserver(mDeviceProvisionedUri, true,
+ mSettingsObserver, 0);
+ mContentResolver.registerContentObserver(mUserSetupUri, true,
+ mSettingsObserver, newUserId);
+ notifyUserChanged();
+ }
+
+ private void notifyUserChanged() {
+ mListeners.forEach(c -> c.onUserSwitched());
+ }
+
+ private void notifySetupChanged() {
+ mListeners.forEach(c -> c.onUserSetupChanged());
+ }
+
+ private void notifyProvisionedChanged() {
+ mListeners.forEach(c -> c.onDeviceProvisionedChanged());
+ }
+
+ protected final ContentObserver mSettingsObserver = new ContentObserver(Dependency.get(
+ Dependency.MAIN_HANDLER)) {
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (mUserSetupUri.equals(uri)) {
+ notifySetupChanged();
+ } else {
+ notifyProvisionedChanged();
+ }
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 6023f3e..e576f36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -14,9 +14,10 @@
package com.android.systemui.statusbar.policy;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
-public interface FlashlightController extends CallbackController<FlashlightListener> {
+public interface FlashlightController extends CallbackController<FlashlightListener>, Dumpable {
boolean hasFlashlight();
void setFlashlight(boolean newState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index daf9d6b..0543678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -16,9 +16,10 @@
package com.android.systemui.statusbar.policy;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
-public interface HotspotController extends CallbackController<Callback> {
+public interface HotspotController extends CallbackController<Callback>, Dumpable {
boolean isHotspotEnabled();
void setHotspotEnabled(boolean enabled);
boolean isHotspotSupported();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index 1cf4050..4b283fed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -30,6 +30,7 @@
import android.widget.FrameLayout;
import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.tiles.UserDetailItemView;
@@ -56,10 +57,10 @@
private boolean mAnimating;
public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
- KeyguardStatusBarView statusBarView, NotificationPanelView panelView,
- UserSwitcherController userSwitcherController) {
+ KeyguardStatusBarView statusBarView, NotificationPanelView panelView) {
boolean keyguardUserSwitcherEnabled =
context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
+ UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
if (userSwitcherController != null && keyguardUserSwitcherEnabled) {
mUserSwitcherContainer = (Container) userSwitcher.inflate();
mBackground = new KeyguardUserSwitcherScrim(context);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 082fe82..a3a9d71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -19,19 +19,22 @@
import android.content.Context;
import android.content.Intent;
import android.telephony.SubscriptionInfo;
+import android.view.View;
+
import com.android.settingslib.net.DataUsageController;
import com.android.settingslib.wifi.AccessPoint;
+import com.android.systemui.DemoMode;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import java.util.List;
-public interface NetworkController extends CallbackController<SignalCallback> {
+public interface NetworkController extends CallbackController<SignalCallback>, DemoMode {
boolean hasMobileDataFeature();
void addCallback(SignalCallback cb);
void removeCallback(SignalCallback cb);
void setWifiEnabled(boolean enabled);
- void onUserSwitched(int newUserId);
AccessPointController getAccessPointController();
DataUsageController getMobileDataController();
DataSaverController getDataSaverController();
@@ -40,6 +43,9 @@
void addEmergencyListener(EmergencyListener listener);
void removeEmergencyListener(EmergencyListener listener);
+ void setUserSetupComplete(boolean userSetup);
+ boolean hasEmergencyCryptKeeperText();
+ boolean isRadioOn();
public interface SignalCallback {
default void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
@@ -64,15 +70,29 @@
public static class IconState {
public final boolean visible;
+
public final int icon;
+
+ /**
+ * Optional iconOverlay resource id.
+ *
+ * <p>Set to -1 if not present.
+ */
+ public final int iconOverlay;
+
public final String contentDescription;
- public IconState(boolean visible, int icon, String contentDescription) {
+ public IconState(boolean visible, int icon, int iconOverlay, String contentDescription) {
this.visible = visible;
this.icon = icon;
+ this.iconOverlay = iconOverlay;
this.contentDescription = contentDescription;
}
+ public IconState(boolean visible, int icon, String contentDescription) {
+ this(visible, icon, -1 /* iconOverlay */, contentDescription);
+ }
+
public IconState(boolean visible, int icon, int contentDescription,
Context context) {
this(visible, icon, context.getString(contentDescription));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index a7fab41..5e13f59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -20,9 +20,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -42,8 +44,12 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.ConfigurationChangedReceiver;
import com.android.systemui.DemoMode;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -60,7 +66,8 @@
/** Platform implementation of the network controller. **/
public class NetworkControllerImpl extends BroadcastReceiver
- implements NetworkController, DemoMode, DataUsageController.NetworkNameProvider {
+ implements NetworkController, DemoMode, DataUsageController.NetworkNameProvider,
+ ConfigurationChangedReceiver, Dumpable {
// debug
static final String TAG = "NetworkController";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -80,7 +87,9 @@
private final boolean mHasMobileDataFeature;
private final SubscriptionDefaults mSubDefaults;
private final DataSaverController mDataSaverController;
+ private final CurrentUserTracker mUserTracker;
private Config mConfig;
+ private final NetworkScoreManager mNetworkScoreManager;
// Subcontrollers.
@VisibleForTesting
@@ -135,26 +144,36 @@
/**
* Construct this controller object and register for updates.
*/
- public NetworkControllerImpl(Context context, Looper bgLooper) {
+ public NetworkControllerImpl(Context context, Looper bgLooper,
+ DeviceProvisionedController deviceProvisionedController) {
this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
+ context.getSystemService(NetworkScoreManager.class),
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
(WifiManager) context.getSystemService(Context.WIFI_SERVICE),
- SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
+ SubscriptionManager.from(context),
+ Config.readConfig(context),
+ bgLooper,
new CallbackHandler(),
new AccessPointControllerImpl(context, bgLooper),
new DataUsageController(context),
- new SubscriptionDefaults());
+ new SubscriptionDefaults(),
+ deviceProvisionedController);
mReceiverHandler.post(mRegisterListeners);
}
@VisibleForTesting
NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
- TelephonyManager telephonyManager, WifiManager wifiManager,
- SubscriptionManager subManager, Config config, Looper bgLooper,
+ NetworkScoreManager networkScoreManager,
+ TelephonyManager telephonyManager,
+ WifiManager wifiManager,
+ SubscriptionManager subManager,
+ Config config,
+ Looper bgLooper,
CallbackHandler callbackHandler,
AccessPointControllerImpl accessPointController,
DataUsageController dataUsageController,
- SubscriptionDefaults defaultsHandler) {
+ SubscriptionDefaults defaultsHandler,
+ DeviceProvisionedController deviceProvisionedController) {
mContext = context;
mConfig = config;
mReceiverHandler = new Handler(bgLooper);
@@ -172,6 +191,7 @@
// wifi
mWifiManager = wifiManager;
+ mNetworkScoreManager = networkScoreManager;
mLocale = mContext.getResources().getConfiguration().locale;
mAccessPoints = accessPointController;
@@ -185,12 +205,26 @@
}
});
mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
- mCallbackHandler, this);
+ mCallbackHandler, this, mNetworkScoreManager);
mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
+ mUserTracker = new CurrentUserTracker(mContext) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ NetworkControllerImpl.this.onUserSwitched(newUserId);
+ }
+ };
+ mUserTracker.startTracking();
+ deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ setUserSetupComplete(deviceProvisionedController.isUserSetup(
+ deviceProvisionedController.getCurrentUser()));
+ }
+ });
}
public DataSaverController getDataSaverController() {
@@ -358,8 +392,7 @@
}.execute();
}
- @Override
- public void onUserSwitched(int newUserId) {
+ private void onUserSwitched(int newUserId) {
mCurrentUserId = newUserId;
mAccessPoints.onUserSwitched(newUserId);
updateConnectivity();
@@ -413,7 +446,7 @@
}
}
- public void onConfigurationChanged() {
+ public void onConfigurationChanged(Configuration newConfig) {
mConfig = Config.readConfig(mContext);
mReceiverHandler.post(new Runnable() {
@Override
@@ -776,6 +809,8 @@
MobileSignalController controller = mMobileSignalControllers
.values().toArray(new MobileSignalController[0])[slot];
controller.getState().dataSim = datatype != null;
+ controller.getState().isDefault = datatype != null;
+ controller.getState().dataConnected = datatype != null;
if (datatype != null) {
controller.getState().iconGroup =
datatype.equals("1x") ? TelephonyIcons.ONE_X :
@@ -788,6 +823,7 @@
datatype.equals("lte") ? TelephonyIcons.LTE :
datatype.equals("lte+") ? TelephonyIcons.LTE_PLUS :
datatype.equals("roam") ? TelephonyIcons.ROAMING :
+ datatype.equals("dis") ? TelephonyIcons.DATA_DISABLED :
TelephonyIcons.UNKNOWN;
}
int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH;
@@ -813,9 +849,11 @@
private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
null, 0, 0, "");
- mMobileSignalControllers.put(id, new MobileSignalController(mContext,
+ MobileSignalController controller = new MobileSignalController(mContext,
mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info,
- mSubDefaults, mReceiverHandler.getLooper()));
+ mSubDefaults, mReceiverHandler.getLooper());
+ mMobileSignalControllers.put(id, controller);
+ controller.getState().userSetup = true;
return info;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
index e5b0c03..366a752 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
@@ -16,9 +16,10 @@
import android.app.AlarmManager;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
-public interface NextAlarmController extends CallbackController<NextAlarmChangeCallback> {
+public interface NextAlarmController extends CallbackController<NextAlarmChangeCallback>, Dumpable {
public interface NextAlarmChangeCallback {
void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 3142228..3f8e41a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -15,9 +15,11 @@
*/
package com.android.systemui.statusbar.policy;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
-public interface SecurityController extends CallbackController<SecurityControllerCallback> {
+public interface SecurityController extends CallbackController<SecurityControllerCallback>,
+ Dumpable {
/** Whether the device has device owner, even if not on this user. */
boolean isDeviceManaged();
boolean hasProfileOwner();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index df959bd..19ced23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -39,12 +39,13 @@
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.systemui.R;
+import com.android.systemui.settings.CurrentUserTracker;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-public class SecurityControllerImpl implements SecurityController {
+public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
private static final String TAG = "SecurityController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -73,6 +74,7 @@
private int mVpnUserId;
public SecurityControllerImpl(Context context) {
+ super(context);
mContext = context;
mDevicePolicyManager = (DevicePolicyManager)
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
@@ -87,6 +89,7 @@
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
onUserSwitched(ActivityManager.getCurrentUser());
+ startTracking();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index b59cf68..ed8c7ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -224,7 +224,7 @@
static final int ICON_CARRIER_NETWORK_CHANGE =
R.drawable.stat_sys_signal_carrier_network_change_animation;
- static final int ICON_DATA_DISABLED = R.drawable.ic_qs_data_disabled;
+ static final int ICON_DATA_DISABLED = R.drawable.stat_sys_data_disabled;
static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte;
static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 4785ba9..f71c5d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -52,13 +52,14 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.util.UserIcons;
import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.GuestResumeSessionReceiver;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUISecondaryUserService;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.tiles.UserDetailView;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import java.io.FileDescriptor;
@@ -645,11 +646,6 @@
}
@VisibleForTesting
- public KeyguardMonitor getKeyguardMonitor() {
- return mKeyguardMonitor;
- }
-
- @VisibleForTesting
public ArrayList<UserRecord> getUsers() {
return mUsers;
}
@@ -657,17 +653,19 @@
public static abstract class BaseUserAdapter extends BaseAdapter {
final UserSwitcherController mController;
+ private final KeyguardMonitor mKeyguardMonitor;
protected BaseUserAdapter(UserSwitcherController controller) {
mController = controller;
+ mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
controller.addAdapter(new WeakReference<>(this));
}
@Override
public int getCount() {
- boolean secureKeyguardShowing = mController.getKeyguardMonitor().isShowing()
- && mController.getKeyguardMonitor().isSecure()
- && !mController.getKeyguardMonitor().canSkipBouncer();
+ boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
+ && mKeyguardMonitor.isSecure()
+ && !mKeyguardMonitor.canSkipBouncer();
if (!secureKeyguardShowing) {
return mController.getUsers().size();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index b1bc2f0..42c20ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -15,18 +15,27 @@
*/
package com.android.systemui.statusbar.policy;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.net.NetworkCapabilities;
+import android.net.NetworkKey;
+import android.net.NetworkScoreManager;
+import android.net.ScoredNetwork;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.WifiNetworkScoreCache.CacheListener;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.AsyncChannel;
+import com.android.settingslib.Utils;
import com.android.settingslib.wifi.WifiStatusTracker;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -34,17 +43,24 @@
import com.android.systemui.R;
import java.util.Objects;
+import java.util.List;
public class WifiSignalController extends
SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
+
private final WifiManager mWifiManager;
private final AsyncChannel mWifiChannel;
private final boolean mHasMobileData;
+ private final NetworkScoreManager mNetworkScoreManager;
+ private final WifiNetworkScoreCache mScoreCache;
private final WifiStatusTracker mWifiTracker;
+ private boolean mScoringEnabled = false;
+
public WifiSignalController(Context context, boolean hasMobileData,
- CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
+ CallbackHandler callbackHandler, NetworkControllerImpl networkController,
+ NetworkScoreManager networkScoreManager) {
super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
callbackHandler, networkController);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
@@ -68,6 +84,44 @@
WifiIcons.QS_WIFI_NO_NETWORK,
AccessibilityContentDescriptions.WIFI_NO_CONNECTION
);
+
+ mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(handler) {
+ @Override
+ public void networkCacheUpdated(List<ScoredNetwork> networks) {
+ mCurrentState.badgeEnum = getWifiBadgeEnum();
+ notifyListenersIfNecessary();
+ }
+ });
+
+ // Setup scoring
+ mNetworkScoreManager = networkScoreManager;
+ ContentObserver observer = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mScoringEnabled =
+ Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1;
+ if (!mScoringEnabled) {
+ mScoreCache.clearScores();
+ }
+ }
+ };
+ ContentResolver cr = mContext.getContentResolver();
+ cr.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED),
+ false /* notifyForDescendants */,
+ observer);
+ observer.onChange(false /* selfChange */);
+ registerScoreCache();
+ }
+
+ private void registerScoreCache() {
+ Log.d(mTag, "Registered score cache");
+ mNetworkScoreManager.registerNetworkScoreCache(
+ NetworkKey.TYPE_WIFI,
+ mScoreCache,
+ NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
}
@Override
@@ -88,27 +142,74 @@
("," + mContext.getString(R.string.accessibility_quick_settings_no_internet));
}
- IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
- IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
- contentDescription);
+ IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(),
+ Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription);
+ IconState qsIcon = new IconState(
+ mCurrentState.connected, getQsCurrentIconId(),
+ Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription);
callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
wifiDesc);
}
+ @Override
+ public int getCurrentIconId() {
+ if (mCurrentState.badgeEnum != ScoredNetwork.BADGING_NONE) {
+ return Utils.WIFI_PIE_FOR_BADGING[mCurrentState.level];
+ }
+ return super.getCurrentIconId();
+ }
+
/**
* Extract wifi state directly from broadcasts about changes in wifi state.
*/
public void handleBroadcast(Intent intent) {
+ // Update the WifiStatusTracker with the new information and update the score cache.
+ NetworkKey previousNetworkKey = mWifiTracker.networkKey;
mWifiTracker.handleBroadcast(intent);
+ updateScoreCacheIfNecessary(previousNetworkKey);
+
mCurrentState.enabled = mWifiTracker.enabled;
mCurrentState.connected = mWifiTracker.connected;
mCurrentState.ssid = mWifiTracker.ssid;
mCurrentState.rssi = mWifiTracker.rssi;
mCurrentState.level = mWifiTracker.level;
+ mCurrentState.badgeEnum = getWifiBadgeEnum();
notifyListenersIfNecessary();
}
+ /**
+ * Clears old scores out of the cache and requests new scores if the network key has changed.
+ *
+ * <p>New scores are requested asynchronously.
+ */
+ private void updateScoreCacheIfNecessary(NetworkKey previousNetworkKey) {
+ if (mWifiTracker.networkKey == null) {
+ return;
+ }
+ if ((previousNetworkKey == null) || !mWifiTracker.networkKey.equals(previousNetworkKey)) {
+ mScoreCache.clearScores();
+ mNetworkScoreManager.requestScores(new NetworkKey[]{mWifiTracker.networkKey});
+ }
+ }
+
+ /**
+ * Returns the wifi badge enum for the current {@link #mWifiTracker} state.
+ *
+ * <p>{@link #updateScoreCacheIfNecessary} should be called prior to this method.
+ */
+ private int getWifiBadgeEnum() {
+ if (!mScoringEnabled || mWifiTracker.networkKey == null) {
+ return ScoredNetwork.BADGING_NONE;
+ }
+ ScoredNetwork score = mScoreCache.getScoredNetwork(mWifiTracker.networkKey);
+
+ if (score != null) {
+ return score.calculateBadge(mWifiTracker.rssi);
+ }
+ return ScoredNetwork.BADGING_NONE;
+ }
+
@VisibleForTesting
void setActivity(int wifiActivity) {
mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
@@ -149,12 +250,14 @@
static class WifiState extends SignalController.State {
String ssid;
+ int badgeEnum;
@Override
public void copyFrom(State s) {
super.copyFrom(s);
WifiState state = (WifiState) s;
ssid = state.ssid;
+ badgeEnum = state.badgeEnum;
}
@Override
@@ -166,7 +269,8 @@
@Override
public boolean equals(Object o) {
return super.equals(o)
- && Objects.equals(((WifiState) o).ssid, ssid);
+ && Objects.equals(((WifiState) o).ssid, ssid)
+ && (((WifiState) o).badgeEnum == badgeEnum);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index bcdb62d..f195f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -30,7 +30,6 @@
ZenRule getManualRule();
ZenModeConfig getConfig();
long getNextAlarm();
- void setUserId(int userId);
boolean isZenAvailable();
ComponentName getEffectsSuppressor();
boolean isCountdownConditionSupported();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 96efea1..e80d3b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -40,13 +40,14 @@
import android.util.Slog;
import com.android.systemui.qs.GlobalSetting;
+import com.android.systemui.settings.CurrentUserTracker;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Objects;
/** Platform implementation of the zen mode controller. **/
-public class ZenModeControllerImpl implements ZenModeController {
+public class ZenModeControllerImpl extends CurrentUserTracker implements ZenModeController {
private static final String TAG = "ZenModeController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -66,6 +67,7 @@
private ZenModeConfig mConfig;
public ZenModeControllerImpl(Context context, Handler handler) {
+ super(context);
mContext = context;
mModeSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE) {
@Override
@@ -87,6 +89,7 @@
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
mUserManager = context.getSystemService(UserManager.class);
+ startTracking();
}
@Override
@@ -137,7 +140,7 @@
}
@Override
- public void setUserId(int userId) {
+ public void onUserSwitched(int userId) {
mUserId = userId;
if (mRegistered) {
mContext.unregisterReceiver(mReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index f6b8891..266f053 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -23,6 +23,7 @@
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.Settings;
import android.support.v14.preference.PreferenceFragment;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.PreferenceCategory;
@@ -30,9 +31,9 @@
import android.support.v7.preference.PreferenceViewHolder;
import android.view.View;
+import com.android.systemui.R;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.PluginPrefs;
-import com.android.systemui.R;
import java.util.List;
import java.util.Set;
@@ -147,6 +148,12 @@
result.activityInfo.name)));
}
});
+ holder.itemView.setOnLongClickListener(v -> {
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.setData(Uri.fromParts("package", mComponent.getPackageName(), null));
+ getContext().startActivity(intent);
+ return true;
+ });
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
index 1f0ee57..36c673c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
@@ -25,7 +25,6 @@
import java.io.PrintWriter;
public interface VolumeComponent extends DemoMode {
- ZenModeController getZenController();
void dismissNow();
void onConfigurationChanged(Configuration newConfig);
void dump(FileDescriptor fd, PrintWriter pw, String[] args);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index d23ebc1..d057d863 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -127,6 +127,7 @@
private boolean mShowing;
private boolean mExpanded;
+ private boolean mShowA11yStream;
private int mActiveStream;
private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
@@ -244,7 +245,6 @@
if (!AudioSystem.isSingleVolume(mContext)) {
addRow(AudioManager.STREAM_RING,
R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
-
addRow(AudioManager.STREAM_ALARM,
R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
addRow(AudioManager.STREAM_VOICE_CALL,
@@ -253,6 +253,8 @@
R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false);
addRow(AudioManager.STREAM_SYSTEM,
R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false);
+ addRow(AudioManager.STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility,
+ R.drawable.ic_volume_accessibility, true);
}
} else {
addExistingRows();
@@ -307,10 +309,24 @@
}
private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {
+ addRow(stream, iconRes, iconMuteRes, important, false);
+ }
+
+ private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
+ boolean dynamic) {
VolumeRow row = new VolumeRow();
initRow(row, stream, iconRes, iconMuteRes, important);
- mDialogRowsView.addView(row.view);
- mRows.add(row);
+ int rowSize;
+ int viewSize;
+ if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1
+ && (viewSize = mDialogRowsView.getChildCount()) > 1) {
+ // A11y Stream should be the last in the list
+ mDialogRowsView.addView(row.view, viewSize - 2);
+ mRows.add(rowSize - 2, row);
+ } else {
+ mDialogRowsView.addView(row.view);
+ mRows.add(row);
+ }
}
private void addExistingRows() {
@@ -592,6 +608,9 @@
}
private boolean shouldBeVisibleH(VolumeRow row, boolean isActive) {
+ if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
+ return mShowA11yStream;
+ }
return mExpanded && row.view.getVisibility() == View.VISIBLE
|| (mExpanded && (row.important || isActive))
|| !mExpanded && isActive;
@@ -644,7 +663,8 @@
if (!ss.dynamic) continue;
mDynamic.put(stream, true);
if (findRow(stream) == null) {
- addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true);
+ addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,
+ true);
}
}
@@ -1009,6 +1029,14 @@
public void onShowSafetyWarning(int flags) {
showSafetyWarningH(flags);
}
+
+ @Override
+ public void onAccessibilityModeChanged(Boolean showA11yStream) {
+ boolean show = showA11yStream == null ? false : showA11yStream;
+ mShowA11yStream = show;
+ updateRowsH(getActiveRow());
+
+ }
};
private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index f195a0b..137a12f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -16,20 +16,17 @@
package com.android.systemui.volume;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.VolumePolicy;
import android.os.Bundle;
import android.os.Handler;
-import android.provider.Settings;
-import android.util.Log;
import android.view.WindowManager;
-import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
+import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -37,11 +34,9 @@
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.volume.car.CarVolumeDialogController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.reflect.Constructor;
/**
* Implementation of VolumeComponent backed by the new volume dialog.
@@ -69,15 +64,14 @@
400 // vibrateToSilentDebounce
);
- public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler,
- ZenModeController zen) {
+ public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler) {
mSysui = sysui;
mContext = context;
mController = SystemUIFactory.getInstance().createVolumeDialogController(context, null);
mController.setUserActivityListener(this);
- mZenModeController = zen;
+ mZenModeController = Dependency.get(ZenModeController.class);
mDialog = new VolumeDialog(context, WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY,
- mController, zen, mVolumeDialogCallback);
+ mController, mZenModeController, mVolumeDialogCallback);
applyConfiguration();
TunerService.get(mContext).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
VOLUME_SILENT_DO_NOT_DISTURB);
@@ -134,11 +128,6 @@
}
@Override
- public ZenModeController getZenController() {
- return mZenModeController;
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfig) {
// noop
}
@@ -166,7 +155,7 @@
}
private void startSettings(Intent intent) {
- mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(intent,
+ Dependency.get(ActivityStarter.class).startActivity(intent,
true /* onlyProvisioned */, true /* dismissShade */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 0e5ff43..276b7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -75,13 +75,13 @@
STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco);
STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf);
STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music);
+ STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility);
STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification);
STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring);
STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system);
STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced);
STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts);
STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call);
- STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility);
}
private final HandlerThread mWorkerThread;
@@ -98,6 +98,7 @@
private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
private final Vibrator mVibrator;
private final boolean mHasVibrator;
+ private boolean mShowA11yStream;
private boolean mDestroyed;
private VolumePolicy mVolumePolicy;
@@ -204,6 +205,7 @@
pw.print(" mHasVibrator: "); pw.println(mHasVibrator);
pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams
.values());
+ pw.print(" mShowA11yStream: "); pw.println(mShowA11yStream);
pw.println();
mMediaSessions.dump(pw);
}
@@ -301,6 +303,10 @@
mCallbacks.onShowSafetyWarning(flags);
}
+ private void onAccessibilityModeChanged(Boolean showA11yStream) {
+ mCallbacks.onAccessibilityModeChanged(showA11yStream);
+ }
+
private boolean checkRoutedToBluetoothW(int stream) {
boolean changed = false;
if (stream == AudioManager.STREAM_MUSIC) {
@@ -570,13 +576,16 @@
switch (mode) {
case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME:
// "legacy" mode
+ mShowA11yStream = false;
break;
case VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME:
+ mShowA11yStream = true;
break;
default:
Log.e(TAG, "Invalid accessibility mode " + mode);
break;
}
+ mWorker.obtainMessage(W.ACCESSIBILITY_MODE_CHANGED, mShowA11yStream).sendToTarget();
}
}
@@ -595,6 +604,7 @@
private static final int NOTIFY_VISIBLE = 12;
private static final int USER_ACTIVITY = 13;
private static final int SHOW_SAFETY_WARNING = 14;
+ private static final int ACCESSIBILITY_MODE_CHANGED = 15;
W(Looper looper) {
super(looper);
@@ -617,6 +627,7 @@
case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;
case USER_ACTIVITY: onUserActivityW(); break;
case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
+ case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
}
}
}
@@ -743,6 +754,19 @@
});
}
}
+
+ @Override
+ public void onAccessibilityModeChanged(Boolean showA11yStream) {
+ boolean show = showA11yStream == null ? false : showA11yStream;
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onAccessibilityModeChanged(show);
+ }
+ });
+ }
+ }
}
@@ -1004,6 +1028,7 @@
.append('[').append(ss.levelMin).append("..").append(ss.levelMax)
.append(']');
if (ss.muted) sb.append(" [MUTED]");
+ if (ss.dynamic) sb.append(" [DYNAMIC]");
}
sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal);
sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal);
@@ -1037,6 +1062,7 @@
void onShowSilentHint();
void onScreenOff();
void onShowSafetyWarning(int flags);
+ void onAccessibilityModeChanged(Boolean showA11yStream);
}
public interface UserActivityListener {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 73d9ea7..02969e4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -42,8 +42,7 @@
public void start() {
mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
if (!mEnabled) return;
- final ZenModeController zenController = new ZenModeControllerImpl(mContext, mHandler);
- mVolumeComponent = new VolumeDialogComponent(this, mContext, null, zenController);
+ mVolumeComponent = new VolumeDialogComponent(this, mContext, null);
putComponent(VolumeComponent.class, getVolumeComponent());
setDefaultVolumeController();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
new file mode 100644
index 0000000..973f1f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.Looper;
+
+import com.android.systemui.statusbar.policy.FlashlightController;
+
+import org.junit.Test;
+
+public class DependencyTest extends SysuiTestCase {
+
+ @Test
+ public void testClassDependency() {
+ FlashlightController f = mock(FlashlightController.class);
+ injectTestDependency(FlashlightController.class, f);
+ assertEquals(f, Dependency.get(FlashlightController.class));
+ }
+
+ @Test
+ public void testStringDependency() {
+ Looper l = Looper.getMainLooper();
+ injectTestDependency(Dependency.BG_LOOPER, l);
+ assertEquals(l, Dependency.get(Dependency.BG_LOOPER));
+ }
+
+ @Test
+ public void testDump() {
+ Dumpable d = mock(Dumpable.class);
+ injectTestDependency("test", d);
+ Dependency.get("test");
+ mDependency.dump(null, null, null);
+ verify(d).dump(eq(null), eq(null), eq(null));
+ }
+
+ @Test
+ public void testConfigurationChanged() {
+ ConfigurationChangedReceiver d = mock(ConfigurationChangedReceiver.class);
+ injectTestDependency("test", d);
+ Dependency.get("test");
+ mDependency.onConfigurationChanged(null);
+ verify(d).onConfigurationChanged(eq(null));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index d1d7520..5fe5174 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -15,11 +15,14 @@
*/
package com.android.systemui;
+import static org.mockito.Mockito.mock;
+
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
+import android.util.ArrayMap;
import com.android.systemui.utils.TestableContext;
import com.android.systemui.utils.leaks.Tracker;
@@ -34,11 +37,15 @@
private Handler mHandler;
protected TestableContext mContext;
+ protected TestDependency mDependency;
@Before
public void SysuiSetup() throws Exception {
System.setProperty("dexmaker.share_classloader", "true");
mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this);
+ mDependency = new TestDependency();
+ mDependency.mContext = mContext;
+ mDependency.start();
}
@After
@@ -78,11 +85,37 @@
return null;
}
+ public void injectMockDependency(Class<?> cls) {
+ mDependency.injectTestDependency(cls.getName(), mock(cls));
+ }
+
+ public void injectTestDependency(Class<?> cls, Object obj) {
+ mDependency.injectTestDependency(cls.getName(), obj);
+ }
+
+ public void injectTestDependency(String key, Object obj) {
+ mDependency.injectTestDependency(key, obj);
+ }
+
public static final class EmptyRunnable implements Runnable {
public void run() {
}
}
+ public static class TestDependency extends Dependency {
+ private final ArrayMap<String, Object> mObjs = new ArrayMap<>();
+
+ private void injectTestDependency(String key, Object obj) {
+ mObjs.put(key, obj);
+ }
+
+ @Override
+ protected <T> T createDependency(String cls) {
+ if (mObjs.containsKey(cls)) return (T) mObjs.get(cls);
+ return super.createDependency(cls);
+ }
+ }
+
public static final class Idler implements MessageQueue.IdleHandler {
private final Runnable mCallback;
private boolean mIdle;
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 d529ee1..3715df2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
@@ -113,8 +113,7 @@
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
waitForIdleSync(mPluginInstanceManager.mMainHandler);
- verify(mMockListener, Mockito.never()).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
}
@Test
@@ -124,7 +123,7 @@
// Verify startup lifecycle
verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
ArgumentCaptor.forClass(Context.class).capture());
- verify(mMockListener).onPluginConnected(ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener).onPluginConnected(any(), any());
}
@Test
@@ -154,8 +153,7 @@
waitForIdleSync(mPluginInstanceManager.mMainHandler);
// Plugin shouldn't be connected because it is the wrong version.
- verify(mMockListener, Mockito.never()).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
verify(nm).notifyAsUser(eq(TestPlugin.class.getName()), eq(SystemMessage.NOTE_PLUGIN),
any(), eq(UserHandle.ALL));
}
@@ -176,8 +174,7 @@
verify(sMockPlugin, Mockito.times(2)).onCreate(
ArgumentCaptor.forClass(Context.class).capture(),
ArgumentCaptor.forClass(Context.class).capture());
- verify(mMockListener, Mockito.times(2)).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.times(2)).onPluginConnected(any(), any());
}
@Test
@@ -193,8 +190,7 @@
waitForIdleSync(mPluginInstanceManager.mMainHandler);;
// Non-debuggable build should receive no plugins.
- verify(mMockListener, Mockito.never()).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
index 8acd6ba..2f6487b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
@@ -29,6 +29,7 @@
import android.view.ViewGroup;
import android.widget.TextView;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -56,6 +57,8 @@
@Before
public void setUp() {
+ injectTestDependency(SecurityController.class, mSecurityController);
+ injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
new LayoutInflaterBuilder(mContext)
.replace("ImageView", TestableImageView.class)
@@ -67,7 +70,7 @@
mFooterText = (TextView) mRootView.findViewById(R.id.footer_text);
mFooterIcon = (TestableImageView) mRootView.findViewById(R.id.footer_icon);
mFooterIcon2 = (TestableImageView) mRootView.findViewById(R.id.footer_icon2);
- mFooter.setHostEnvironment(null, mSecurityController, Looper.getMainLooper());
+ mFooter.setHostEnvironment(null);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index c0d5bbd..e3ee851 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -18,11 +18,12 @@
import static org.mockito.Mockito.when;
import android.os.Handler;
+import android.os.Looper;
import android.support.test.runner.AndroidJUnit4;
+import com.android.systemui.Dependency;
import com.android.systemui.FragmentTestCase;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.QuickStatusBarHeader;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -42,6 +43,7 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,33 +56,24 @@
super(QSFragment.class);
}
+ @Before
+ public void addLeakCheckDependencies() {
+ injectMockDependency(UserSwitcherController.class);
+ injectLeakCheckedDependencies(BluetoothController.class, LocationController.class,
+ RotationLockController.class, NetworkController.class, ZenModeController.class,
+ HotspotController.class, CastController.class, FlashlightController.class,
+ UserInfoController.class, KeyguardMonitor.class, SecurityController.class,
+ BatteryController.class, NextAlarmController.class);
+ }
+
@Test
public void testListening() {
QSFragment qs = (QSFragment) mFragment;
postAndWait(() -> mFragments.dispatchResume());
- UserSwitcherController userSwitcher = mock(UserSwitcherController.class);
- KeyguardMonitor keyguardMonitor = getLeakChecker(KeyguardMonitor.class);
- when(userSwitcher.getKeyguardMonitor()).thenReturn(keyguardMonitor);
- when(userSwitcher.getUsers()).thenReturn(new ArrayList<>());
- QSTileHost host = new QSTileHost(mContext,
- null,
- getLeakChecker(BluetoothController.class),
- getLeakChecker(LocationController.class),
- getLeakChecker(RotationLockController.class),
- getLeakChecker(NetworkController.class),
- getLeakChecker(ZenModeController.class),
- getLeakChecker(HotspotController.class),
- getLeakChecker(CastController.class),
- getLeakChecker(FlashlightController.class),
- userSwitcher,
- getLeakChecker(UserInfoController.class),
- keyguardMonitor,
- getLeakChecker(SecurityController.class),
- getLeakChecker(BatteryController.class),
- mock(StatusBarIconController.class),
- getLeakChecker(NextAlarmController.class));
+ QSTileHost host = new QSTileHost(mContext, null,
+ mock(StatusBarIconController.class));
qs.setHost(host);
- Handler h = new Handler(host.getLooper());
+ Handler h = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
qs.setListening(true);
waitForIdleSync(h);
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 3ee1372..4146cb81 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
@@ -47,13 +47,7 @@
@Before
public void setUp() throws Exception {
mManagers = new ArrayList<>();
- final NetworkController networkController = Mockito.mock(NetworkController.class);
- Mockito.when(networkController.getDataSaverController()).thenReturn(
- Mockito.mock(DataSaverController.class));
- QSTileHost host = new QSTileHost(mContext, null, null, null, null,
- networkController, null,
- Mockito.mock(HotspotController.class), null,
- null, null, null, null, null, null, null, null);
+ QSTileHost host = new QSTileHost(mContext, null, null);
mTileService = new TestTileServices(host, Looper.getMainLooper());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
index c65f7150..cac0806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
@@ -91,7 +91,6 @@
// mMockStatusBarNotification with a test channel.
mNotificationChannel = new NotificationChannel(
TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
- when(mMockStatusBarNotification.getNotificationChannel()).thenReturn(mNotificationChannel);
when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
}
@@ -100,7 +99,7 @@
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
}
@@ -109,7 +108,7 @@
@UiThreadTest
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -119,8 +118,8 @@
public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, (View v, int appUid) -> { latch.countDown(); },
- null, null);
+ mMockStatusBarNotification, mNotificationChannel,
+ (View v, int appUid) -> { latch.countDown(); }, null, null);
final TextView settingsButton =
(TextView) mNotificationGuts.findViewById(R.id.more_settings);
@@ -134,7 +133,7 @@
public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null,
+ mMockStatusBarNotification, mNotificationChannel, null,
(View v) -> { latch.countDown(); },
null);
@@ -148,7 +147,7 @@
@UiThreadTest
public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
assertFalse(mNotificationGuts.hasImportanceChanged());
}
@@ -157,7 +156,7 @@
public void testHasImportanceChanged_ReturnsTrueAfterButtonChecked() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
// Find the high button and check it.
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
@@ -169,7 +168,7 @@
public void testImportanceButtonCheckedBasedOnInitialImportance() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
assertTrue(highButton.isChecked());
@@ -179,7 +178,7 @@
@UiThreadTest
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -189,7 +188,7 @@
public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
@@ -201,7 +200,7 @@
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
mNotificationGuts.closeControls(-1, -1, true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -213,7 +212,7 @@
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnspecified() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
mNotificationGuts.closeControls(-1, -1, true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -225,7 +224,7 @@
public void testCloseControls_CallsUpdateNotificationChannelIfChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
@@ -240,7 +239,7 @@
public void testCloseControls_DoesNotUpdateNotificationChannelIfSaveFalse() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
@@ -254,7 +253,7 @@
public void testEnabledSwitchOnByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
assertTrue(enabledSwitch.isChecked());
@@ -265,7 +264,7 @@
public void testEnabledSwitchVisibleByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
@@ -276,7 +275,8 @@
public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+ mMockStatusBarNotification, mNotificationChannel, null, null,
+ Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
@@ -287,7 +287,8 @@
public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+ mMockStatusBarNotification, mNotificationChannel, null, null,
+ Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
@@ -301,7 +302,7 @@
public void testEnabledSwitchOverridesOtherButtons() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index e140e98..9fcb5f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -47,10 +47,6 @@
mContext.addMockSystemService(Context.WINDOW_SERVICE, mock(WindowManager.class));
NavigationBarFragment navigationBarFragment = (NavigationBarFragment) mFragment;
- AssistManager assistManager = new AssistManager(mContext.getComponent(PhoneStatusBar.class),
- mContext);
- navigationBarFragment.setAssistManager(assistManager);
-
postAndWait(() -> mFragments.dispatchResume());
navigationBarFragment.onHomeLongClick(navigationBarFragment.getView());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTest.java
new file mode 100644
index 0000000..d82566f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.keyguard.KeyguardHostView.OnDismissAction;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.BaseStatusBar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PhoneStatusBarTest extends SysuiTestCase {
+
+ StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ PhoneStatusBar mPhoneStatusBar;
+
+ @Before
+ public void setup() {
+ mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager.class);
+ mPhoneStatusBar = new TestablePhoneStatusBar(mStatusBarKeyguardViewManager);
+
+ doAnswer(invocation -> {
+ OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
+ onDismissAction.onDismiss();
+ return null;
+ }).when(mStatusBarKeyguardViewManager).dismissWithAction(any(), any(), anyBoolean());
+
+ doAnswer(invocation -> {
+ Runnable runnable = (Runnable) invocation.getArguments()[0];
+ runnable.run();
+ return null;
+ }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
+ }
+
+ @Test
+ public void executeRunnableDismissingKeyguard_nullRunnable_showingAndOccluded() {
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
+
+ mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+ }
+
+ @Test
+ public void executeRunnableDismissingKeyguard_nullRunnable_showing() {
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+
+ mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+ }
+
+ @Test
+ public void executeRunnableDismissingKeyguard_nullRunnable_notShowing() {
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+
+ mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+ }
+
+ static class TestablePhoneStatusBar extends PhoneStatusBar {
+ public TestablePhoneStatusBar(StatusBarKeyguardViewManager man) {
+ mStatusBarKeyguardViewManager = man;
+ }
+
+ @Override
+ protected BaseStatusBar.H createHandler() {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index 7b56ea3..c969cc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -18,7 +18,6 @@
import android.os.HandlerThread;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.SubscriptionInfo;
-import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 6fe7768..6aa021e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.os.Looper;
import android.telephony.PhoneStateListener;
@@ -79,6 +80,7 @@
protected Config mConfig;
protected CallbackHandler mCallbackHandler;
protected SubscriptionDefaults mMockSubDefaults;
+ protected NetworkScoreManager mMockNetworkScoreManager;
protected int mSubId;
@@ -105,6 +107,8 @@
mMockCm = mock(ConnectivityManager.class);
mMockSubDefaults = mock(SubscriptionDefaults.class);
mNetCapabilities = new NetworkCapabilities();
+ mMockNetworkScoreManager = mock(NetworkScoreManager.class);
+
when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
when(mMockCm.getDefaultNetworkCapabilitiesForUser(0)).thenReturn(
new NetworkCapabilities[] { mNetCapabilities });
@@ -115,10 +119,11 @@
mConfig = new Config();
mConfig.hspaDataDistinguishable = true;
mCallbackHandler = mock(CallbackHandler.class);
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
+ mMockTm, mMockWm, mMockSm,
mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
- mMockSubDefaults);
+ mMockSubDefaults, mock(DeviceProvisionedController.class));
setupNetworkController();
// Trigger blank callbacks to always get the current state (some tests don't trigger
@@ -157,10 +162,11 @@
protected NetworkControllerImpl setUpNoMobileData() {
when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
NetworkControllerImpl networkControllerNoMobile
- = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
- mConfig, mContext.getMainLooper(), mCallbackHandler,
+ = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, mMockTm,
+ mMockWm, mMockSm, mConfig, mContext.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class),
- mock(DataUsageController.class), mMockSubDefaults);
+ mock(DataUsageController.class), mMockSubDefaults,
+ mock(DeviceProvisionedController.class));
setupNetworkController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 4f961ab..1ec0418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,5 +1,7 @@
package com.android.systemui.statusbar.policy;
+import static org.mockito.Mockito.mock;
+
import android.net.NetworkCapabilities;
import android.os.Looper;
import android.support.test.runner.AndroidJUnit4;
@@ -98,10 +100,12 @@
public void test4gDataIcon() {
// Switch to showing 4g icon and re-initialize the NetworkController.
mConfig.show4gForLte = true;
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
+ mMockTm, mMockWm, mMockSm,
mConfig, Looper.getMainLooper(), mCallbackHandler,
- Mockito.mock(AccessPointControllerImpl.class),
- Mockito.mock(DataUsageController.class), mMockSubDefaults);
+ mock(AccessPointControllerImpl.class),
+ mock(DataUsageController.class), mMockSubDefaults,
+ mock(DeviceProvisionedController.class));
setupNetworkController();
setupDefaultSignal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 4560aa7..00e5926 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -53,10 +53,11 @@
// Turn off mobile network support.
Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
+ mMockTm, mMockWm, mMockSm,
mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
- mMockSubDefaults);
+ mMockSubDefaults, mock(DeviceProvisionedController.class));
setupNetworkController();
verifyLastMobileDataIndicators(false, 0, 0);
@@ -107,10 +108,11 @@
// Turn off mobile network support.
Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
+ mMockTm, mMockWm, mMockSm,
mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
- mMockSubDefaults);
+ mMockSubDefaults, mock(DeviceProvisionedController.class));
setupNetworkController();
// No Subscriptions.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index ed32f65..06a5122 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -1,24 +1,50 @@
package com.android.systemui.statusbar.policy;
import android.content.Intent;
+import android.graphics.drawable.Drawable;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import com.android.settingslib.Utils;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -27,6 +53,15 @@
private static final int MIN_RSSI = -100;
private static final int MAX_RSSI = -55;
+ private static final int LATCH_TIMEOUT = 2000;
+ private static final String TEST_SSID = "\"Test SSID\"";
+ private static final String TEST_BSSID = "00:00:00:00:00:00";
+
+ private final List<NetworkKey> mRequestedKeys = new ArrayList<>();
+ private CountDownLatch mRequestScoresLatch;
+
+ private SettingOverrider mSettingsOverrider;
+
@Test
public void testWifiIcon() {
String testSsid = "Test SSID";
@@ -47,6 +82,77 @@
}
@Test
+ public void testBadgedWifiIcon() throws Exception {
+ int testLevel = 1;
+ RssiCurve mockBadgeCurve = mock(RssiCurve.class);
+ Bundle attr = new Bundle();
+ attr.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve);
+ ScoredNetwork score =
+ new ScoredNetwork(
+ new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)),
+ null,
+ false /* meteredHint */,
+ attr);
+
+ // Enable scoring
+ mSettingsOverrider = mContext.getSettingsProvider().acquireOverridesBuilder(this)
+ .addSetting("global", Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, "1")
+ .build();
+
+ setupNetworkScoreManager();
+ mRequestScoresLatch = new CountDownLatch(1);
+ setWifiEnabled(true);
+ setWifiState(true, TEST_SSID, TEST_BSSID);
+ mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) ScoredNetwork.BADGING_SD);
+
+ ArgumentCaptor<WifiNetworkScoreCache> scoreCacheCaptor =
+ ArgumentCaptor.forClass(WifiNetworkScoreCache.class);
+ verify(mMockNetworkScoreManager).registerNetworkScoreCache(
+ anyInt(),
+ scoreCacheCaptor.capture(),
+ Matchers.anyInt());
+ scoreCacheCaptor.getValue().updateScores(Arrays.asList(score));
+
+ setWifiLevel(testLevel);
+ NetworkController.SignalCallback mockCallback =
+ mock(NetworkController.SignalCallback.class);
+ mNetworkController.addCallback(mockCallback);
+
+ ArgumentCaptor<IconState> iconState = ArgumentCaptor.forClass(IconState.class);
+ Mockito.verify(mockCallback).setWifiIndicators(
+ anyBoolean(), iconState.capture(), any(), anyBoolean(), anyBoolean(), any());
+
+ assertEquals("Badged Wifi Resource is set",
+ Utils.WIFI_PIE_FOR_BADGING[testLevel],
+ iconState.getValue().icon);
+ assertEquals("SD Badge is set",
+ Utils.getWifiBadgeResource(ScoredNetwork.BADGING_SD),
+ iconState.getValue().iconOverlay);
+
+ mSettingsOverrider.release();
+ }
+
+ private void setupNetworkScoreManager() {
+ // Capture requested keys and count down latch if present
+ doAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock input) {
+ if (mRequestScoresLatch != null) {
+ mRequestScoresLatch.countDown();
+ }
+ NetworkKey[] keys = (NetworkKey[]) input.getArguments()[0];
+ for (NetworkKey key : keys) {
+ mRequestedKeys.add(key);
+ }
+ return true;
+ }
+ }).when(mMockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any());
+ }
+
+ @Test
public void testQsWifiIcon() {
String testSsid = "Test SSID";
@@ -97,7 +203,7 @@
@Test
public void testRoamingIconDuringWifi() {
// Setup normal connection
- String testSsid = "Test SSID";
+ String testSsid = "\"Test SSID\"";
int testLevel = 2;
setWifiEnabled(true);
setWifiState(true, testSsid);
@@ -137,12 +243,19 @@
}
protected void setWifiState(boolean connected, String ssid) {
+ setWifiState(connected, ssid, null);
+ }
+
+ protected void setWifiState(boolean connected, String ssid, String bssid) {
Intent i = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
NetworkInfo networkInfo = Mockito.mock(NetworkInfo.class);
Mockito.when(networkInfo.isConnected()).thenReturn(connected);
WifiInfo wifiInfo = Mockito.mock(WifiInfo.class);
Mockito.when(wifiInfo.getSSID()).thenReturn(ssid);
+ if (bssid != null) {
+ Mockito.when(wifiInfo.getBSSID()).thenReturn(bssid);
+ }
i.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
i.putExtra(WifiManager.EXTRA_WIFI_INFO, wifiInfo);
@@ -166,8 +279,8 @@
ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
- enabledArg.capture(), any(), iconArg.capture(), anyBoolean(), anyBoolean(),
- descArg.capture());
+ enabledArg.capture(), any(), iconArg.capture(), anyBoolean(),
+ anyBoolean(), descArg.capture());
IconState iconState = iconArg.getValue();
assertEquals("WiFi enabled, in quick settings", enabled, (boolean) enabledArg.getValue());
assertEquals("WiFi connected, in quick settings", connected, iconState.visible);
@@ -179,7 +292,8 @@
ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
- anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(), any());
+ anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(),
+ any());
IconState iconState = iconArg.getValue();
assertEquals("WiFi visible, in status bar", visible, iconState.visible);
assertEquals("WiFi signal, in status bar", icon, iconState.icon);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
index 0238bf7..b118fdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
@@ -14,9 +14,13 @@
package com.android.systemui.utils.leaks;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.CallbackController;
-public class BaseLeakChecker<T> implements CallbackController<T> {
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class BaseLeakChecker<T> implements CallbackController<T>, Dumpable {
private final Tracker mTracker;
@@ -37,4 +41,9 @@
public void removeCallback(T listener) {
mTracker.getLeakInfo(listener).clearAllocations();
}
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
index fcfe9aa..5497686 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -14,11 +14,16 @@
package com.android.systemui.utils.leaks;
+import android.os.Bundle;
+
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
public class FakeNetworkController extends BaseLeakChecker<SignalCallback>
implements NetworkController {
@@ -42,6 +47,21 @@
}
@Override
+ public void setUserSetupComplete(boolean userSetup) {
+
+ }
+
+ @Override
+ public boolean hasEmergencyCryptKeeperText() {
+ return false;
+ }
+
+ @Override
+ public boolean isRadioOn() {
+ return false;
+ }
+
+ @Override
public DataSaverController getDataSaverController() {
return mDataSaverController;
}
@@ -57,11 +77,6 @@
}
@Override
- public void onUserSwitched(int newUserId) {
-
- }
-
- @Override
public AccessPointController getAccessPointController() {
return null;
}
@@ -75,4 +90,9 @@
public boolean hasVoiceCallingFeature() {
return false;
}
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
index 13ea385..7581363 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
@@ -53,11 +53,6 @@
}
@Override
- public void setUserId(int userId) {
-
- }
-
- @Override
public boolean isZenAvailable() {
return false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index 728ed60..c182493 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -118,6 +118,12 @@
mTrackers.values().forEach(Tracker::verify);
}
+ public void injectLeakCheckedDependencies(Class<?>... cls) {
+ for (Class<?> c : cls) {
+ injectTestDependency(c, getLeakChecker(c));
+ }
+ }
+
public <T extends CallbackController> T addListening(T mock, Class<T> cls, String tag) {
doAnswer(new Answer<Void>() {
@Override
diff --git a/preloaded-classes b/preloaded-classes
index 86cbb69..2fad5dd 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -823,11 +823,6 @@
android.graphics.EmbossMaskFilter
android.graphics.FontFamily
android.graphics.FontListParser
-android.graphics.FontListParser$Alias
-android.graphics.FontListParser$Axis
-android.graphics.FontListParser$Config
-android.graphics.FontListParser$Family
-android.graphics.FontListParser$Font
android.graphics.Insets
android.graphics.Interpolator
android.graphics.Interpolator$Result
@@ -1001,10 +996,6 @@
android.hardware.input.InputDeviceIdentifier$1
android.hardware.input.InputManager
android.hardware.input.InputManager$InputDevicesChangedListener
-# These cannot be preloaded and need to be refactored into system server. b/17791590, b/21935130
-# android.hardware.location.ActivityRecognitionHardware
-# android.hardware.location.IActivityRecognitionHardware
-# android.hardware.location.IActivityRecognitionHardware$Stub
android.hardware.location.ContextHubManager
android.hardware.location.IContextHubService
android.hardware.location.IContextHubService$Stub
@@ -1847,6 +1838,11 @@
android.text.DynamicLayout$ChangeWatcher
android.text.Editable
android.text.Editable$Factory
+android.text.FontConfig
+android.text.FontConfig$Alias
+android.text.FontConfig$Axis
+android.text.FontConfig$Family
+android.text.FontConfig$Font
android.text.GetChars
android.text.GraphicsOperations
android.text.Html
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 88bc99f..341438d 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3311,6 +3311,26 @@
// OS: 8.0
MANAGE_EXTERNAL_SOURCES = 808;
+ // ACTION: Logged when terms activity finishes.
+ // TIME: Indicates time taken by terms activity to finish in MS.
+ PROVISIONING_TERMS_ACTIVITY_TIME_MS = 809;
+
+ // Indicates number of terms displayed on the terms screen.
+ PROVISIONING_TERMS_COUNT = 810;
+
+ // Indicates number of terms read on the terms screen.
+ PROVISIONING_TERMS_READ = 811;
+
+ // Logs that the user has edited the picture-in-picture settings.
+ // CATEGORY: SETTINGS
+ SETTINGS_MANAGE_PICTURE_IN_PICTURE = 812;
+
+ // ACTION: Allow "Enable picture-in-picture on hide" for an app
+ APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW = 813;
+
+ // ACTION: Deny "Enable picture-in-picture on hide" for an app
+ APP_PICTURE_IN_PICTURE_ON_HIDE_DENY = 814;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 7e82586..88c05b5 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -34,13 +34,15 @@
import android.app.backup.BackupTransport;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
-import android.app.backup.IBackupObserver;
-import android.app.backup.RestoreDescription;
-import android.app.backup.RestoreSet;
import android.app.backup.IBackupManager;
+import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.app.backup.RestoreDescription;
+import android.app.backup.RestoreSet;
+import android.app.backup.SelectBackupTransportCallback;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -55,16 +57,15 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Environment.UserEnvironment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -79,15 +80,12 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
-import android.os.Environment.UserEnvironment;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.EventLog;
import android.util.Log;
@@ -105,6 +103,8 @@
import com.android.server.SystemService;
import com.android.server.backup.PackageManagerBackupAgent.Metadata;
+import libcore.io.IoUtils;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -139,7 +139,6 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Random;
@@ -166,8 +165,6 @@
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
-import libcore.io.IoUtils;
-
public class BackupManagerService {
private static final String TAG = "BackupManagerService";
@@ -271,6 +268,8 @@
private IStorageManager mStorageManager;
IBackupManager mBackupManagerBinder;
+ private final TransportManager mTransportManager;
+
boolean mEnabled; // access to this is synchronized on 'this'
boolean mProvisioned;
boolean mAutoRestore;
@@ -322,16 +321,6 @@
final Object mClearDataLock = new Object();
volatile boolean mClearingData;
- // Transport bookkeeping
- final ArraySet<ComponentName> mTransportWhitelist;
- final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
- final ArrayMap<String,String> mTransportNames
- = new ArrayMap<String,String>(); // component name -> registration name
- final ArrayMap<String,IBackupTransport> mTransports
- = new ArrayMap<String,IBackupTransport>(); // registration name -> binder
- final ArrayMap<String,TransportConnection> mTransportConnections
- = new ArrayMap<String,TransportConnection>();
- String mCurrentTransport;
ActiveRestoreSession mActiveRestoreSession;
// Watch the device provisioning operation during setup
@@ -756,7 +745,7 @@
{
mLastBackupPass = System.currentTimeMillis();
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport == null) {
Slog.v(TAG, "Backup requested but no transport available");
synchronized (mQueueLock) {
@@ -1202,32 +1191,19 @@
// Set up our transport options and initialize the default transport
// TODO: Don't create transports that we don't need to?
SystemConfig systemConfig = SystemConfig.getInstance();
- mTransportWhitelist = systemConfig.getBackupTransportWhitelist();
+ Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
String transport = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.BACKUP_TRANSPORT);
if (TextUtils.isEmpty(transport)) {
transport = null;
}
- mCurrentTransport = transport;
- if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport);
+ String currentTransport = transport;
+ if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
- // Find all transport hosts and bind to their services
- // TODO: http://b/22388012
- List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
- mTransportServiceIntent, 0, UserHandle.USER_SYSTEM);
- if (DEBUG) {
- Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size()));
- }
- if (hosts != null) {
- for (int i = 0; i < hosts.size(); i++) {
- final ServiceInfo transportService = hosts.get(i).serviceInfo;
- if (MORE_DEBUG) {
- Slog.v(TAG, " " + transportService.packageName + "/" + transportService.name);
- }
- tryBindTransport(transportService);
- }
- }
+ mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
+ mTransportBoundListener);
+ mTransportManager.registerAllTransports();
// Now that we know about valid backup participants, parse any
// leftover journal files into the pending backup set
@@ -1751,7 +1727,7 @@
mBackupHandler.removeMessages(MSG_RETRY_INIT);
try {
- IBackupTransport transport = getTransport(transportName);
+ IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
if (transport != null) {
String transportDirName = transport.transportDirName();
File stateDir = new File(mBaseStateDir, transportDirName);
@@ -1829,49 +1805,39 @@
}
}
- // Add a transport to our set of available backends. If 'transport' is null, this
- // is an unregistration, and the transport's entry is removed from our bookkeeping.
- private void registerTransport(String name, String component, IBackupTransport transport) {
- synchronized (mTransports) {
- if (DEBUG) Slog.v(TAG, "Registering transport "
- + component + "::" + name + " = " + transport);
- if (transport != null) {
- mTransports.put(name, transport);
- mTransportNames.put(component, name);
- } else {
- mTransports.remove(mTransportNames.get(component));
- mTransportNames.remove(component);
- // Nothing further to do in the unregistration case
- return;
- }
- }
+ private TransportManager.TransportBoundListener mTransportBoundListener =
+ new TransportManager.TransportBoundListener() {
+ @Override
+ public boolean onTransportBound(IBackupTransport transport) {
+ // If the init sentinel file exists, we need to be sure to perform the init
+ // as soon as practical. We also create the state directory at registration
+ // time to ensure it's present from the outset.
+ String name = null;
+ try {
+ name = transport.name();
+ String transportDirName = transport.transportDirName();
+ File stateDir = new File(mBaseStateDir, transportDirName);
+ stateDir.mkdirs();
- // If the init sentinel file exists, we need to be sure to perform the init
- // as soon as practical. We also create the state directory at registration
- // time to ensure it's present from the outset.
- try {
- String transportName = transport.transportDirName();
- File stateDir = new File(mBaseStateDir, transportName);
- stateDir.mkdirs();
+ File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+ if (initSentinel.exists()) {
+ synchronized (mQueueLock) {
+ mPendingInits.add(name);
- File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
- if (initSentinel.exists()) {
- synchronized (mQueueLock) {
- mPendingInits.add(name);
-
- // TODO: pick a better starting time than now + 1 minute
- long delay = 1000 * 60; // one minute, in milliseconds
- mAlarmManager.set(AlarmManager.RTC_WAKEUP,
- System.currentTimeMillis() + delay, mRunInitIntent);
+ // TODO: pick a better starting time than now + 1 minute
+ long delay = 1000 * 60; // one minute, in milliseconds
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + delay, mRunInitIntent);
+ }
}
+ return true;
+ } catch (Exception e) {
+ // the transport threw when asked its file naming prefs; declare it invalid
+ Slog.w(TAG, "Failed to regiser transport: " + name);
+ return false;
}
- } catch (Exception e) {
- // the transport threw when asked its file naming prefs; declare it invalid
- Slog.e(TAG, "Unable to register transport as " + name);
- mTransportNames.remove(component);
- mTransports.remove(name);
}
- }
+ };
// ----- Track installation/removal of packages -----
BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -1899,75 +1865,17 @@
// At package-changed we only care about looking at new transport states
if (changed) {
- try {
- String[] components =
- intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ String[] components =
+ intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
- if (MORE_DEBUG) {
- Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
- for (int i = 0; i < components.length; i++) {
- Slog.i(TAG, " * " + components[i]);
- }
- }
-
- // In general we need to try to bind any time we see a component enable
- // state change, because that change may have made a transport available.
- // However, because we currently only support a single transport component
- // per package, we can skip the bind attempt if the change (a) affects a
- // package known to host a transport, but (b) does not affect the known
- // transport component itself.
- //
- // In addition, if the change *is* to a known transport component, we need
- // to unbind it before retrying the binding.
- boolean tryBind = true;
- synchronized (mTransports) {
- TransportConnection conn = mTransportConnections.get(pkgName);
- if (conn != null) {
- // We have a bound transport in this package; do we need to rebind it?
- final ServiceInfo svc = conn.mTransport;
- ComponentName svcName =
- new ComponentName(svc.packageName, svc.name);
- if (svc.packageName.equals(pkgName)) {
- final String className = svcName.getClassName();
- if (MORE_DEBUG) {
- Slog.i(TAG, "Checking need to rebind " + className);
- }
- // See whether it's the transport component within this package
- boolean isTransport = false;
- for (int i = 0; i < components.length; i++) {
- if (className.equals(components[i])) {
- // Okay, it's an existing transport component.
- final String flatName = svcName.flattenToShortString();
- mContext.unbindService(conn);
- mTransportConnections.remove(pkgName);
- mTransports.remove(mTransportNames.get(flatName));
- mTransportNames.remove(flatName);
- isTransport = true;
- break;
- }
- }
- if (!isTransport) {
- // A non-transport component within a package that is hosting
- // a bound transport
- tryBind = false;
- }
- }
- }
- }
- // and now (re)bind as appropriate
- if (tryBind) {
- if (MORE_DEBUG) {
- Slog.i(TAG, "Yes, need to recheck binding");
- }
- PackageInfo app = mPackageManager.getPackageInfo(pkgName, 0);
- checkForTransportAndBind(app);
- }
- } catch (NameNotFoundException e) {
- // Nope, can't find it - just ignore
- if (MORE_DEBUG) {
- Slog.w(TAG, "Can't find changed package " + pkgName);
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
+ for (int i = 0; i < components.length; i++) {
+ Slog.i(TAG, " * " + components[i]);
}
}
+
+ mTransportManager.onPackageChanged(pkgName, components);
return; // nothing more to do in the PACKAGE_CHANGED case
}
@@ -2015,19 +1923,7 @@
writeFullBackupScheduleAsync();
}
- // Transport maintenance: rebind to known existing transports that have
- // just been updated; and bind to any newly-installed transport services.
- synchronized (mTransports) {
- final TransportConnection conn = mTransportConnections.get(packageName);
- if (conn != null) {
- if (MORE_DEBUG) {
- Slog.i(TAG, "Transport package changed; rebinding");
- }
- bindTransport(conn.mTransport);
- } else {
- checkForTransportAndBind(app);
- }
- }
+ mTransportManager.onPackageAdded(packageName);
} catch (NameNotFoundException e) {
// doesn't really exist; ignore it
@@ -2051,107 +1947,13 @@
removePackageParticipantsLocked(pkgList, uid);
}
}
+ for (String pkgName : pkgList) {
+ mTransportManager.onPackageRemoved(pkgName);
+ }
}
}
};
- // ----- Track connection to transports service -----
- class TransportConnection implements ServiceConnection {
- ServiceInfo mTransport;
-
- public TransportConnection(ServiceInfo transport) {
- mTransport = transport;
- }
-
- @Override
- public void onServiceConnected(ComponentName component, IBinder service) {
- if (DEBUG) Slog.v(TAG, "Connected to transport " + component);
- final String name = component.flattenToShortString();
- try {
- IBackupTransport transport = IBackupTransport.Stub.asInterface(service);
- registerTransport(transport.name(), name, transport);
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 1);
- } catch (Exception e) {
- Slog.e(TAG, "Unable to register transport " + component
- + ": " + e.getMessage());
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 0);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName component) {
- if (DEBUG) Slog.v(TAG, "Disconnected from transport " + component);
- final String name = component.flattenToShortString();
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 0);
- registerTransport(null, name, null);
- }
- };
-
- // Check whether the given package hosts a transport, and bind if so
- void checkForTransportAndBind(PackageInfo pkgInfo) {
- Intent intent = new Intent(mTransportServiceIntent)
- .setPackage(pkgInfo.packageName);
- // TODO: http://b/22388012
- List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
- intent, 0, UserHandle.USER_SYSTEM);
- if (hosts != null) {
- final int N = hosts.size();
- for (int i = 0; i < N; i++) {
- final ServiceInfo info = hosts.get(i).serviceInfo;
- tryBindTransport(info);
- }
- }
- }
-
- // Verify that the service exists and is hosted by a privileged app, then proceed to bind
- boolean tryBindTransport(ServiceInfo info) {
- try {
- PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0);
- if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
- != 0) {
- return bindTransport(info);
- } else {
- Slog.w(TAG, "Transport package " + info.packageName + " not privileged");
- }
- } catch (NameNotFoundException e) {
- Slog.w(TAG, "Problem resolving transport package " + info.packageName);
- }
- return false;
- }
-
- // Actually bind; presumes that we have already validated the transport service
- boolean bindTransport(ServiceInfo transport) {
- ComponentName svcName = new ComponentName(transport.packageName, transport.name);
- if (!mTransportWhitelist.contains(svcName)) {
- Slog.w(TAG, "Proposed transport " + svcName + " not whitelisted; ignoring");
- return false;
- }
-
- if (MORE_DEBUG) {
- Slog.i(TAG, "Binding to transport host " + svcName);
- }
- Intent intent = new Intent(mTransportServiceIntent);
- intent.setComponent(svcName);
-
- TransportConnection connection;
- synchronized (mTransports) {
- connection = mTransportConnections.get(transport.packageName);
- if (null == connection) {
- connection = new TransportConnection(transport);
- mTransportConnections.put(transport.packageName, connection);
- } else {
- // This is a rebind due to package upgrade. The service won't be
- // automatically relaunched for us until we explicitly rebind, but
- // we need to unbind the now-orphaned original connection.
- mContext.unbindService(connection);
- }
- }
- // TODO: http://b/22388012
- return mContext.bindServiceAsUser(intent,
- connection, Context.BIND_AUTO_CREATE,
- UserHandle.SYSTEM);
- }
-
// Add the backup agents in the given packages to our set of known backup participants.
// If 'packageNames' is null, adds all backup agents in the whole system.
void addPackageParticipantsLocked(String[] packageNames) {
@@ -2352,34 +2154,12 @@
}
}
- // Return the given transport
- private IBackupTransport getTransport(String transportName) {
- synchronized (mTransports) {
- IBackupTransport transport = mTransports.get(transportName);
- if (transport == null) {
- Slog.w(TAG, "Requested unavailable transport: " + transportName);
- }
- return transport;
- }
- }
-
// What name is this transport registered under...?
private String getTransportName(IBackupTransport transport) {
if (MORE_DEBUG) {
Slog.v(TAG, "Searching for transport name of " + transport);
}
- synchronized (mTransports) {
- final int N = mTransports.size();
- for (int i = 0; i < N; i++) {
- if (mTransports.valueAt(i).equals(transport)) {
- if (MORE_DEBUG) {
- Slog.v(TAG, " Name found: " + mTransports.keyAt(i));
- }
- return mTransports.keyAt(i);
- }
- }
- }
- return null;
+ return mTransportManager.getTransportName(transport);
}
// fire off a backup agent, blocking until it attaches or times out
@@ -2505,7 +2285,7 @@
throw new IllegalArgumentException("No packages are provided for backup");
}
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport == null) {
sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
return BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -3025,7 +2805,7 @@
if (MORE_DEBUG) Slog.d(TAG, "Server requires init; rerunning");
addBackupTrace("init required; rerunning");
try {
- final String name = getTransportName(mTransport);
+ final String name = mTransportManager.getTransportName(mTransport);
if (name != null) {
mPendingInits.add(name);
} else {
@@ -4503,7 +4283,7 @@
return;
}
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -5119,7 +4899,7 @@
headBusy = false;
- if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+ if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) {
if (MORE_DEBUG) {
Slog.i(TAG, "Preconditions not met; not running full backup");
}
@@ -9115,7 +8895,8 @@
public void run() {
try {
for (String transportName : mQueue) {
- IBackupTransport transport = getTransport(transportName);
+ IBackupTransport transport =
+ mTransportManager.getTransportBinder(transportName);
if (transport == null) {
Slog.e(TAG, "Requested init for " + transportName + " but not found");
continue;
@@ -9312,7 +9093,8 @@
if (MORE_DEBUG) Slog.v(TAG, "Found the app - running clear process");
mBackupHandler.removeMessages(MSG_RETRY_CLEAR);
synchronized (mQueueLock) {
- final IBackupTransport transport = getTransport(transportName);
+ final IBackupTransport transport =
+ mTransportManager.getTransportBinder(transportName);
if (transport == null) {
// transport is currently unavailable -- make sure to retry
Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR,
@@ -9450,7 +9232,7 @@
throw new IllegalStateException("Restore supported only for the device owner");
}
- if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+ if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) {
Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?");
} else {
if (DEBUG) {
@@ -9718,10 +9500,7 @@
if (wasEnabled && mProvisioned) {
// NOTE: we currently flush every registered transport, not just
// the currently-active one.
- HashSet<String> allTransports;
- synchronized (mTransports) {
- allTransports = new HashSet<String>(mTransports.keySet());
- }
+ String[] allTransports = mTransportManager.getBoundTransportNames();
// build the set of transports for which we are posting an init
for (String transport : allTransports) {
recordInitPendingLocked(true, transport);
@@ -9774,36 +9553,27 @@
public String getCurrentTransport() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getCurrentTransport");
- if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport);
- return mCurrentTransport;
+ String currentTransport = mTransportManager.getCurrentTransportName();
+ if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + currentTransport);
+ return currentTransport;
}
// Report all known, available backup transports
public String[] listAllTransports() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports");
- String[] list = null;
- ArrayList<String> known = new ArrayList<String>();
- for (Map.Entry<String, IBackupTransport> entry : mTransports.entrySet()) {
- if (entry.getValue() != null) {
- known.add(entry.getKey());
- }
- }
+ return mTransportManager.getBoundTransportNames();
+ }
- if (known.size() > 0) {
- list = new String[known.size()];
- known.toArray(list);
- }
- return list;
+ public ComponentName[] listAllTransportComponents() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "listAllTransportComponents");
+ return mTransportManager.getAllTransportCompenents();
}
public String[] getTransportWhitelist() {
// No permission check, intentionally.
- String[] whitelist = new String[mTransportWhitelist.size()];
- for (int i = mTransportWhitelist.size() - 1; i >= 0; i--) {
- whitelist[i] = mTransportWhitelist.valueAt(i).flattenToShortString();
- }
- return whitelist;
+ return mTransportManager.getTransportWhitelist().toArray(new String[0]);
}
// Select which transport to use for the next backup operation.
@@ -9811,22 +9581,58 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"selectBackupTransport");
- synchronized (mTransports) {
- final long oldId = Binder.clearCallingIdentity();
- try {
- String prevTransport = mCurrentTransport;
- mCurrentTransport = transport;
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.BACKUP_TRANSPORT, transport);
- Slog.v(TAG, "selectBackupTransport() set " + mCurrentTransport
- + " returning " + prevTransport);
- return prevTransport;
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ String prevTransport = mTransportManager.selectTransport(transport);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_TRANSPORT, transport);
+ Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName()
+ + " returning " + prevTransport);
+ return prevTransport;
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
}
}
+ public void selectBackupTransportAsync(final ComponentName transport,
+ final ISelectBackupTransportCallback listener) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "selectBackupTransportAsync");
+
+ final long oldId = Binder.clearCallingIdentity();
+
+ Slog.v(TAG, "selectBackupTransportAsync() called with transport " +
+ transport.flattenToShortString());
+
+ mTransportManager.ensureTransportReady(transport, new SelectBackupTransportCallback() {
+ @Override
+ public void onSuccess(String transportName) {
+ mTransportManager.selectTransport(transportName);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_TRANSPORT,
+ mTransportManager.getCurrentTransportName());
+ Slog.v(TAG, "Transport successfully selected: " + transport.flattenToShortString());
+ try {
+ listener.onSuccess(transportName);
+ } catch (RemoteException e) {
+ // Nothing to do here.
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Slog.v(TAG, "Failed to select transport: " + transport.flattenToShortString());
+ try {
+ listener.onFailure(reason);
+ } catch (RemoteException e) {
+ // Nothing to do here.
+ }
+ }
+ });
+
+ Binder.restoreCallingIdentity(oldId);
+ }
+
// Supply the configuration Intent for the given transport. If the name is not one
// of the available transports, or if the transport does not supply any configuration
// UI, the method returns null.
@@ -9834,18 +9640,16 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getConfigurationIntent");
- synchronized (mTransports) {
- final IBackupTransport transport = mTransports.get(transportName);
- if (transport != null) {
- try {
- final Intent intent = transport.configurationIntent();
- if (MORE_DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent "
- + intent);
- return intent;
- } catch (Exception e) {
- /* fall through to return null */
- Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage());
- }
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final Intent intent = transport.configurationIntent();
+ if (MORE_DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent "
+ + intent);
+ return intent;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage());
}
}
@@ -9861,17 +9665,15 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDestinationString");
- synchronized (mTransports) {
- final IBackupTransport transport = mTransports.get(transportName);
- if (transport != null) {
- try {
- final String text = transport.currentDestinationString();
- if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
- return text;
- } catch (Exception e) {
- /* fall through to return null */
- Slog.e(TAG, "Unable to get string from transport: " + e.getMessage());
- }
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final String text = transport.currentDestinationString();
+ if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
+ return text;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get string from transport: " + e.getMessage());
}
}
@@ -9883,18 +9685,16 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDataManagementIntent");
- synchronized (mTransports) {
- final IBackupTransport transport = mTransports.get(transportName);
- if (transport != null) {
- try {
- final Intent intent = transport.dataManagementIntent();
- if (MORE_DEBUG) Slog.d(TAG, "getDataManagementIntent() returning intent "
- + intent);
- return intent;
- } catch (Exception e) {
- /* fall through to return null */
- Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage());
- }
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final Intent intent = transport.dataManagementIntent();
+ if (MORE_DEBUG) Slog.d(TAG, "getDataManagementIntent() returning intent "
+ + intent);
+ return intent;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage());
}
}
@@ -9907,17 +9707,15 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDataManagementLabel");
- synchronized (mTransports) {
- final IBackupTransport transport = mTransports.get(transportName);
- if (transport != null) {
- try {
- final String text = transport.dataManagementLabel();
- if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text);
- return text;
- } catch (Exception e) {
- /* fall through to return null */
- Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage());
- }
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final String text = transport.dataManagementLabel();
+ if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text);
+ return text;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage());
}
}
@@ -9979,7 +9777,7 @@
}
// Do we have a transport to fetch data for us?
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport == null) {
if (DEBUG) Slog.w(TAG, "No transport");
skip = true;
@@ -10033,7 +9831,7 @@
boolean needPermission = true;
if (transport == null) {
- transport = mCurrentTransport;
+ transport = mTransportManager.getCurrentTransportName();
if (packageName != null) {
PackageInfo app = null;
@@ -10127,7 +9925,7 @@
appIsStopped(packageInfo.applicationInfo)) {
return false;
}
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport != null) {
try {
return transport.isAppEligibleForBackup(packageInfo,
@@ -10156,7 +9954,7 @@
ActiveRestoreSession(String packageName, String transport) {
mPackageName = packageName;
- mRestoreTransport = getTransport(transport);
+ mRestoreTransport = mTransportManager.getTransportBinder(transport);
}
public void markTimedOut() {
@@ -10515,7 +10313,7 @@
pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled());
pw.println("Transport whitelist:");
- for (ComponentName transport : mTransportWhitelist) {
+ for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
pw.print(" ");
pw.println(transport.flattenToShortString());
}
@@ -10524,9 +10322,9 @@
final String[] transports = listAllTransports();
if (transports != null) {
for (String t : listAllTransports()) {
- pw.println((t.equals(mCurrentTransport) ? " * " : " ") + t);
+ pw.println((t.equals(mTransportManager.getCurrentTransportName()) ? " * " : " ") + t);
try {
- IBackupTransport transport = getTransport(t);
+ IBackupTransport transport = mTransportManager.getTransportBinder(t);
File dir = new File(mBaseStateDir, transport.transportDirName());
pw.println(" destination: " + transport.currentDestinationString());
pw.println(" intent: " + transport.configurationIntent());
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index d677f5e..a1a2c95 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -20,6 +20,8 @@
import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
@@ -275,6 +277,12 @@
}
@Override
+ public ComponentName[] listAllTransportComponents() throws RemoteException {
+ BackupManagerService svc = mService;
+ return (svc != null) ? svc.listAllTransportComponents() : null;
+ }
+
+ @Override
public String[] getTransportWhitelist() {
BackupManagerService svc = mService;
return (svc != null) ? svc.getTransportWhitelist() : null;
@@ -287,6 +295,15 @@
}
@Override
+ public void selectBackupTransportAsync(ComponentName transport,
+ ISelectBackupTransportCallback listener) throws RemoteException {
+ BackupManagerService svc = mService;
+ if (svc != null) {
+ svc.selectBackupTransportAsync(transport, listener);
+ }
+ }
+
+ @Override
public Intent getConfigurationIntent(String transport) throws RemoteException {
BackupManagerService svc = mService;
return (svc != null) ? svc.getConfigurationIntent(transport) : null;
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
new file mode 100644
index 0000000..93d5a1e
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -0,0 +1,410 @@
+/*
+ * 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.backup;
+
+import android.app.backup.BackupManager;
+import android.app.backup.SelectBackupTransportCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.EventLogTags;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Handles in-memory bookkeeping of all BackupTransport objects.
+ */
+class TransportManager {
+
+ private static final String TAG = "BackupTransportManager";
+
+ private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+
+ private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final Set<ComponentName> mTransportWhitelist;
+
+ /**
+ * This listener is called after we bind to any transport. If it returns true, this is a valid
+ * transport.
+ */
+ private final TransportBoundListener mTransportBoundListener;
+
+ private String mCurrentTransportName;
+
+ /** Lock on this before accessing mValidTransports and mBoundTransports. */
+ private final Object mTransportLock = new Object();
+
+ /**
+ * We have detected these transports on the device. Unless in exceptional cases, we are also
+ * bound to all of these.
+ */
+ @GuardedBy("mTransportLock")
+ private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>();
+
+ /** We are currently bound to these transports. */
+ @GuardedBy("mTransportLock")
+ private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
+
+ TransportManager(Context context, Set<ComponentName> whitelist, String defaultTransport,
+ TransportBoundListener listener) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mTransportWhitelist = whitelist;
+ mCurrentTransportName = defaultTransport;
+ mTransportBoundListener = listener;
+ }
+
+ void onPackageAdded(String packageName) {
+ // New package added. Bind to all transports it contains.
+ synchronized (mTransportLock) {
+ log_verbose("Package added. Binding to all transports. " + packageName);
+ bindToAllInternal(packageName, null /* all components */);
+ }
+ }
+
+ void onPackageRemoved(String packageName) {
+ // Package removed. Remove all its transports from our list. These transports have already
+ // been removed from mBoundTransports because onServiceDisconnected would already been
+ // called on TransportConnection objects.
+ synchronized (mTransportLock) {
+ for (ComponentName transport : mValidTransports.keySet()) {
+ if (transport.getPackageName().equals(packageName)) {
+ TransportConnection removed = mValidTransports.remove(transport);
+ if (removed != null) {
+ mContext.unbindService(removed);
+ log_verbose("Package removed, Removing transport: " +
+ transport.flattenToShortString());
+ }
+ }
+ }
+ }
+ }
+
+ void onPackageChanged(String packageName, String[] components) {
+ synchronized (mTransportLock) {
+ // Remove all changed components from mValidTransports. We'll bind to them again
+ // and re-add them if still valid.
+ for (String component : components) {
+ ComponentName componentName = new ComponentName(packageName, component);
+ TransportConnection removed = mValidTransports.remove(componentName);
+ if (removed != null) {
+ mContext.unbindService(removed);
+ log_verbose("Package changed. Removing transport: " +
+ componentName.flattenToShortString());
+ }
+ }
+ bindToAllInternal(packageName, components);
+ }
+ }
+
+ IBackupTransport getTransportBinder(String transportName) {
+ synchronized (mTransportLock) {
+ ComponentName component = mBoundTransports.get(transportName);
+ if (component == null) {
+ Slog.w(TAG, "Transport " + transportName + " not bound.");
+ return null;
+ }
+ TransportConnection conn = mValidTransports.get(component);
+ if (conn == null) {
+ Slog.w(TAG, "Transport " + transportName + " not valid.");
+ return null;
+ }
+ return conn.getBinder();
+ }
+ }
+
+ IBackupTransport getCurrentTransportBinder() {
+ return getTransportBinder(mCurrentTransportName);
+ }
+
+ String getTransportName(IBackupTransport binder) {
+ synchronized (mTransportLock) {
+ for (TransportConnection conn : mValidTransports.values()) {
+ if (conn.getBinder() == binder) {
+ return conn.getName();
+ }
+ }
+ }
+ return null;
+ }
+
+ String[] getBoundTransportNames() {
+ synchronized (mTransportLock) {
+ return mBoundTransports.keySet().toArray(new String[0]);
+ }
+ }
+
+ ComponentName[] getAllTransportCompenents() {
+ synchronized (mTransportLock) {
+ return mValidTransports.keySet().toArray(new ComponentName[0]);
+ }
+ }
+
+ String getCurrentTransportName() {
+ return mCurrentTransportName;
+ }
+
+ Set<ComponentName> getTransportWhitelist() {
+ return mTransportWhitelist;
+ }
+
+ String selectTransport(String transport) {
+ synchronized (mTransportLock) {
+ String prevTransport = mCurrentTransportName;
+ mCurrentTransportName = transport;
+ return prevTransport;
+ }
+ }
+
+ void ensureTransportReady(ComponentName transportComponent, SelectBackupTransportCallback listener) {
+ synchronized (mTransportLock) {
+ TransportConnection conn = mValidTransports.get(transportComponent);
+ if (conn == null) {
+ listener.onFailure(BackupManager.ERROR_TRANSPORT_UNAVAILABLE);
+ return;
+ }
+ // Transport can be unbound if the process hosting it crashed.
+ conn.bindIfUnbound();
+ conn.addListener(listener);
+ }
+ }
+
+ void registerAllTransports() {
+ bindToAllInternal(null /* all packages */, null /* all components */);
+ }
+
+ /**
+ * Bind to all transports belonging to the given package and the given component list.
+ * null acts a wildcard.
+ *
+ * If packageName is null, bind to all transports in all packages.
+ * If components is null, bind to all transports in the given package.
+ */
+ private void bindToAllInternal(String packageName, String[] components) {
+ PackageInfo pkgInfo = null;
+ if (packageName != null) {
+ try {
+ pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package not found: " + packageName);
+ return;
+ }
+ }
+
+ Intent intent = new Intent(mTransportServiceIntent);
+ if (packageName != null) {
+ intent.setPackage(packageName);
+ }
+
+ List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
+ intent, 0, UserHandle.USER_SYSTEM);
+ if (hosts != null) {
+ for (ResolveInfo host : hosts) {
+ final ServiceInfo info = host.serviceInfo;
+ boolean shouldBind = false;
+ if (components != null && packageName != null) {
+ for (String component : components) {
+ ComponentName cn = new ComponentName(pkgInfo.packageName, component);
+ if (info.getComponentName().equals(cn)) {
+ shouldBind = true;
+ break;
+ }
+ }
+ } else {
+ shouldBind = true;
+ }
+ if (shouldBind && isTransportTrusted(info.getComponentName())) {
+ tryBindTransport(info);
+ }
+ }
+ }
+ }
+
+ /** Transport has to be whitelisted and privileged. */
+ private boolean isTransportTrusted(ComponentName transport) {
+ if (!mTransportWhitelist.contains(transport)) {
+ Slog.w(TAG, "BackupTransport " + transport.flattenToShortString() +
+ " not whitelisted.");
+ return false;
+ }
+ try {
+ PackageInfo packInfo = mPackageManager.getPackageInfo(transport.getPackageName(), 0);
+ if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+ == 0) {
+ Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package not found.", e);
+ return false;
+ }
+ return true;
+ }
+
+ private void tryBindTransport(ServiceInfo transport) {
+ Slog.d(TAG, "Binding to transport: " + transport.getComponentName().flattenToShortString());
+ // TODO: b/22388012 (Multi user backup and restore)
+ TransportConnection connection = new TransportConnection(transport.getComponentName());
+ if (bindToTransport(transport.getComponentName(), connection)) {
+ synchronized (mTransportLock) {
+ mValidTransports.put(transport.getComponentName(), connection);
+ }
+ } else {
+ Slog.w(TAG, "Couldn't bind to transport " + transport.getComponentName());
+ }
+ }
+
+ private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) {
+ Intent intent = new Intent(mTransportServiceIntent)
+ .setComponent(componentName);
+ return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+ UserHandle.SYSTEM);
+ }
+
+ private class TransportConnection implements ServiceConnection {
+
+ // Hold mTransportsLock to access these fields so as to provide a consistent view of them.
+ private IBackupTransport mBinder;
+ private final List<SelectBackupTransportCallback> mListeners = new ArrayList<>();
+ private String mTransportName;
+
+ private final ComponentName mTransportComponent;
+
+ private TransportConnection(ComponentName transportComponent) {
+ mTransportComponent = transportComponent;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder binder) {
+ synchronized (mTransportLock) {
+ mBinder = IBackupTransport.Stub.asInterface(binder);
+ boolean success = false;
+
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
+ component.flattenToShortString(), 1);
+
+ try {
+ mTransportName = mBinder.name();
+ // BackupManager requests some fields from the transport. If they are
+ // invalid, throw away this transport.
+ success = mTransportBoundListener.onTransportBound(mBinder);
+ } catch (RemoteException e) {
+ success = false;
+ Slog.e(TAG, "Couldn't get transport name.", e);
+ } finally {
+ if (success) {
+ Slog.d(TAG, "Bound to transport: " + component.flattenToShortString());
+ mBoundTransports.put(mTransportName, component);
+ for (SelectBackupTransportCallback listener : mListeners) {
+ listener.onSuccess(mTransportName);
+ }
+ } else {
+ Slog.w(TAG, "Bound to transport " + component.flattenToShortString() +
+ " but it is invalid");
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
+ component.flattenToShortString(), 0);
+ mContext.unbindService(this);
+ mValidTransports.remove(component);
+ mBinder = null;
+ for (SelectBackupTransportCallback listener : mListeners) {
+ listener.onFailure(BackupManager.ERROR_TRANSPORT_INVALID);
+ }
+ }
+ mListeners.clear();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ synchronized (mTransportLock) {
+ mBinder = null;
+ mBoundTransports.remove(mTransportName);
+ }
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
+ component.flattenToShortString(), 0);
+ Slog.w(TAG, "Disconnected from transport " + component.flattenToShortString());
+ }
+
+ private IBackupTransport getBinder() {
+ synchronized (mTransportLock) {
+ return mBinder;
+ }
+ }
+
+ private String getName() {
+ synchronized (mTransportLock) {
+ return mTransportName;
+ }
+ }
+
+ private void bindIfUnbound() {
+ synchronized (mTransportLock) {
+ if (mBinder == null) {
+ Slog.d(TAG,
+ "Rebinding to transport " + mTransportComponent.flattenToShortString());
+ bindToTransport(mTransportComponent, this);
+ }
+ }
+ }
+
+ private void addListener(SelectBackupTransportCallback listener) {
+ synchronized (mTransportLock) {
+ if (mBinder == null) {
+ // We are waiting for bind to complete. If mBinder is set to null after the bind
+ // is complete due to transport being invalid, we won't find 'this' connection
+ // object in mValidTransports list and this function can't be called.
+ mListeners.add(listener);
+ } else {
+ listener.onSuccess(mTransportName);
+ }
+ }
+ }
+ }
+
+ interface TransportBoundListener {
+ /** Should return true if this is a valid transport. */
+ boolean onTransportBound(IBackupTransport binder);
+ }
+
+ private static void log_verbose(String message) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Slog.v(TAG, message);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 581aa05..0e07ec0 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -504,8 +504,8 @@
for (int i = alarms.size()-1; i >= 0; i--) {
Alarm alarm = alarms.get(i);
try {
- if (alarm.uid == uid && ActivityManager.getService().getAppStartMode(
- uid, alarm.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+ if (alarm.uid == uid && ActivityManager.getService().isAppStartModeDisabled(
+ uid, alarm.packageName)) {
alarms.remove(i);
didRemove = true;
if (alarm.alarmClock != null) {
@@ -1089,8 +1089,7 @@
operation, directReceiver, listenerTag, workSource, flags, alarmClock,
callingUid, callingPackage);
try {
- if (ActivityManager.getService().getAppStartMode(callingUid, callingPackage)
- == ActivityManager.APP_START_MODE_DISABLED) {
+ if (ActivityManager.getService().isAppStartModeDisabled(callingUid, callingPackage)) {
Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+ " -- package not allowed to start");
return;
diff --git a/services/core/java/com/android/server/FontManagerService.java b/services/core/java/com/android/server/FontManagerService.java
new file mode 100644
index 0000000..593c322
--- /dev/null
+++ b/services/core/java/com/android/server/FontManagerService.java
@@ -0,0 +1,107 @@
+/*
+ * 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.content.Context;
+import android.graphics.FontListParser;
+import android.os.ParcelFileDescriptor;
+import android.text.FontConfig;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.font.IFontManager;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+public class FontManagerService extends IFontManager.Stub {
+ private static final String TAG = "FontManagerService";
+ private static final String FONTS_CONFIG = "/system/etc/fonts.xml";
+
+ @GuardedBy("mLock")
+ private FontConfig mConfig;
+ private final Object mLock = new Object();
+
+ public static final class Lifecycle extends SystemService {
+ private final FontManagerService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mService = new FontManagerService();
+ }
+
+ @Override
+ public void onStart() {
+ try {
+ publishBinderService(Context.FONT_SERVICE, mService);
+ } catch (Throwable t) {
+ // Starting this service is not critical to the running of this device and should
+ // therefore not crash the device. If it fails, log the error and continue.
+ Slog.e(TAG, "Could not start the FontManagerService.", t);
+ }
+ }
+ }
+
+ @Override
+ public FontConfig getSystemFonts() {
+ synchronized (mLock) {
+ if (mConfig != null) {
+ return new FontConfig(mConfig);
+ }
+
+ FontConfig config = loadFromSystem();
+ if (config == null) {
+ return null;
+ }
+
+ final int size = config.getFamilies().size();
+ for (int i = 0; i < size; ++i) {
+ FontConfig.Family family = config.getFamilies().get(i);
+ for (int j = 0; j < family.getFonts().size(); ++j) {
+ FontConfig.Font font = family.getFonts().get(j);
+ File fontFile = new File(font.getFontName());
+ try {
+ font.setFd(ParcelFileDescriptor.open(
+ fontFile, ParcelFileDescriptor.MODE_READ_ONLY));
+ } catch (IOException e) {
+ Slog.e(TAG, "Error opening font file " + font.getFontName(), e);
+ }
+ }
+ }
+
+ mConfig = config;
+ return new FontConfig(mConfig);
+ }
+ }
+
+ private FontConfig loadFromSystem() {
+ File configFilename = new File(FONTS_CONFIG);
+ try {
+ FileInputStream fontsIn = new FileInputStream(configFilename);
+ return FontListParser.parse(fontsIn);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Error opening " + configFilename, e);
+ }
+ return null;
+ }
+
+ public FontManagerService() {
+ }
+}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index f718fa1..bee1f97 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -243,7 +243,6 @@
private PendingIntent mImeSwitchPendingIntent;
private boolean mShowOngoingImeSwitcherForPhones;
private boolean mNotificationShown;
- private final boolean mImeSelectedOnBoot;
static class SessionState {
final ClientState client;
@@ -566,7 +565,7 @@
}
}
- class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
+ class ImmsBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
@@ -587,6 +586,10 @@
Intent.EXTRA_SETTING_NEW_VALUE);
restoreEnabledInputMethods(mContext, prevValue, newValue);
}
+ } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ synchronized (mMethodMap) {
+ resetStateIfCurrentLocaleChangedLocked();
+ }
} else {
Slog.w(TAG, "Unexpected intent " + intent);
}
@@ -845,9 +848,11 @@
return;
}
mSettings.switchCurrentUser(currentUserId, !mSystemReady);
- // We need to rebuild IMEs.
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ if (mSystemReady) {
+ // We need to rebuild IMEs.
+ buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ }
}
}
@@ -897,13 +902,6 @@
mShowOngoingImeSwitcherForPhones = false;
- final IntentFilter broadcastFilter = new IntentFilter();
- broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
- broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
- broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED);
- mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
-
mNotificationShown = false;
int userId = 0;
try {
@@ -911,7 +909,6 @@
} catch (RemoteException e) {
Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
}
- mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
@@ -919,48 +916,8 @@
updateCurrentProfileIds();
mFileManager = new InputMethodFileManager(mMethodMap, userId);
- synchronized (mMethodMap) {
- mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mSettings, context);
- }
-
- // Just checking if defaultImiId is empty or not
- final String defaultImiId = mSettings.getSelectedInputMethod();
- if (DEBUG) {
- Slog.d(TAG, "Initial default ime = " + defaultImiId);
- }
- mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
-
- synchronized (mMethodMap) {
- buildInputMethodListLocked(!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
- }
- mSettings.enableAllIMEsIfThereIsNoEnabledIME();
-
- if (!mImeSelectedOnBoot) {
- Slog.w(TAG, "No IME selected. Choose the most applicable IME.");
- synchronized (mMethodMap) {
- resetDefaultImeLocked(context);
- }
- }
-
- synchronized (mMethodMap) {
- mSettingsObserver.registerContentObserverLocked(userId);
- updateFromSettingsLocked(true);
- }
-
- // IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME
- // according to the new system locale.
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_LOCALE_CHANGED);
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized(mMethodMap) {
- resetStateIfCurrentLocaleChangedLocked();
- }
- }
- }, filter);
+ mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
+ mSettings, context);
}
private void resetDefaultImeLocked(Context context) {
@@ -1089,6 +1046,7 @@
}
if (!mSystemReady) {
mSystemReady = true;
+ mLastSystemLocales = mRes.getConfiguration().getLocales();
final int currentUserId = mSettings.getCurrentUserId();
mSettings.switchCurrentUser(currentUserId,
!mUserManager.isUserUnlockingOrUnlocked(currentUserId));
@@ -1105,14 +1063,25 @@
mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
mHardKeyboardListener);
}
- if (!mImeSelectedOnBoot) {
- Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
- resetStateIfCurrentLocaleChangedLocked();
- InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
- mSettings.getEnabledInputMethodListLocked(),
- mSettings.getCurrentUserId(), mContext.getBasePackageName());
- }
- mLastSystemLocales = mRes.getConfiguration().getLocales();
+
+ mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+ mSettingsObserver.registerContentObserverLocked(currentUserId);
+
+ final IntentFilter broadcastFilter = new IntentFilter();
+ broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
+ broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
+ broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED);
+ broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
+
+ buildInputMethodListLocked(true /* resetDefaultEnabledIme */);
+ resetDefaultImeLocked(mContext);
+ updateFromSettingsLocked(true);
+ InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
+ mSettings.getEnabledInputMethodListLocked(), currentUserId,
+ mContext.getBasePackageName());
+
try {
startInputInnerLocked();
} catch (RuntimeException e) {
@@ -2624,6 +2593,9 @@
// additional input method subtypes to the IME.
if (TextUtils.isEmpty(imiId) || subtypes == null) return;
synchronized (mMethodMap) {
+ if (!mSystemReady) {
+ return;
+ }
final InputMethodInfo imi = mMethodMap.get(imiId);
if (imi == null) return;
final String[] packageInfos;
@@ -3048,6 +3020,10 @@
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ caller=" + Debug.getCallers(10));
}
+ if (!mSystemReady) {
+ Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
+ return;
+ }
mMethodList.clear();
mMethodMap.clear();
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index c9b59ade..6cc72de 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.app.ActivityManager;
import android.content.pm.PackageManagerInternal;
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
@@ -138,6 +139,9 @@
// The maximum interval a location request can have and still be considered "high power".
private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
+ // default background throttling interval if not overriden in settings
+ private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 1000;
+
// Location Providers may sometimes deliver location updates
// slightly faster that requested - provide grace period so
// we don't unnecessarily filter events that are otherwise on
@@ -157,6 +161,7 @@
private GeofenceManager mGeofenceManager;
private PackageManager mPackageManager;
private PowerManager mPowerManager;
+ private ActivityManager mActivityManager;
private UserManager mUserManager;
private GeocoderProxy mGeocodeProvider;
private IGnssStatusProvider mGnssStatusProvider;
@@ -171,47 +176,47 @@
// --- fields below are protected by mLock ---
// Set of providers that are explicitly enabled
// Only used by passive, fused & test. Network & GPS are controlled separately, and not listed.
- private final Set<String> mEnabledProviders = new HashSet<String>();
+ private final Set<String> mEnabledProviders = new HashSet<>();
// Set of providers that are explicitly disabled
- private final Set<String> mDisabledProviders = new HashSet<String>();
+ private final Set<String> mDisabledProviders = new HashSet<>();
// Mock (test) providers
private final HashMap<String, MockProvider> mMockProviders =
- new HashMap<String, MockProvider>();
+ new HashMap<>();
// all receivers
- private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>();
+ private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
// currently installed providers (with mocks replacing real providers)
private final ArrayList<LocationProviderInterface> mProviders =
- new ArrayList<LocationProviderInterface>();
+ new ArrayList<>();
// real providers, saved here when mocked out
private final HashMap<String, LocationProviderInterface> mRealProviders =
- new HashMap<String, LocationProviderInterface>();
+ new HashMap<>();
// mapping from provider name to provider
private final HashMap<String, LocationProviderInterface> mProvidersByName =
- new HashMap<String, LocationProviderInterface>();
+ new HashMap<>();
// mapping from provider name to all its UpdateRecords
private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
- new HashMap<String, ArrayList<UpdateRecord>>();
+ new HashMap<>();
private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
// mapping from provider name to last known location
- private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>();
+ private final HashMap<String, Location> mLastLocation = new HashMap<>();
// same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
// locations stored here are not fudged for coarse permissions.
private final HashMap<String, Location> mLastLocationCoarseInterval =
- new HashMap<String, Location>();
+ new HashMap<>();
// all providers that operate over proxy, for authorizing incoming location
private final ArrayList<LocationProviderProxy> mProxyProviders =
- new ArrayList<LocationProviderProxy>();
+ new ArrayList<>();
// current active user on the device - other users are denied location data
private int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -252,6 +257,10 @@
// fetch power manager
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ // fetch activity manager
+ mActivityManager
+ = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+
// prepare worker thread
mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper());
@@ -286,6 +295,40 @@
};
mPackageManager.addOnPermissionsChangeListener(permissionListener);
+ // listen for background/foreground changes
+ ActivityManager.OnUidImportanceListener uidImportanceListener
+ = new ActivityManager.OnUidImportanceListener() {
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ boolean foreground = isImportanceForeground(importance);
+ HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
+ synchronized (mLock) {
+ for (Map.Entry<String, ArrayList<UpdateRecord>> entry
+ : mRecordsByProvider.entrySet()) {
+ String provider = entry.getKey();
+ for (UpdateRecord record : entry.getValue()) {
+ if (record.mReceiver.mUid == uid
+ && record.mIsForegroundUid != foreground) {
+ if (D) Log.d(TAG, "request from uid " + uid + " is now "
+ + (foreground ? "foreground" : "background)"));
+ record.mIsForegroundUid = foreground;
+
+ if (!isThrottlingExemptLocked(record.mReceiver)) {
+ affectedProviders.add(provider);
+ }
+ }
+ }
+ }
+ for (String provider : affectedProviders) {
+ applyRequirementsLocked(provider);
+ }
+ }
+
+ }
+ };
+ mActivityManager.addOnUidImportanceListener(uidImportanceListener,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE);
+
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
updateUserProfiles(mCurrentUserId);
@@ -305,6 +348,17 @@
}
}
}, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS),
+ true,
+ new ContentObserver(mLocationHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ updateProvidersLocked();
+ }
+ }
+ }, UserHandle.USER_ALL);
mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
// listen for user change
@@ -334,6 +388,10 @@
}, UserHandle.ALL, intentFilter, null, mLocationHandler);
}
+ private static boolean isImportanceForeground(int importance) {
+ return importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+ }
+
/**
* Provides a way for components held by the {@link LocationManagerService} to clean-up
* gracefully on system's shutdown.
@@ -483,7 +541,7 @@
that matches the signature of at least one package on this list.
*/
Resources resources = mContext.getResources();
- ArrayList<String> providerPackageNames = new ArrayList<String>();
+ ArrayList<String> providerPackageNames = new ArrayList<>();
String[] pkgs = resources.getStringArray(
com.android.internal.R.array.config_locationProviderPackageNames);
if (D) Log.d(TAG, "certificates for location providers pulled from: " +
@@ -651,7 +709,7 @@
final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
final Object mKey;
- final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>();
+ final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<>();
// True if app ops has started monitoring this receiver for locations.
boolean mOpMonitoring;
@@ -691,10 +749,7 @@
@Override
public boolean equals(Object otherObj) {
- if (otherObj instanceof Receiver) {
- return mKey.equals(((Receiver)otherObj).mKey);
- }
- return false;
+ return (otherObj instanceof Receiver) && mKey.equals(((Receiver) otherObj).mKey);
}
@Override
@@ -1011,13 +1066,25 @@
mProvidersByName.remove(provider.getName());
}
+ private boolean isOverlayProviderPackageLocked(String packageName) {
+ for (LocationProviderInterface provider : mProviders) {
+ if (provider instanceof LocationProviderProxy) {
+ if (packageName.equals(
+ ((LocationProviderProxy) provider).getConnectedPackageName())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
/**
* Returns "true" if access to the specified location provider is allowed by the current
* user's settings. Access to all location providers is forbidden to non-location-provider
* processes belonging to background users.
*
* @param provider the name of the location provider
- * @return
*/
private boolean isAllowedByCurrentUserSettingsLocked(String provider) {
if (mEnabledProviders.contains(provider)) {
@@ -1039,7 +1106,6 @@
*
* @param provider the name of the location provider
* @param uid the requestor's UID
- * @return
*/
private boolean isAllowedByUserSettingsLocked(String provider, int uid) {
if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) {
@@ -1197,11 +1263,7 @@
}
}
- if (getAllowedResolutionLevel(pid, uid) < allowedResolutionLevel) {
- return false;
- }
-
- return true;
+ return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
}
boolean checkLocationAccess(int pid, int uid, String packageName, int allowedResolutionLevel) {
@@ -1212,11 +1274,7 @@
}
}
- if (getAllowedResolutionLevel(pid, uid) < allowedResolutionLevel) {
- return false;
- }
-
- return true;
+ return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
}
/**
@@ -1228,7 +1286,7 @@
public List<String> getAllProviders() {
ArrayList<String> out;
synchronized (mLock) {
- out = new ArrayList<String>(mProviders.size());
+ out = new ArrayList<>(mProviders.size());
for (LocationProviderInterface provider : mProviders) {
String name = provider.getName();
if (LocationManager.FUSED_PROVIDER.equals(name)) {
@@ -1251,11 +1309,11 @@
public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
ArrayList<String> out;
- int uid = Binder.getCallingUid();;
+ int uid = Binder.getCallingUid();
long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- out = new ArrayList<String>(mProviders.size());
+ out = new ArrayList<>(mProviders.size());
for (LocationProviderInterface provider : mProviders) {
String name = provider.getName();
if (LocationManager.FUSED_PROVIDER.equals(name)) {
@@ -1370,14 +1428,12 @@
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
if (records != null) {
- final int N = records.size();
- for (int i = 0; i < N; i++) {
- UpdateRecord record = records.get(i);
+ for (UpdateRecord record : records) {
if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mUid))) {
// Sends a notification message to the receiver
if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
if (deadReceivers == null) {
- deadReceivers = new ArrayList<Receiver>();
+ deadReceivers = new ArrayList<>();
}
deadReceivers.add(record.mReceiver);
}
@@ -1410,6 +1466,12 @@
WorkSource worksource = new WorkSource();
ProviderRequest providerRequest = new ProviderRequest();
+ ContentResolver resolver = mContext.getContentResolver();
+ long backgroundThrottleInterval = Settings.Global.getLong(
+ resolver,
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+ DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+
if (records != null) {
for (UpdateRecord record : records) {
if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mUid))) {
@@ -1419,10 +1481,22 @@
record.mReceiver.mPackageName,
record.mReceiver.mAllowedResolutionLevel)) {
LocationRequest locationRequest = record.mRequest;
+ long interval = locationRequest.getInterval();
+
+ if (!isThrottlingExemptLocked(record.mReceiver)) {
+ if (!record.mIsForegroundUid) {
+ interval = Math.max(interval, backgroundThrottleInterval);
+ }
+ if (interval != locationRequest.getInterval()) {
+ locationRequest = new LocationRequest(locationRequest);
+ locationRequest.setInterval(interval);
+ }
+ }
+
providerRequest.locationRequests.add(locationRequest);
- if (locationRequest.getInterval() < providerRequest.interval) {
+ if (interval < providerRequest.interval) {
providerRequest.reportLocation = true;
- providerRequest.interval = locationRequest.getInterval();
+ providerRequest.interval = interval;
}
}
}
@@ -1468,10 +1542,15 @@
p.setRequest(providerRequest, worksource);
}
+ private boolean isThrottlingExemptLocked(Receiver recevier) {
+ return isOverlayProviderPackageLocked(recevier.mPackageName);
+ }
+
private class UpdateRecord {
final String mProvider;
final LocationRequest mRequest;
final Receiver mReceiver;
+ boolean mIsForegroundUid;
Location mLastFixBroadcast;
long mLastStatusBroadcast;
@@ -1482,10 +1561,12 @@
mProvider = provider;
mRequest = request;
mReceiver = receiver;
+ mIsForegroundUid = isImportanceForeground(
+ mActivityManager.getPackageImportance(mReceiver.mPackageName));
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
if (records == null) {
- records = new ArrayList<UpdateRecord>();
+ records = new ArrayList<>();
mRecordsByProvider.put(provider, records);
}
if (!records.contains(this)) {
@@ -1517,7 +1598,7 @@
receiverRecords.remove(this.mProvider);
// and also remove the Receiver if it has no more update records
- if (removeReceiver && receiverRecords.size() == 0) {
+ if (receiverRecords.size() == 0) {
removeUpdatesLocked(mReceiver);
}
}
@@ -1525,14 +1606,9 @@
@Override
public String toString() {
- StringBuilder s = new StringBuilder();
- s.append("UpdateRecord[");
- s.append(mProvider);
- s.append(' ').append(mReceiver.mPackageName).append('(');
- s.append(mReceiver.mUid).append(')');
- s.append(' ').append(mRequest);
- s.append(']');
- return s.toString();
+ return "UpdateRecord[" + mProvider + " " + mReceiver.mPackageName
+ + "(" + mReceiver.mUid + (mIsForegroundUid ? " foreground" : " background")
+ + ")" + " " + mRequest + "]";
}
}
@@ -1681,14 +1757,17 @@
throw new IllegalArgumentException("provider name must not be null");
}
- if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
- + " " + name + " " + request + " from " + packageName + "(" + uid + ")");
LocationProviderInterface provider = mProvidersByName.get(name);
if (provider == null) {
throw new IllegalArgumentException("provider doesn't exist: " + name);
}
UpdateRecord record = new UpdateRecord(name, request, receiver);
+ if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
+ + " " + name + " " + request + " from " + packageName + "(" + uid + " "
+ + (record.mIsForegroundUid ? "foreground" : "background")
+ + (isOverlayProviderPackageLocked(receiver.mPackageName) ? " [whitelisted]" : "") + ")");
+
UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
if (oldRecord != null) {
oldRecord.disposeLocked(false);
@@ -1743,7 +1822,7 @@
receiver.updateMonitoring(false);
// Record which providers were associated with this listener
- HashSet<String> providers = new HashSet<String>();
+ HashSet<String> providers = new HashSet<>();
HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
if (oldRecords != null) {
// Call dispose() on the obsolete update records.
@@ -2084,9 +2163,7 @@
try {
synchronized (mLock) {
LocationProviderInterface p = mProvidersByName.get(provider);
- if (p == null) return false;
-
- return isAllowedByUserSettingsLocked(provider, uid);
+ return p != null && isAllowedByUserSettingsLocked(provider, uid);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2197,11 +2274,7 @@
}
// Check whether the expiry date has passed
- if (record.mRequest.getExpireAt() < now) {
- return false;
- }
-
- return true;
+ return record.mRequest.getExpireAt() >= now;
}
private void handleLocationChangedLocked(Location location, boolean passive) {
@@ -2216,7 +2289,7 @@
// Update last known locations
Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
- Location lastNoGPSLocation = null;
+ Location lastNoGPSLocation;
Location lastLocation = mLastLocation.get(provider);
if (lastLocation == null) {
lastLocation = new Location(provider);
@@ -2296,7 +2369,7 @@
continue;
}
- Location notifyLocation = null;
+ Location notifyLocation;
if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
notifyLocation = coarseLocation; // use coarse location
} else {
@@ -2333,14 +2406,14 @@
// track expired records
if (r.mRequest.getNumUpdates() <= 0 || r.mRequest.getExpireAt() < now) {
if (deadUpdateRecords == null) {
- deadUpdateRecords = new ArrayList<UpdateRecord>();
+ deadUpdateRecords = new ArrayList<>();
}
deadUpdateRecords.add(r);
}
// track dead receivers
if (receiverDead) {
if (deadReceivers == null) {
- deadReceivers = new ArrayList<Receiver>();
+ deadReceivers = new ArrayList<>();
}
if (!deadReceivers.contains(receiver)) {
deadReceivers.add(receiver);
@@ -2417,7 +2490,7 @@
for (Receiver receiver : mReceivers.values()) {
if (receiver.mPackageName.equals(packageName)) {
if (deadReceivers == null) {
- deadReceivers = new ArrayList<Receiver>();
+ deadReceivers = new ArrayList<>();
}
deadReceivers.add(receiver);
}
@@ -2694,6 +2767,13 @@
pw.println(" " + record);
}
}
+ pw.println(" Overlay Provider Packages:");
+ for (LocationProviderInterface provider : mProviders) {
+ if (provider instanceof LocationProviderProxy) {
+ pw.println(" " + provider.getName() + ": "
+ + ((LocationProviderProxy) provider).getConnectedPackageName());
+ }
+ }
pw.println(" Historical Records by Provider:");
for (Map.Entry<PackageProviderKey, PackageStatistics> entry
: mRequestStatistics.statistics.entrySet()) {
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index e8ecc3e..dab4dfb 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -41,6 +41,10 @@
import android.net.RecommendationResult;
import android.net.ScoredNetwork;
import android.net.Uri;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -76,7 +80,9 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
/**
* Backing service for {@link android.net.NetworkScoreManager}.
@@ -391,6 +397,7 @@
isEmpty = callbackList == null
|| callbackList.getRegisteredCallbackCount() == 0;
}
+
if (isEmpty) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "No scorer registered for type " + entry.getKey()
@@ -399,18 +406,10 @@
continue;
}
- sendCallback(new Consumer<INetworkScoreCache>() {
- @Override
- public void accept(INetworkScoreCache networkScoreCache) {
- try {
- networkScoreCache.updateScores(entry.getValue());
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
- }
- }
- }
- }, Collections.singleton(callbackList));
+ final BiConsumer<INetworkScoreCache, Object> consumer =
+ new FilteringCacheUpdatingConsumer(mContext, entry.getValue(),
+ entry.getKey());
+ sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
}
return true;
@@ -419,6 +418,229 @@
}
}
+ /**
+ * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
+ * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
+ * accepted {@link INetworkScoreCache} implementation.
+ */
+ @VisibleForTesting
+ public static class FilteringCacheUpdatingConsumer
+ implements BiConsumer<INetworkScoreCache, Object> {
+ private final Context mContext;
+ private final List<ScoredNetwork> mScoredNetworkList;
+ private final int mNetworkType;
+ // TODO(jjoslin): 1/23/17 - Consider a Map if we implement more filters.
+ private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
+ private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;
+
+ public FilteringCacheUpdatingConsumer(Context context,
+ List<ScoredNetwork> scoredNetworkList, int networkType) {
+ this(context, scoredNetworkList, networkType, null, null);
+ }
+
+ @VisibleForTesting
+ public FilteringCacheUpdatingConsumer(Context context,
+ List<ScoredNetwork> scoredNetworkList, int networkType,
+ Function<List<ScoredNetwork>, List<ScoredNetwork>> currentNetworkFilter,
+ Function<List<ScoredNetwork>, List<ScoredNetwork>> scanResultsFilter) {
+ mContext = context;
+ mScoredNetworkList = scoredNetworkList;
+ mNetworkType = networkType;
+ mCurrentNetworkFilter = currentNetworkFilter;
+ mScanResultsFilter = scanResultsFilter;
+ }
+
+ @Override
+ public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
+ int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
+ if (cookie instanceof Integer) {
+ filterType = (Integer) cookie;
+ }
+
+ try {
+ final List<ScoredNetwork> filteredNetworkList =
+ filterScores(mScoredNetworkList, filterType);
+ if (!filteredNetworkList.isEmpty()) {
+ networkScoreCache.updateScores(
+ Collections.unmodifiableList(filteredNetworkList));
+ }
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
+ }
+ }
+ }
+
+ /**
+ * Applies the appropriate filter and returns the filtered results.
+ */
+ private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
+ int filterType) {
+ switch (filterType) {
+ case NetworkScoreManager.CACHE_FILTER_NONE:
+ return scoredNetworkList;
+
+ case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
+ if (mCurrentNetworkFilter == null) {
+ mCurrentNetworkFilter =
+ new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
+ }
+ return mCurrentNetworkFilter.apply(scoredNetworkList);
+
+ case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
+ if (mScanResultsFilter == null) {
+ mScanResultsFilter = new ScanResultsScoreCacheFilter(
+ new ScanResultsSupplier(mContext));
+ }
+ return mScanResultsFilter.apply(scoredNetworkList);
+
+ default:
+ Log.w(TAG, "Unknown filter type: " + filterType);
+ return scoredNetworkList;
+ }
+ }
+ }
+
+ /**
+ * Helper class that improves the testability of the cache filter Functions.
+ */
+ private static class WifiInfoSupplier implements Supplier<WifiInfo> {
+ private final Context mContext;
+
+ WifiInfoSupplier(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public WifiInfo get() {
+ WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+ if (wifiManager != null) {
+ return wifiManager.getConnectionInfo();
+ }
+ Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
+ return null;
+ }
+ }
+
+ /**
+ * Helper class that improves the testability of the cache filter Functions.
+ */
+ private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
+ private final Context mContext;
+
+ ScanResultsSupplier(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public List<ScanResult> get() {
+ WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
+ if (wifiScanner != null) {
+ return wifiScanner.getSingleScanResults();
+ }
+ Log.w(TAG, "WifiScanner is null, failed to return scan results.");
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
+ * {@link ScoredNetwork} associated with the current network. If no network is connected the
+ * returned list will be empty.
+ * <p>
+ * Note: this filter performs some internal caching for consistency and performance. The
+ * current network is determined at construction time and never changed. Also, the
+ * last filtered list is saved so if the same input is provided multiple times in a row
+ * the computation is only done once.
+ */
+ @VisibleForTesting
+ public static class CurrentNetworkScoreCacheFilter
+ implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
+ private final NetworkKey mCurrentNetwork;
+ private Pair<List<ScoredNetwork>, Integer> mCache;
+
+ CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
+ mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
+ }
+
+ @Override
+ public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
+ if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ final int inputListHash = scoredNetworks.hashCode();
+ if (mCache == null || mCache.second != inputListHash) {
+ ScoredNetwork currentScore = null;
+ for (int i = 0; i < scoredNetworks.size(); i++) {
+ final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+ if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
+ currentScore = scoredNetwork;
+ break;
+ }
+ }
+
+ if (currentScore == null) {
+ mCache = Pair.create(Collections.emptyList(), inputListHash);
+ } else {
+ mCache = Pair.create(Collections.singletonList(currentScore), inputListHash);
+ }
+ }
+
+ return mCache.first;
+ }
+ }
+
+ /**
+ * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
+ * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
+ * If there are no {@link ScanResult}s the returned list will be empty.
+ * <p>
+ * Note: this filter performs some internal caching for consistency and performance. The
+ * current set of ScanResults is determined at construction time and never changed.
+ * Also, the last filtered list is saved so if the same input is provided multiple
+ * times in a row the computation is only done once.
+ */
+ @VisibleForTesting
+ public static class ScanResultsScoreCacheFilter
+ implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
+ private final List<NetworkKey> mScanResultKeys;
+ private Pair<List<ScoredNetwork>, Integer> mCache;
+
+ ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
+ mScanResultKeys = new ArrayList<>();
+ List<ScanResult> scanResults = resultsSupplier.get();
+ for (int i = 0; i < scanResults.size(); i++) {
+ ScanResult scanResult = scanResults.get(i);
+ mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult));
+ }
+ }
+
+ @Override
+ public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
+ if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ final int inputListHash = scoredNetworks.hashCode();
+ if (mCache == null || mCache.second != inputListHash) {
+ List<ScoredNetwork> filteredScores = new ArrayList<>();
+ for (int i = 0; i < scoredNetworks.size(); i++) {
+ final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+ for (int j = 0; j < mScanResultKeys.size(); j++) {
+ final NetworkKey scanResultKey = mScanResultKeys.get(j);
+ if (scanResultKey.equals(scoredNetwork.networkKey)) {
+ filteredScores.add(scoredNetwork);
+ }
+ }
+ }
+ mCache = Pair.create(filteredScores, inputListHash);
+ }
+
+ return mCache.first;
+ }
+ }
+
private boolean isCallerSystemUid() {
// REQUEST_NETWORK_SCORES is a signature only permission.
return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
@@ -499,9 +721,9 @@
/** Clear scores. Callers are responsible for checking permissions as appropriate. */
private void clearInternal() {
- sendCallback(new Consumer<INetworkScoreCache>() {
+ sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
@Override
- public void accept(INetworkScoreCache networkScoreCache) {
+ public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
try {
networkScoreCache.clearScores();
} catch (RemoteException e) {
@@ -675,9 +897,9 @@
}
writer.println("Current scorer: " + currentScorer.packageName);
- sendCallback(new Consumer<INetworkScoreCache>() {
+ sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
@Override
- public void accept(INetworkScoreCache networkScoreCache) {
+ public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
try {
TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
} catch (IOException | RemoteException e) {
@@ -708,14 +930,15 @@
}
}
- private void sendCallback(Consumer<INetworkScoreCache> consumer,
+ private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
final int count = callbackList.beginBroadcast();
try {
for (int i = 0; i < count; i++) {
- consumer.accept(callbackList.getBroadcastItem(i));
+ consumer.accept(callbackList.getBroadcastItem(i),
+ callbackList.getRegisteredCallbackCookie(i));
}
} finally {
callbackList.finishBroadcast();
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index bb8401f..d51e96a 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -19,6 +19,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.os.Build;
import android.os.RecoverySystem;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -45,6 +46,7 @@
public class RescueParty {
private static final String TAG = "RescueParty";
+ private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
private static final String PROP_RESCUE_LEVEL = "sys.rescue_level";
private static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
@@ -55,16 +57,23 @@
private static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
private static final int LEVEL_FACTORY_RESET = 4;
+ private static final boolean DISABLE_RESET_SETTINGS = true;
+
/** Threshold for boot loops */
private static final Threshold sBoot = new BootThreshold();
/** Threshold for app crash loops */
private static SparseArray<Threshold> sApps = new SparseArray<>();
+ private static boolean isDisabled() {
+ return Build.IS_ENG || SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false);
+ }
+
/**
* Take note of a boot event. If we notice too many of these events
* happening in rapid succession, we'll send out a rescue party.
*/
public static void noteBoot(Context context) {
+ if (isDisabled()) return;
if (sBoot.incrementAndTest()) {
sBoot.reset();
incrementRescueLevel(sBoot.uid);
@@ -77,6 +86,7 @@
* events happening in rapid succession, we'll send out a rescue party.
*/
public static void notePersistentAppCrash(Context context, int uid) {
+ if (isDisabled()) return;
Threshold t = sApps.get(uid);
if (t == null) {
t = new AppThreshold(uid);
@@ -151,6 +161,8 @@
}
private static void resetAllSettings(Context context, int mode) throws Exception {
+ if (DISABLE_RESET_SETTINGS) return;
+
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
Exception res = null;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 94acd75..e11dd1a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -33,6 +33,7 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IActivityManager;
+import android.app.usage.StorageStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -3270,6 +3271,29 @@
}
}
+ @Override
+ public long getCacheQuotaBytes(String volumeUuid, int uid) {
+ if (uid != Binder.getCallingUid()) {
+ mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG);
+ }
+ // TODO: wire up to cache quota once merged
+ return 64 * TrafficStats.MB_IN_BYTES;
+ }
+
+ @Override
+ public long getCacheSizeBytes(String volumeUuid, int uid) {
+ if (uid != Binder.getCallingUid()) {
+ mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG);
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mContext.getSystemService(StorageStatsManager.class)
+ .queryStatsForUid(volumeUuid, uid).getCacheBytes();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private void addObbStateLocked(ObbState obbState) throws RemoteException {
final IBinder binder = obbState.getBinder();
List<ObbState> obbStates = mObbMounts.get(binder);
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index d879919..3f97d4f 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -36,7 +36,6 @@
private final Context mContext;
private boolean mSafeMode;
private boolean mRuntimeRestarted;
- private boolean mFirstBoot;
// Services that should receive lifecycle events.
private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
@@ -260,16 +259,6 @@
mRuntimeRestarted = runtimeRestarted;
}
- /**
- * @return true if it's first boot after OTA
- */
- public boolean isFirstBoot() {
- return mFirstBoot;
- }
-
- void setFirstBoot(boolean firstBoot) {
- mFirstBoot = firstBoot;
- }
/**
* Outputs the state of this manager to the System log.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4b89b40..5655dc5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -349,8 +349,8 @@
try {
// Before going further -- if this app is not allowed to start services in the
// background, then at this point we aren't going to let it period.
- final int allowed = mAm.checkAllowBackgroundLocked(
- r.appInfo.uid, r.packageName, callingPid, false);
+ final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
+ r.appInfo.targetSdkVersion, callingPid, false, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service "
+ service + " to " + r.name.flattenToShortString()
@@ -607,8 +607,9 @@
for (int i=services.mServicesByName.size()-1; i>=0; i--) {
ServiceRecord service = services.mServicesByName.valueAt(i);
if (service.appInfo.uid == uid && service.startRequested) {
- if (mAm.checkAllowBackgroundLocked(service.appInfo.uid, service.packageName,
- -1, false) != ActivityManager.APP_START_MODE_NORMAL) {
+ if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,
+ service.appInfo.targetSdkVersion, -1, false, false)
+ != ActivityManager.APP_START_MODE_NORMAL) {
if (stopping == null) {
stopping = new ArrayList<>();
stopping.add(service);
@@ -707,7 +708,7 @@
try {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
- setServiceForegroundInnerLocked(r, userId, notification, flags);
+ setServiceForegroundInnerLocked(r, id, notification, flags);
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8a0baad..a5742c2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
@@ -4936,7 +4937,7 @@
}
@Override
- public void crashApplication(int uid, int initialPid, String packageName,
+ public void crashApplication(int uid, int initialPid, String packageName, int userId,
String message) {
if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
@@ -4949,7 +4950,7 @@
}
synchronized(this) {
- mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, message);
+ mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId, message);
}
}
@@ -7284,18 +7285,23 @@
Slog.d(TAG, "tempWhitelistAppForPowerSave(" + callerPid + ", " + callerUid + ", "
+ targetUid + ", " + duration + ")");
}
- synchronized (mPidsSelfLocked) {
- final ProcessRecord pr = mPidsSelfLocked.get(callerPid);
- if (pr == null) {
- Slog.w(TAG, "tempWhitelistAppForPowerSave() no ProcessRecord for pid " + callerPid);
- return;
- }
- if (!pr.whitelistManager) {
- if (DEBUG_WHITELISTS) {
- Slog.d(TAG, "tempWhitelistAppForPowerSave() for target " + targetUid + ": pid "
- + callerPid + " is not allowed");
+
+ if (checkPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST, callerPid, callerUid)
+ != PackageManager.PERMISSION_GRANTED) {
+ synchronized (mPidsSelfLocked) {
+ final ProcessRecord pr = mPidsSelfLocked.get(callerPid);
+ if (pr == null) {
+ Slog.w(TAG, "tempWhitelistAppForPowerSave() no ProcessRecord for pid "
+ + callerPid);
+ return;
}
- return;
+ if (!pr.whitelistManager) {
+ if (DEBUG_WHITELISTS) {
+ Slog.d(TAG, "tempWhitelistAppForPowerSave() for target " + targetUid
+ + ": pid " + callerPid + " is not allowed");
+ }
+ return;
+ }
}
}
@@ -7619,12 +7625,11 @@
final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
- ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
- aspectRatio)
+ ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, aspectRatio)
: mWindowManager.getPictureInPictureDefaultBounds(DEFAULT_DISPLAY);
mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
bounds, true /* moveHomeStackToFront */);
- mWindowManager.setPictureInPictureActions(actions);
+ mStackSupervisor.getStack(PINNED_STACK_ID).setPictureInPictureActions(actions);
};
if (isKeyguardLocked()) {
@@ -7672,12 +7677,11 @@
// Only update the saved args from the args that are set
r.pictureInPictureArgs.copyOnlySet(args);
- if (r.getStack().getStackId() == PINNED_STACK_ID) {
+ final ActivityStack stack = r.getStack();
+ if (stack.getStackId() == PINNED_STACK_ID) {
// If the activity is already in picture-in-picture, update the pinned stack now
- mWindowManager.setPictureInPictureAspectRatio(
- r.pictureInPictureArgs.getAspectRatio());
- mWindowManager.setPictureInPictureActions(
- r.pictureInPictureArgs.getActions());
+ stack.setPictureInPictureAspectRatio(r.pictureInPictureArgs.getAspectRatio());
+ stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions());
}
}
} finally {
@@ -8020,65 +8024,42 @@
return readMet && writeMet;
}
- public int getAppStartMode(int uid, String packageName) {
+ public boolean isAppStartModeDisabled(int uid, String packageName) {
synchronized (this) {
- return checkAllowBackgroundLocked(uid, packageName, -1, false);
+ return getAppStartModeLocked(uid, packageName, 0, -1, false, true)
+ == ActivityManager.APP_START_MODE_DISABLED;
}
}
// Unified app-op and target sdk check
- int appRestrictedInBackgroundLocked(int uid, String packageName) {
- if (packageName == null) {
- packageName = mPackageManagerInt.getNameForUid(uid);
- if (packageName == null) {
- Slog.w(TAG, "No package known for uid " + uid);
- return ActivityManager.APP_START_MODE_NORMAL;
- }
- }
-
- // !!! TODO: cache the package/versionCode lookups to fast path this
- ApplicationInfo app = getPackageManagerInternalLocked().getApplicationInfo(packageName,
- UserHandle.getUserId(uid));
- if (app != null) {
- // Apps that target O+ are always subject to background check
- if (mEnforceBackgroundCheck && app.targetSdkVersion >= Build.VERSION_CODES.O) {
- if (DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
- }
- return ActivityManager.APP_START_MODE_DELAYED_RIGID;
- }
- // ...and legacy apps get an AppOp check
- int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
- uid, packageName);
+ int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
+ // Apps that target O+ are always subject to background check
+ if (mEnforceBackgroundCheck && packageTargetSdk >= Build.VERSION_CODES.O) {
if (DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);
+ Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
}
- switch (appop) {
- case AppOpsManager.MODE_ALLOWED:
- return ActivityManager.APP_START_MODE_NORMAL;
- case AppOpsManager.MODE_IGNORED:
- return ActivityManager.APP_START_MODE_DELAYED;
- default:
- return ActivityManager.APP_START_MODE_DELAYED_RIGID;
- }
- } else {
- Slog.w(TAG, "Unknown app " + packageName + " / " + uid);
+ return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
- return ActivityManager.APP_START_MODE_NORMAL;
+ // ...and legacy apps get an AppOp check
+ int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
+ uid, packageName);
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);
+ }
+ switch (appop) {
+ case AppOpsManager.MODE_ALLOWED:
+ return ActivityManager.APP_START_MODE_NORMAL;
+ case AppOpsManager.MODE_IGNORED:
+ return ActivityManager.APP_START_MODE_DELAYED;
+ default:
+ return ActivityManager.APP_START_MODE_DELAYED_RIGID;
+ }
}
// Service launch is available to apps with run-in-background exemptions but
// some other background operations are not. If we're doing a check
// of service-launch policy, allow those callers to proceed unrestricted.
- int appServicesRestrictedInBackgroundLocked(int uid, String packageName) {
- if (packageName == null) {
- packageName = mPackageManagerInt.getNameForUid(uid);
- if (packageName == null) {
- Slog.w(TAG, "No package known for uid " + uid);
- return ActivityManager.APP_START_MODE_NORMAL;
- }
- }
-
+ int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// Persistent app? NB: expects that persistent uids are always active.
final UidRecord uidRec = mActiveUids.get(uid);
if (uidRec != null && uidRec.persistent) {
@@ -8108,11 +8089,11 @@
}
// None of the service-policy criteria apply, so we apply the common criteria
- return appRestrictedInBackgroundLocked(uid, packageName);
+ return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
- int checkAllowBackgroundLocked(int uid, String packageName, int callingPid,
- boolean alwaysRestrict) {
+ int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
+ int callingPid, boolean alwaysRestrict, boolean disabledOnly) {
UidRecord uidRec = mActiveUids.get(uid);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="
+ packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
@@ -8130,27 +8111,37 @@
// We are hard-core about ephemeral apps not running in the background.
return ActivityManager.APP_START_MODE_DISABLED;
} else {
- /** Don't want to allow this exception in the final background check impl?
- if (callingPid >= 0) {
- ProcessRecord proc;
- synchronized (mPidsSelfLocked) {
- proc = mPidsSelfLocked.get(callingPid);
- }
- if (proc != null && proc.curProcState
- < ActivityManager.PROCESS_STATE_RECEIVER) {
- // Whoever is instigating this is in the foreground, so we will allow it
- // to go through.
- return ActivityManager.APP_START_MODE_NORMAL;
- }
+ if (disabledOnly) {
+ // The caller is only interested in whether app starts are completely
+ // disabled for the given package (that is, it is an instant app). So
+ // we don't need to go further, which is all just seeing if we should
+ // apply a "delayed" mode for a regular app.
+ return ActivityManager.APP_START_MODE_NORMAL;
}
- */
-
final int startMode = (alwaysRestrict)
- ? appRestrictedInBackgroundLocked(uid, packageName)
- : appServicesRestrictedInBackgroundLocked(uid, packageName);
+ ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
+ : appServicesRestrictedInBackgroundLocked(uid, packageName,
+ packageTargetSdk);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid
+ " pkg=" + packageName + " startMode=" + startMode
+ " onwhitelist=" + isOnDeviceIdleWhitelistLocked(uid));
+ if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
+ // This is an old app that has been forced into a "compatible as possible"
+ // mode of background check. To increase compatibility, we will allow other
+ // foreground apps to cause its services to start.
+ if (callingPid >= 0) {
+ ProcessRecord proc;
+ synchronized (mPidsSelfLocked) {
+ proc = mPidsSelfLocked.get(callingPid);
+ }
+ if (proc != null && proc.curProcState
+ < ActivityManager.PROCESS_STATE_RECEIVER) {
+ // Whoever is instigating this is in the foreground, so we will allow it
+ // to go through.
+ return ActivityManager.APP_START_MODE_NORMAL;
+ }
+ }
+ }
return startMode;
}
}
@@ -9348,7 +9339,7 @@
if (tr.mBounds != null) {
rti.bounds = new Rect(tr.mBounds);
}
- rti.isDockable = tr.canGoInDockedStack();
+ rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreen();
rti.resizeMode = tr.mResizeMode;
ActivityRecord base = null;
@@ -10228,7 +10219,9 @@
synchronized (this) {
if (animate) {
if (stackId == PINNED_STACK_ID) {
- mWindowManager.animateResizePinnedStack(bounds, animationDuration);
+ final ActivityStack pinnedStack =
+ mStackSupervisor.getStack(PINNED_STACK_ID);
+ pinnedStack.animateResizePinnedStack(bounds, animationDuration);
} else {
throw new IllegalArgumentException("Stack: " + stackId
+ " doesn't support animated resize.");
@@ -12051,14 +12044,12 @@
final long ident = Binder.clearCallingIdentity();
try {
if (mUserController.shouldConfirmCredentials(userId)) {
- final int currentUserId = mUserController.getCurrentUserIdLocked();
- if (!mKeyguardController.isKeyguardLocked()) {
- // If the device is not locked, we will prompt for credentials immediately.
- mStackSupervisor.lockAllProfileTasks(userId);
- } else {
+ if (mKeyguardController.isKeyguardLocked()) {
// Showing launcher to avoid user entering credential twice.
+ final int currentUserId = mUserController.getCurrentUserIdLocked();
startHomeActivityLocked(currentUserId, "notifyLockedProfile");
}
+ mStackSupervisor.lockAllProfileTasks(userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -13411,13 +13402,12 @@
if (supportsMultiWindow || forceResizable) {
mSupportsMultiWindow = true;
mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
- mSupportsPictureInPicture = supportsPictureInPicture || forceResizable;
} else {
mSupportsMultiWindow = false;
mSupportsFreeformWindowManagement = false;
- mSupportsPictureInPicture = false;
}
mSupportsSplitScreenMultiWindow = supportsSplitScreenMultiWindow;
+ mSupportsPictureInPicture = supportsPictureInPicture;
mWindowManager.setForceResizableTasks(mForceResizableActivities);
mWindowManager.setSupportsPictureInPicture(mSupportsPictureInPicture);
// This happens before any activities are started, so we can change global configuration
@@ -17763,6 +17753,7 @@
// Not backing this app up any more; reset its OOM adjustment
final ProcessRecord proc = mBackupTarget.app;
updateOomAdjLocked(proc);
+ proc.inFullBackup = false;
// If the app crashed during backup, 'thread' will be null here
if (proc.thread != null) {
@@ -19601,7 +19592,7 @@
/** Helper method that requests bounds from WM and applies them to stack. */
private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) {
- final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId);
+ final Rect newBounds = mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration();
mStackSupervisor.resizeStackLocked(
stackId, newBounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);
@@ -19622,7 +19613,8 @@
&& config.navigation == Configuration.NAVIGATION_NONAV);
int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR
- && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE)));
+ && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE))
+ && modeType != Configuration.UI_MODE_TYPE_TELEVISION);
return inputMethodExists && uiModeSupportsDialogs && !inVrMode;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 202868a..1a2a31b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -167,6 +167,8 @@
return runBugReport(pw);
case "force-stop":
return runForceStop(pw);
+ case "crash":
+ return runCrash(pw);
case "kill":
return runKill(pw);
case "kill-all":
@@ -851,6 +853,32 @@
return 0;
}
+ int runCrash(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+
+ int pid = -1;
+ String packageName = null;
+ final String arg = getNextArgRequired();
+ // The argument is either a pid or a package name
+ try {
+ pid = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ packageName = arg;
+ }
+ mInterface.crashApplication(-1, pid, packageName, userId, "shell-induced crash");
+ return 0;
+ }
+
int runKill(PrintWriter pw) throws RemoteException {
int userId = UserHandle.USER_ALL;
@@ -2480,6 +2508,8 @@
pw.println(" --telephony: will dump only telephony sections.");
pw.println(" force-stop [--user <USER_ID> | all | current] <PACKAGE>");
pw.println(" Completely stop the given application package.");
+ pw.println(" crash [--user <USER_ID>] <PACKAGE|PID>");
+ pw.println(" Induce a VM crash in the specified package or process");
pw.println(" kill [--user <USER_ID> | all | current] <PACKAGE>");
pw.println(" Kill all processes associated with the given application.");
pw.println(" kill-all");
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index e46d204..65b8554 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -3,9 +3,7 @@
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.ActivityManager.StackId.RECENTS_STACK_ID;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -17,7 +15,7 @@
import android.os.SystemClock;
import android.util.Slog;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -172,7 +170,7 @@
MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS,
(int) (SystemClock.uptimeMillis() / 1000));
- LogBuilder builder = new LogBuilder(MetricsEvent.APP_TRANSITION);
+ LogMaker builder = new LogMaker(MetricsEvent.APP_TRANSITION);
builder.addTaggedData(MetricsEvent.APP_TRANSITION_COMPONENT_NAME, componentName);
builder.addTaggedData(MetricsEvent.APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0);
builder.addTaggedData(MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS,
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index a968e0b..2a849b6 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -22,6 +22,8 @@
import static android.app.ActivityManager.StackId.FREEFORM_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;
+import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
@@ -37,7 +39,6 @@
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
@@ -450,6 +451,7 @@
}
if (info != null) {
pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
+ pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
}
pw.println(prefix + "supportsPictureInPictureWhilePausing: "
+ supportsPictureInPictureWhilePausing);
@@ -875,24 +877,50 @@
}
boolean isResizeable() {
- return ActivityInfo.isResizeableMode(info.resizeMode);
+ return ActivityInfo.isResizeableMode(info.resizeMode) || info.supportsPictureInPicture();
}
- boolean isResizeableOrForced() {
- return !isHomeActivity() && (isResizeable() || service.mForceResizableActivities);
- }
-
- boolean isNonResizableOrForced() {
+ /**
+ * @return whether this activity is non-resizeable or forced to be resizeable
+ */
+ boolean isNonResizableOrForcedResizable() {
return info.resizeMode != RESIZE_MODE_RESIZEABLE
- && info.resizeMode != RESIZE_MODE_RESIZEABLE_AND_PIPABLE
&& info.resizeMode != RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
}
/**
- * @return whether this activity's resize mode supports PIP.
+ * @return whether this activity supports PiP multi-window and can be put in the pinned stack.
*/
boolean supportsPictureInPicture() {
- return !isHomeActivity() && info.resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+ return service.mSupportsPictureInPicture && !isHomeActivity()
+ && info.supportsPictureInPicture();
+ }
+
+ /**
+ * @return whether this activity supports split-screen multi-window and can be put in the docked
+ * stack.
+ */
+ boolean supportsSplitScreen() {
+ // An activity can not be docked even if it is considered resizeable because it only
+ // supports picture-in-picture mode but has a non-resizeable resizeMode
+ return service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
+ }
+
+ /**
+ * @return whether this activity supports freeform multi-window and can be put in the freeform
+ * stack.
+ */
+ boolean supportsFreeform() {
+ return service.mSupportsFreeformWindowManagement && supportsResizeableMultiWindow();
+ }
+
+ /**
+ * @return whether this activity supports non-PiP multi-window.
+ */
+ private boolean supportsResizeableMultiWindow() {
+ return service.mSupportsMultiWindow && !isHomeActivity()
+ && (ActivityInfo.isResizeableMode(info.resizeMode)
+ || service.mForceResizableActivities);
}
/**
@@ -911,13 +939,15 @@
case PAUSED:
// When pausing, only allow enter PiP if not on the lockscreen and there is not
// already an existing PiP activity
- return !isKeyguardLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing;
+ return !isKeyguardLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing
+ && checkEnterPictureInPictureOnHideAppOpsState();
case STOPPING:
// When stopping in a valid state, then only allow enter PiP as in the pause state.
// Otherwise, fall through to throw an exception if the caller is trying to enter
// PiP in an invalid stopping state.
if (supportsPictureInPictureWhilePausing) {
- return !isKeyguardLocked && !hasPinnedStack;
+ return !isKeyguardLocked && !hasPinnedStack
+ && checkEnterPictureInPictureOnHideAppOpsState();
}
default:
throw new IllegalStateException(caller
@@ -926,8 +956,17 @@
}
}
- boolean canGoInDockedStack() {
- return !isHomeActivity() && isResizeableOrForced();
+ /**
+ * @return Whether AppOps allows this package to enter picture-in-picture when it is hidden.
+ */
+ private boolean checkEnterPictureInPictureOnHideAppOpsState() {
+ try {
+ return service.getAppOpsService().checkOperation(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+ appInfo.uid, packageName) == MODE_ALLOWED;
+ } catch (RemoteException e) {
+ // Local call
+ }
+ return false;
}
boolean isAlwaysFocusable() {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index d7b3728e..973951e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -62,7 +62,6 @@
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
-import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
@@ -73,7 +72,6 @@
import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
import static java.lang.Integer.MAX_VALUE;
-import static java.lang.Integer.MIN_VALUE;
import android.app.Activity;
import android.app.ActivityManager;
@@ -82,6 +80,7 @@
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityController;
+import android.app.RemoteAction;
import android.app.ResultInfo;
import android.content.ComponentName;
import android.content.Intent;
@@ -116,6 +115,8 @@
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
+import com.android.server.wm.StackWindowController;
+import com.android.server.wm.StackWindowListener;
import com.android.server.wm.WindowManagerService;
import java.io.FileDescriptor;
@@ -130,7 +131,7 @@
/**
* State and management of a single stack of activities.
*/
-final class ActivityStack extends ConfigurationContainer {
+final class ActivityStack extends ConfigurationContainer implements StackWindowListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
@@ -242,6 +243,7 @@
final ActivityManagerService mService;
private final WindowManagerService mWindowManager;
+ private StackWindowController mWindowContainerController;
private final RecentTasks mRecentTasks;
/**
@@ -330,7 +332,7 @@
private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
- private final Rect tempRect2 = new Rect();
+ private final Rect mTmpRect2 = new Rect();
/** Run all ActivityStacks through this */
private final ActivityStackSupervisor mStackSupervisor;
@@ -443,7 +445,7 @@
}
ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
- RecentTasks recentTasks) {
+ RecentTasks recentTasks, boolean onTop) {
mActivityContainer = activityContainer;
mStackSupervisor = activityContainer.getOuter();
mService = mStackSupervisor.mService;
@@ -454,13 +456,25 @@
mRecentTasks = recentTasks;
mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
? new LaunchingTaskPositioner() : null;
+ final ActivityStackSupervisor.ActivityDisplay display = mActivityContainer.mActivityDisplay;
+ mTmpRect2.setEmpty();
+ mWindowContainerController = new StackWindowController(mStackId, this,
+ display.mDisplayId, onTop, mTmpRect2);
+ activityContainer.mStack = this;
+ mStackSupervisor.mActivityContainers.put(mStackId, activityContainer);
+ postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
+ }
+
+ StackWindowController getWindowContainerController() {
+ return mWindowContainerController;
}
/** Adds the stack to specified display and calls WindowManager to do the same. */
- void addToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
- final Rect bounds = mWindowManager.addStackToDisplay(mStackId, activityDisplay.mDisplayId,
- onTop);
- postAddToDisplay(activityDisplay, bounds);
+ void reparent(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+ removeFromDisplay();
+ mTmpRect2.setEmpty();
+ mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2);
+ postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
}
/**
@@ -469,10 +483,10 @@
* @param bounds Updated bounds.
*/
private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
- Rect bounds) {
+ Rect bounds, boolean onTop) {
mDisplayId = activityDisplay.mDisplayId;
mStacks = activityDisplay.mStacks;
- mBounds = bounds;
+ mBounds = bounds != null ? new Rect(bounds) : null;
mFullscreen = mBounds == null;
if (mTaskPositioner != null) {
mTaskPositioner.setDisplay(activityDisplay.mDisplay);
@@ -480,6 +494,7 @@
}
onParentChanged();
+ activityDisplay.attachStack(this, onTop);
if (mStackId == DOCKED_STACK_ID) {
// If we created a docked stack we want to resize it so it resizes all other stacks
// in the system.
@@ -489,16 +504,6 @@
}
/**
- * Moves the stack to specified display.
- * @param activityDisplay Target display to move the stack to.
- */
- void moveToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay) {
- removeFromDisplay();
- final Rect bounds = mWindowManager.moveStackToDisplay(mStackId, activityDisplay.mDisplayId);
- postAddToDisplay(activityDisplay, bounds);
- }
-
- /**
* Updates the inner state of the stack to remove it from its current parent, so it can be
* either destroyed completely or re-parented.
*/
@@ -520,7 +525,8 @@
void remove() {
removeFromDisplay();
mStackSupervisor.deleteActivityContainerRecord(mStackId);
- mWindowManager.removeStack(mStackId);
+ mWindowContainerController.removeContainer();
+ mWindowContainerController = null;
onParentChanged();
}
@@ -528,6 +534,42 @@
mActivityContainer.mActivityDisplay.mDisplay.getSize(out);
}
+ void animateResizePinnedStack(Rect bounds, int animationDuration) {
+ mWindowContainerController.animateResizePinnedStack(bounds, animationDuration);
+ }
+
+ void setPictureInPictureAspectRatio(float aspectRatio) {
+ mWindowContainerController.setPictureInPictureAspectRatio(aspectRatio);
+ }
+
+ void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) {
+ mWindowContainerController.getStackDockedModeBounds(outBounds, ignoreVisibility);
+ }
+
+ void prepareFreezingTaskBounds() {
+ mWindowContainerController.prepareFreezingTaskBounds();
+ }
+
+ void setPictureInPictureActions(List<RemoteAction> actions) {
+ mWindowContainerController.setPictureInPictureActions(actions);
+ }
+
+ void getWindowContainerBounds(Rect outBounds) {
+ if (mWindowContainerController != null) {
+ mWindowContainerController.getBounds(outBounds);
+ }
+ outBounds.setEmpty();
+ }
+
+ Rect getBoundsForNewConfiguration() {
+ return mWindowContainerController.getBoundsForNewConfiguration();
+ }
+
+ void positionChildWindowContainerAtTop(TaskRecord child) {
+ mWindowContainerController.positionChildAtTop(child.getWindowContainerController(),
+ true /* includingParents */);
+ }
+
/**
* Defers updating the bounds of the stack. If the stack was resized/repositioned while
* deferring, the bounds will update in {@link #continueUpdateBounds()}.
@@ -548,8 +590,7 @@
final boolean wasDeferred = mUpdateBoundsDeferred;
mUpdateBoundsDeferred = false;
if (wasDeferred && mUpdateBoundsDeferredCalled) {
- mStackSupervisor.resizeStackUncheckedLocked(this,
- mDeferredBounds.isEmpty() ? null : mDeferredBounds,
+ resize(mDeferredBounds.isEmpty() ? null : mDeferredBounds,
mDeferredTaskBounds.isEmpty() ? null : mDeferredTaskBounds,
mDeferredTaskInsetBounds.isEmpty() ? null : mDeferredTaskInsetBounds);
}
@@ -767,7 +808,8 @@
task = topTask();
if (task != null) {
- task.moveWindowContainerToTop(true /* includingParents */);
+ mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+ true /* includingParents */);
}
}
@@ -786,7 +828,7 @@
mTaskHistory.remove(task);
mTaskHistory.add(0, task);
updateTaskMovement(task, false);
- task.moveWindowContainerToBottom();
+ mWindowContainerController.positionChildAtBottom(task.getWindowContainerController());
}
}
@@ -1545,7 +1587,7 @@
// home task even though it's not resizable.
final ActivityRecord r = focusedStack.topRunningActivityLocked();
final TaskRecord task = r != null ? r.task : null;
- return task == null || task.canGoInDockedStack() || task.isHomeTask() ? STACK_VISIBLE
+ return task == null || task.supportsSplitScreen() || task.isHomeTask() ? STACK_VISIBLE
: STACK_INVISIBLE;
}
@@ -1795,6 +1837,12 @@
}
}
+ void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
+ for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+ mTaskHistory.get(taskNdx).addStartingWindowsForVisibleActivities(taskSwitch);
+ }
+ }
+
/**
* @return true if the top visible activity wants to occlude the Keyguard, false otherwise
*/
@@ -2574,7 +2622,8 @@
position = getAdjustedPositionForTask(task, position, null /* starting */);
mTaskHistory.remove(task);
mTaskHistory.add(position, task);
- task.positionWindowContainerAt(position);
+ mWindowContainerController.positionChildAt(task.getWindowContainerController(), position,
+ task.mBounds, task.getOverrideConfiguration());
updateTaskMovement(task, true);
}
@@ -2586,7 +2635,8 @@
final int position = getAdjustedPositionForTask(task, mTaskHistory.size(), starting);
mTaskHistory.add(position, task);
updateTaskMovement(task, true);
- task.moveWindowContainerToTop(true /* includingParents */);
+ mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+ true /* includingParents */);
}
private void updateTaskReturnToForTopInsertion(TaskRecord task) {
@@ -2614,7 +2664,7 @@
// This also makes sure that non-home activities are visible under a transparent
// non-home activity.
task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
- } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask != task)) {
+ } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) {
// If it's a last task over home - we default to keep its return to type not to
// make underlying task focused when this one will be finished.
int returnToType = isLastTaskOverHome
@@ -2864,7 +2914,8 @@
targetTask.addActivityAtBottom(p);
}
- targetTask.moveWindowContainerToBottom();
+ mWindowContainerController.positionChildAtBottom(
+ targetTask.getWindowContainerController());
replyChainEnd = -1;
} else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
// If the activity should just be removed -- either
@@ -3000,7 +3051,8 @@
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Pulling activity " + p
+ " from " + srcPos + " in to resetting task " + task);
}
- task.moveWindowContainerToTop(true /* includingParents */);
+ mWindowContainerController.positionChildAtTop(
+ task.getWindowContainerController(), true /* includingParents */);
// Now we've moved it in to place... but what if this is
// a singleTop activity and we have put it on top of another
@@ -4369,7 +4421,7 @@
}
mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
- tr.moveWindowContainerToBottom();
+ mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController());
final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) {
@@ -4432,11 +4484,24 @@
}
}
- /** Update override configurations of all tasks in the stack. */
- void updateOverrideConfiguration(Rect stackBounds, Rect tempTaskBounds,
- Rect tempTaskInsetBounds) {
+ // TODO: Figure-out a way to consolidate with resize() method below.
+ @Override
+ public void requestResize(Rect bounds) {
+ mService.resizeStack(mStackId, bounds, true /* allowResizeInDockedMode */,
+ false /* preserveWindows */, false /* animate */, -1 /* animationDuration */);
+ }
- final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : stackBounds;
+ // TODO: Can only be called from special methods in ActivityStackSupervisor.
+ // Need to consolidate those calls points into this resize method so anyone can call directly.
+ void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
+ bounds = TaskRecord.validateBounds(bounds);
+
+ if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
+ return;
+ }
+
+ // Update override configurations of all tasks in the stack.
+ final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
mTmpBounds.clear();
@@ -4450,9 +4515,9 @@
// For freeform stack we don't adjust the size of the tasks to match that
// of the stack, but we do try to make sure the tasks are still contained
// with the bounds of the stack.
- tempRect2.set(task.mBounds);
- fitWithinBounds(tempRect2, stackBounds);
- task.updateOverrideConfiguration(tempRect2);
+ mTmpRect2.set(task.mBounds);
+ fitWithinBounds(mTmpRect2, bounds);
+ task.updateOverrideConfiguration(mTmpRect2);
} else {
task.updateOverrideConfiguration(taskBounds, insetBounds);
}
@@ -4465,11 +4530,9 @@
}
}
- // We might trigger a configuration change. Save the current task bounds for freezing.
- mWindowManager.prepareFreezingTaskBounds(mStackId);
- mFullscreen = mWindowManager.resizeStack(mStackId, stackBounds, mTmpConfigs, mTmpBounds,
+ mFullscreen = mWindowContainerController.resize(bounds, mTmpConfigs, mTmpBounds,
mTmpInsetBounds);
- setBounds(stackBounds);
+ setBounds(bounds);
}
@@ -4659,7 +4722,7 @@
}
ci.numActivities = numActivities;
ci.numRunning = numRunning;
- ci.isDockable = task.canGoInDockedStack();
+ ci.supportsSplitScreenMultiWindow = task.supportsSplitScreen();
ci.resizeMode = task.mResizeMode;
list.add(ci);
}
@@ -4893,7 +4956,8 @@
addTask(task, toTop ? MAX_VALUE : 0, reason);
if (toTop) {
// TODO: figure-out a way to remove this call.
- task.moveWindowContainerToTop(true /* includingParents */);
+ mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+ true /* includingParents */);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 14899b4..29032f8 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -90,7 +90,6 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
import static java.lang.Integer.MAX_VALUE;
-import static java.lang.Integer.MIN_VALUE;
import android.Manifest;
import android.annotation.NonNull;
@@ -377,7 +376,7 @@
// TODO: Add listener for removal of references.
/** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
- private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
+ SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
/** Mapping from displayId to display current state */
private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
@@ -2199,7 +2198,7 @@
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
mWindowManager.deferSurfaceLayout();
try {
- resizeStackUncheckedLocked(stack, bounds, tempTaskBounds, tempTaskInsetBounds);
+ stack.resize(bounds, tempTaskBounds, tempTaskInsetBounds);
if (!deferResume) {
stack.ensureVisibleActivitiesConfigurationLocked(
stack.topRunningActivityLocked(), preserveWindows);
@@ -2237,17 +2236,6 @@
mResizingTasksDuringAnimation.clear();
}
- void resizeStackUncheckedLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
- Rect tempTaskInsetBounds) {
- bounds = TaskRecord.validateBounds(bounds);
-
- if (!stack.updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
- return;
- }
-
- stack.updateOverrideConfiguration(bounds, tempTaskBounds, tempTaskInsetBounds);
- }
-
void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
final ActivityStack stack = getStack(fromStackId);
if (stack == null) {
@@ -2341,8 +2329,7 @@
// Don't allow re-entry while resizing. E.g. due to docked stack detaching.
mAllowDockedStackResize = false;
ActivityRecord r = stack.topRunningActivityLocked();
- resizeStackUncheckedLocked(stack, dockedBounds, tempDockedTaskBounds,
- tempDockedTaskInsetBounds);
+ stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds);
// TODO: Checking for isAttached might not be needed as if the user passes in null
// dockedBounds then they want the docked stack to be dismissed.
@@ -2359,10 +2346,10 @@
// static stacks need to be adjusted so they don't overlap with the docked stack.
// We get the bounds to use from window manager which has been adjusted for any
// screen controls and is also the same for all stacks.
- mWindowManager.getStackDockedModeBounds(
- HOME_STACK_ID, tempRect, true /* ignoreVisibility */);
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
+ final ActivityStack current = getStack(i);
+ if (current != null && StackId.isResizeableByDockedStack(i)) {
+ current.getStackDockedModeBounds(tempRect, true /* ignoreVisibility */);
resizeStackLocked(i, tempRect, tempOtherTaskBounds,
tempOtherTaskInsetBounds, preserveWindows,
true /* allowResizeInDockedMode */, deferResume);
@@ -2395,8 +2382,7 @@
mWindowManager.deferSurfaceLayout();
try {
ActivityRecord r = stack.topRunningActivityLocked();
- resizeStackUncheckedLocked(stack, pinnedBounds, tempPinnedTaskBounds,
- null);
+ stack.resize(pinnedBounds, tempPinnedTaskBounds, null);
stack.ensureVisibleActivitiesConfigurationLocked(r, false);
} finally {
mWindowManager.continueSurfaceLayout();
@@ -2405,14 +2391,13 @@
}
ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
- ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay == null) {
return null;
}
- ActivityContainer activityContainer = new ActivityContainer(stackId);
- mActivityContainers.put(stackId, activityContainer);
- activityContainer.addToDisplayLocked(activityDisplay, onTop);
+ final ActivityContainer activityContainer =
+ new ActivityContainer(stackId, activityDisplay, onTop);
return activityContainer.mStack;
}
@@ -2576,7 +2561,7 @@
// This means that tasks that were on external displays will be restored on the
// primary display.
stackId = task.getLaunchStackId();
- } else if (stackId == DOCKED_STACK_ID && !task.canGoInDockedStack()) {
+ } else if (stackId == DOCKED_STACK_ID && !task.supportsSplitScreen()) {
// Preferred stack is the docked stack, but the task can't go in the docked stack.
// Put it in the fullscreen stack.
stackId = FULLSCREEN_WORKSPACE_STACK_ID;
@@ -2771,7 +2756,8 @@
}
// We might trigger a configuration change. Save the current task bounds for freezing.
- mWindowManager.prepareFreezingTaskBounds(stack.mStackId);
+ // TODO: Should this call be moved inside the resize method in WM?
+ stack.prepareFreezingTaskBounds();
// Make sure the task has the appropriate bounds/size for the stack it is in.
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
@@ -2840,7 +2826,11 @@
void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds,
boolean moveHomeStackToFront) {
+
mWindowManager.deferSurfaceLayout();
+ // Need to make sure the pinned stack exist so we can resize it below...
+ final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+
try {
final TaskRecord task = r.task;
@@ -2850,9 +2840,6 @@
requestVisibleBehindLocked(r, false);
}
- // Need to make sure the pinned stack exist so we can resize it below...
- final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-
// Resize the pinned stack to match the current size of the task the activity we are
// going to be moving is currently contained in. We do this to have the right starting
// animation bounds for the pinned stack to the desired bounds the caller wants.
@@ -2892,7 +2879,7 @@
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
- mWindowManager.animateResizePinnedStack(bounds, -1);
+ stack.animateResizePinnedStack(bounds, -1);
mService.mTaskChangeNotificationController.notifyActivityPinned();
}
@@ -3229,6 +3216,17 @@
}
}
+ void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
+ for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+ final int topStackNdx = stacks.size() - 1;
+ for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = stacks.get(stackNdx);
+ stack.addStartingWindowsForVisibleActivities(taskSwitch);
+ }
+ }
+ }
+
void invalidateTaskLayers() {
mTaskLayersChanged = true;
}
@@ -3346,7 +3344,7 @@
stack.switchUserLocked(userId);
TaskRecord task = stack.topTask();
if (task != null) {
- task.moveWindowContainerToTop(true /* includingParents */);
+ stack.positionChildWindowContainerAtTop(task);
}
}
}
@@ -3784,7 +3782,7 @@
private StackInfo getStackInfoLocked(ActivityStack stack) {
final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
StackInfo info = new StackInfo();
- mWindowManager.getStackBounds(stack.mStackId, info.bounds);
+ stack.getWindowContainerBounds(info.bounds);
info.displayId = DEFAULT_DISPLAY;
info.stackId = stack.mStackId;
info.userId = stack.mCurrentUser;
@@ -3884,14 +3882,14 @@
}
final ActivityRecord topActivity = task.getTopActivity();
- if (!task.canGoInDockedStack() || forceNonResizable) {
+ if (!task.supportsSplitScreen() || forceNonResizable) {
// Display a warning toast that we tried to put a non-dockable task in the docked stack.
mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
// Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
// we need to move it to top of fullscreen stack, otherwise it will be covered.
moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, actualStackId == DOCKED_STACK_ID);
- } else if (topActivity != null && topActivity.isNonResizableOrForced()
+ } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
&& !topActivity.noDisplay) {
String packageName = topActivity.appInfo.packageName;
mService.mTaskChangeNotificationController.notifyActivityForcedResizable(
@@ -4310,7 +4308,7 @@
FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION;
final int mStackId;
IActivityContainerCallback mCallback = null;
- final ActivityStack mStack;
+ ActivityStack mStack;
ActivityRecord mParentActivity = null;
String mIdString;
@@ -4324,10 +4322,11 @@
final static int CONTAINER_STATE_FINISHING = 2;
int mContainerState = CONTAINER_STATE_HAS_SURFACE;
- ActivityContainer(int stackId) {
+ ActivityContainer(int stackId, ActivityDisplay activityDisplay, boolean onTop) {
synchronized (mService) {
mStackId = stackId;
- mStack = new ActivityStack(this, mRecentTasks);
+ mActivityDisplay = activityDisplay;
+ new ActivityStack(this, mRecentTasks, onTop);
mIdString = "ActivtyContainer{" + mStackId + "}";
if (DEBUG_STACK) Slog.d(TAG_STACK, "Creating " + this);
}
@@ -4335,21 +4334,18 @@
/**
* Adds the stack to specified display. Also calls WindowManager to do the same from
- * {@link ActivityStack#addToDisplay(ActivityDisplay, boolean)}.
+ * {@link ActivityStack#reparent(ActivityDisplay, boolean)}.
* @param activityDisplay The display to add the stack to.
- * @param onTop If true the stack will be place at the top of the display, else at the
- * bottom.
*/
- void addToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
+ void addToDisplayLocked(ActivityDisplay activityDisplay) {
if (DEBUG_STACK) Slog.d(TAG_STACK, "addToDisplayLocked: " + this
- + " to display=" + activityDisplay + " onTop=" + onTop);
+ + " to display=" + activityDisplay);
if (mActivityDisplay != null) {
throw new IllegalStateException("ActivityContainer is already attached, " +
"displayId=" + mActivityDisplay.mDisplayId);
}
mActivityDisplay = activityDisplay;
- mStack.addToDisplay(activityDisplay, onTop);
- activityDisplay.attachActivities(mStack, onTop);
+ mStack.reparent(activityDisplay, true /* onTop */);
}
@Override
@@ -4359,7 +4355,7 @@
if (activityDisplay == null) {
return;
}
- addToDisplayLocked(activityDisplay, true /* onTop */);
+ addToDisplayLocked(activityDisplay);
}
}
@@ -4436,7 +4432,7 @@
if (DEBUG_STACK) Slog.d(TAG_STACK, "removeFromDisplayLocked: " + this
+ " current displayId=" + mActivityDisplay.mDisplayId);
- mActivityDisplay.detachActivitiesLocked(mStack);
+ mActivityDisplay.detachStack(mStack);
mActivityDisplay = null;
}
@@ -4452,8 +4448,7 @@
removeFromDisplayLocked();
mActivityDisplay = activityDisplay;
- mStack.moveToDisplay(activityDisplay);
- activityDisplay.attachActivities(mStack, ON_TOP);
+ mStack.reparent(activityDisplay, ON_TOP);
}
@Override
@@ -4543,7 +4538,8 @@
boolean mDrawn = false;
VirtualActivityContainer(ActivityRecord parent, IActivityContainerCallback callback) {
- super(getNextStackId());
+ super(getNextStackId(), parent.getStack().mActivityContainer.mActivityDisplay,
+ true /* onTop */);
mParentActivity = parent;
mCallback = callback;
mContainerState = CONTAINER_STATE_NO_SURFACE;
@@ -4575,7 +4571,7 @@
new VirtualActivityDisplay(width, height, density);
mActivityDisplay = virtualActivityDisplay;
mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
- addToDisplayLocked(virtualActivityDisplay, true /* onTop */);
+ addToDisplayLocked(virtualActivityDisplay);
}
if (mSurface != null) {
@@ -4664,10 +4660,9 @@
mDisplayId = display.getDisplayId();
}
- void attachActivities(ActivityStack stack, boolean onTop) {
- if (DEBUG_STACK) Slog.v(TAG_STACK,
- "attachActivities: attaching " + stack + " to displayId=" + mDisplayId
- + " onTop=" + onTop);
+ void attachStack(ActivityStack stack, boolean onTop) {
+ if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack
+ + " to displayId=" + mDisplayId + " onTop=" + onTop);
if (onTop) {
mStacks.add(stack);
} else {
@@ -4675,8 +4670,8 @@
}
}
- void detachActivitiesLocked(ActivityStack stack) {
- if (DEBUG_STACK) Slog.v(TAG_STACK, "detachActivitiesLocked: detaching " + stack
+ void detachStack(ActivityStack stack) {
+ if (DEBUG_STACK) Slog.v(TAG_STACK, "detachStack: detaching " + stack
+ " from displayId=" + mDisplayId);
mStacks.remove(stack);
}
@@ -4754,8 +4749,8 @@
}
@Override
- void detachActivitiesLocked(ActivityStack stack) {
- super.detachActivitiesLocked(stack);
+ void detachStack(ActivityStack stack) {
+ super.detachStack(stack);
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
mVirtualDisplay = null;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 26d2ee2..b913a23 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -460,6 +460,7 @@
final String splitName = rInfo.ephemeralResponse.splitName;
final boolean needsPhaseTwo = rInfo.ephemeralResponse.needsPhase2;
final String token = rInfo.ephemeralResponse.token;
+ final int versionCode = rInfo.ephemeralResponse.resolveInfo.getVersionCode();
if (needsPhaseTwo) {
// request phase two resolution
mService.getPackageManagerInternalLocked().requestEphemeralResolutionPhaseTwo(
@@ -467,8 +468,8 @@
callingPackage, userId);
}
intent = EphemeralResolver.buildEphemeralInstallerIntent(intent, ephemeralIntent,
- callingPackage, resolvedType, userId, packageName, splitName, token,
- needsPhaseTwo);
+ callingPackage, resolvedType, userId, packageName, splitName, versionCode,
+ token, needsPhaseTwo);
resolvedType = null;
callingUid = realCallingUid;
callingPid = realCallingPid;
@@ -1834,7 +1835,7 @@
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
mIntent, null, null, true, mStartActivity.mActivityType);
mStartActivity.setTask(task, null);
- mStartActivity.task.moveWindowContainerToTop(true /* includingParents */);
+ mStartActivity.task.getStack().positionChildWindowContainerAtTop(mStartActivity.task);
if (DEBUG_TASKS) Slog.v(TAG_TASKS,
"Starting new activity " + mStartActivity + " in new guessed " + mStartActivity.task);
}
@@ -1966,10 +1967,10 @@
canUseFocusedStack = true;
break;
case DOCKED_STACK_ID:
- canUseFocusedStack = r.canGoInDockedStack();
+ canUseFocusedStack = r.supportsSplitScreen();
break;
case FREEFORM_WORKSPACE_STACK_ID:
- canUseFocusedStack = r.isResizeableOrForced();
+ canUseFocusedStack = r.supportsFreeform();
break;
default:
canUseFocusedStack = isDynamicStack(focusedStackId)
@@ -2056,29 +2057,24 @@
}
boolean isValidLaunchStackId(int stackId, ActivityRecord r) {
- if (stackId == INVALID_STACK_ID || stackId == HOME_STACK_ID) {
- return false;
+ switch (stackId) {
+ case INVALID_STACK_ID:
+ case HOME_STACK_ID:
+ return false;
+ case FULLSCREEN_WORKSPACE_STACK_ID:
+ return true;
+ case FREEFORM_WORKSPACE_STACK_ID:
+ return r.supportsFreeform();
+ case DOCKED_STACK_ID:
+ return r.supportsSplitScreen();
+ case PINNED_STACK_ID:
+ return r.supportsPictureInPicture();
+ case RECENTS_STACK_ID:
+ return r.isRecentsActivity();
+ default:
+ Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId);
+ return false;
}
-
- if (stackId != FULLSCREEN_WORKSPACE_STACK_ID
- && (!mService.mSupportsMultiWindow || !r.isResizeableOrForced())) {
- return false;
- }
-
- if (stackId == DOCKED_STACK_ID && r.canGoInDockedStack()) {
- return true;
- }
-
- if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
- return false;
- }
-
- final boolean supportsPip = mService.mSupportsPictureInPicture
- && (r.supportsPictureInPicture() || mService.mForceResizableActivities);
- if (stackId == PINNED_STACK_ID && !supportsPip) {
- return false;
- }
- return true;
}
Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 739a8c4..384f2f8 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -259,7 +259,16 @@
}
}
- void scheduleAppCrashLocked(int uid, int initialPid, String packageName,
+ /**
+ * Induce a crash in the given app.
+ *
+ * @param uid if nonnegative, the required matching uid of the target to crash
+ * @param initialPid fast-path match for the target to crash
+ * @param packageName fallback match if the stated pid is not found or doesn't match uid
+ * @param userId If nonnegative, required to identify a match by package name
+ * @param message
+ */
+ void scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId,
String message) {
ProcessRecord proc = null;
@@ -270,14 +279,15 @@
synchronized (mService.mPidsSelfLocked) {
for (int i=0; i<mService.mPidsSelfLocked.size(); i++) {
ProcessRecord p = mService.mPidsSelfLocked.valueAt(i);
- if (p.uid != uid) {
+ if (uid >= 0 && p.uid != uid) {
continue;
}
if (p.pid == initialPid) {
proc = p;
break;
}
- if (p.pkgList.containsKey(packageName)) {
+ if (p.pkgList.containsKey(packageName)
+ && (userId < 0 || p.userId == userId)) {
proc = p;
}
}
@@ -286,7 +296,8 @@
if (proc == null) {
Slog.w(TAG, "crashApplication: nothing for uid=" + uid
+ " initialPid=" + initialPid
- + " packageName=" + packageName);
+ + " packageName=" + packageName
+ + " userId=" + userId);
return;
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 61e555b..ee2467a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -592,22 +592,6 @@
+ " (uid " + r.callingUid + ")");
skip = true;
}
- if (!skip) {
- final int allowed = mService.checkAllowBackgroundLocked(filter.receiverList.uid,
- filter.packageName, -1, false);
- if (false && allowed == ActivityManager.APP_START_MODE_DISABLED) {
- // XXX should we really not allow this? It means that while we are
- // keeping an ephemeral app cached, its registered receivers will stop
- // receiving broadcasts after it goes idle... so if it comes back to
- // the foreground, it won't know what the current state of those broadcasts is.
- Slog.w(TAG, "Background execution not allowed: receiving "
- + r.intent
- + " to " + filter.receiverList.app
- + " (pid=" + filter.receiverList.pid
- + ", uid=" + filter.receiverList.uid + ")");
- skip = true;
- }
- }
if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
r.callingPid, r.resolvedType, filter.receiverList.uid)) {
@@ -1156,13 +1140,14 @@
info.activityInfo.applicationInfo.uid, false);
if (!skip) {
- final int allowed = mService.checkAllowBackgroundLocked(
- info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, -1, true);
+ final int allowed = mService.getAppStartModeLocked(
+ info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
+ info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
// We won't allow this receiver to be launched if the app has been
// completely disabled from launches, or it was not explicitly sent
// to it and the app is in a state that should not receive it
- // (depending on how checkAllowBackgroundLocked has determined that).
+ // (depending on how getAppStartModeLocked has determined that).
if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
Slog.w(TAG, "Background execution disabled: receiving "
+ r.intent + " to "
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index cfe2eb0..b0a4746 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -29,6 +29,7 @@
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY;
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_OCCLUDE;
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static com.android.server.wm.AppTransition.TRANSIT_NONE;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import android.os.IBinder;
@@ -120,6 +121,7 @@
// Some stack visibility might change (e.g. docked stack)
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+ mStackSupervisor.addStartingWindowsForVisibleActivities(true /* taskSwitch */);
mWindowManager.executeAppTransition();
} finally {
mWindowManager.continueSurfaceLayout();
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 71c7fd3..82b00da 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -534,7 +534,7 @@
// get to be foreground.
ams.setServiceForeground(name, ServiceRecord.this,
0, null, 0);
- ams.crashApplication(appUid, appPid, localPackageName,
+ ams.crashApplication(appUid, appPid, localPackageName, -1,
"Bad notification for startForeground: " + e);
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 1f5152a..f12d7b7 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -85,6 +85,7 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
@@ -141,6 +142,7 @@
private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
private static final String ATTR_CALLING_UID = "calling_uid";
private static final String ATTR_CALLING_PACKAGE = "calling_package";
+ private static final String ATTR_SUPPORTS_PICTURE_IN_PICTURE = "supports_picture_in_picture";
private static final String ATTR_RESIZE_MODE = "resize_mode";
private static final String ATTR_PRIVILEGED = "privileged";
private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds";
@@ -188,6 +190,9 @@
int mResizeMode; // The resize mode of this task and its activities.
// Based on the {@link ActivityInfo#resizeMode} of the root activity.
+ boolean mSupportsPictureInPicture; // Whether or not this task and its activities support PiP.
+ // Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag of the root
+ // activity.
boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
// changes on a temporary basis.
private int mLockTaskMode; // Which tasklock mode to launch this task in. One of
@@ -356,8 +361,9 @@
boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
TaskThumbnailInfo lastThumbnailInfo, int taskAffiliation, int prevTaskId,
int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
- int resizeMode, boolean privileged, boolean _realActivitySuspended,
- boolean userSetupComplete, int minWidth, int minHeight) {
+ int resizeMode, boolean supportsPictureInPicture, boolean privileged,
+ boolean _realActivitySuspended, boolean userSetupComplete, int minWidth,
+ int minHeight) {
mService = service;
mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
TaskPersister.IMAGE_EXTENSION;
@@ -396,6 +402,7 @@
mCallingUid = callingUid;
mCallingPackage = callingPackage;
mResizeMode = resizeMode;
+ mSupportsPictureInPicture = supportsPictureInPicture;
mPrivileged = privileged;
mMinWidth = minWidth;
mMinHeight = minHeight;
@@ -414,9 +421,10 @@
final Rect bounds = updateOverrideConfigurationFromLaunchBounds();
final Configuration overrideConfig = getOverrideConfiguration();
- mWindowContainerController = new TaskWindowContainerController(taskId, this, getStackId(),
- userId, bounds, overrideConfig, mResizeMode, isHomeTask(), isOnTopLauncher(), onTop,
- showForAllUsers);
+ mWindowContainerController = new TaskWindowContainerController(taskId, this,
+ getStack().getWindowContainerController(), userId, bounds, overrideConfig,
+ mResizeMode, mSupportsPictureInPicture, isHomeTask(), isOnTopLauncher(),
+ onTop, showForAllUsers, lastTaskDescription);
}
void removeWindowContainer() {
@@ -450,6 +458,12 @@
mWindowContainerController.setTaskDockedResizing(resizing);
}
+ // TODO: Consolidate this with the resize() method below.
+ @Override
+ public void requestResize(Rect bounds, int resizeMode) {
+ mService.resizeTask(taskId, bounds, resizeMode);
+ }
+
boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
if (!isResizeable()) {
Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
@@ -520,25 +534,6 @@
false /* forced */);
}
- // TODO: Remove once we have a stack controller.
- void positionWindowContainerAt(int position) {
- mWindowContainerController.positionAt(position, mBounds, getOverrideConfiguration());
- }
-
- // TODO: Replace with moveChildToTop?
- void moveWindowContainerToTop(boolean includingParents) {
- if (mWindowContainerController != null) {
- mWindowContainerController.moveToTop(includingParents);
- }
- }
-
- // TODO: Replace with moveChildToBottom?
- void moveWindowContainerToBottom() {
- if (mWindowContainerController != null) {
- mWindowContainerController.moveToBottom();
- }
- }
-
void getWindowContainerBounds(Rect bounds) {
mWindowContainerController.getBounds(bounds);
}
@@ -556,7 +551,7 @@
// Must reparent first in window manager to avoid a situation where AM can delete the
// we are coming from in WM before we reparent because it became empty.
- mWindowContainerController.reparent(stackId, position);
+ mWindowContainerController.reparent(newStack.getWindowContainerController(), position);
final ActivityStack prevStack = mStack;
prevStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
@@ -691,6 +686,7 @@
autoRemoveRecents = false;
}
mResizeMode = info.resizeMode;
+ mSupportsPictureInPicture = info.supportsPictureInPicture();
mIsOnTopLauncher = (info.flags & FLAG_ON_TOP_LAUNCHER) != 0;
mLockTaskMode = info.lockTaskLaunchMode;
mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
@@ -1301,9 +1297,21 @@
return mTaskToReturnTo == HOME_ACTIVITY_TYPE;
}
+ private boolean isResizeable(boolean checkSupportsPip) {
+ return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
+ || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable;
+ }
+
boolean isResizeable() {
- return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode))
- && !mTemporarilyUnresizable;
+ return isResizeable(true /* checkSupportsPip */);
+ }
+
+ boolean supportsSplitScreen() {
+ // A task can not be docked even if it is considered resizeable because it only supports
+ // picture-in-picture mode but has a non-resizeable resizeMode
+ return mService.mSupportsSplitScreenMultiWindow
+ && isResizeable(false /* checkSupportsPip */)
+ && !ActivityInfo.isPreserveOrientationMode(mResizeMode);
}
/**
@@ -1329,11 +1337,6 @@
return isHomeTask() && mIsOnTopLauncher;
}
- boolean canGoInDockedStack() {
- return isResizeable() && mService.mSupportsSplitScreenMultiWindow &&
- !ActivityInfo.isPreserveOrientationMode(mResizeMode);
- }
-
/**
* Find the activity in the history stack within the given task. Returns
* the index within the history at which it's found, or < 0 if not found.
@@ -1402,6 +1405,9 @@
}
lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary,
colorBackground);
+ if (mWindowContainerController != null) {
+ mWindowContainerController.setTaskDescription(lastTaskDescription);
+ }
// Update the task affiliation color if we are the parent of the group
if (taskId == mAffiliatedTaskId) {
mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor();
@@ -1479,6 +1485,8 @@
out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid));
out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage);
out.attribute(null, ATTR_RESIZE_MODE, String.valueOf(mResizeMode));
+ out.attribute(null, ATTR_SUPPORTS_PICTURE_IN_PICTURE,
+ String.valueOf(mSupportsPictureInPicture));
out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged));
if (mLastNonFullscreenBounds != null) {
out.attribute(
@@ -1549,6 +1557,7 @@
int callingUid = -1;
String callingPackage = "";
int resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
+ boolean supportsPictureInPicture = false;
boolean privileged = false;
Rect bounds = null;
int minWidth = INVALID_MIN_SIZE;
@@ -1615,6 +1624,8 @@
callingPackage = attrValue;
} else if (ATTR_RESIZE_MODE.equals(attrName)) {
resizeMode = Integer.parseInt(attrValue);
+ } else if (ATTR_SUPPORTS_PICTURE_IN_PICTURE.equals(attrName)) {
+ supportsPictureInPicture = Boolean.parseBoolean(attrValue);
} else if (ATTR_PRIVILEGED.equals(attrName)) {
privileged = Boolean.parseBoolean(attrValue);
} else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) {
@@ -1687,6 +1698,15 @@
if (taskType == HOME_ACTIVITY_TYPE && resizeMode == RESIZE_MODE_RESIZEABLE) {
resizeMode = RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
}
+ } else {
+ // This activity has previously marked itself explicitly as both resizeable and
+ // supporting picture-in-picture. Since there is no longer a requirement for
+ // picture-in-picture activities to be resizeable, we can mark this simply as
+ // resizeable and supporting picture-in-picture separately.
+ if (resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED) {
+ resizeMode = RESIZE_MODE_RESIZEABLE;
+ supportsPictureInPicture = true;
+ }
}
final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
@@ -1694,8 +1714,9 @@
autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
taskDescription, thumbnailInfo, taskAffiliation, prevTaskId, nextTaskId,
- taskAffiliationColor, callingUid, callingPackage, resizeMode, privileged,
- realActivitySuspended, userSetupComplete, minWidth, minHeight);
+ taskAffiliationColor, callingUid, callingPackage, resizeMode,
+ supportsPictureInPicture, privileged, realActivitySuspended, userSetupComplete,
+ minWidth, minHeight);
task.updateOverrideConfiguration(bounds);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
@@ -1981,6 +2002,15 @@
return rootAffinity != null && getStackId() != PINNED_STACK_ID;
}
+ void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
+ for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = mActivities.get(activityNdx);
+ if (r.visible) {
+ r.showStartingWindow(null /* prev */, false /* newTask */, taskSwitch);
+ }
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("userId="); pw.print(userId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
@@ -2072,6 +2102,7 @@
pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
+ pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture);
pw.print(" isResizeable=" + isResizeable());
pw.print(" firstActiveTime=" + lastActiveTime);
pw.print(" lastActiveTime=" + lastActiveTime);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5bf92d7..728476a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -47,6 +47,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.IStopUserCallback;
@@ -55,6 +56,7 @@
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.BatteryStats;
@@ -255,7 +257,9 @@
// storage is already unlocked.
if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- if (!mInjector.isRuntimeRestarted() && !mInjector.isFirstBoot()) {
+ // Do not report secondary users, runtime restarts or first boot/upgrade
+ if (userId == UserHandle.USER_SYSTEM
+ && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
MetricsLogger.histogram(mInjector.getContext(),
"framework_locked_boot_completed", uptimeSeconds);
@@ -436,7 +440,9 @@
}
Slog.d(TAG, "Sending BOOT_COMPLETE user #" + userId);
- if (!mInjector.isRuntimeRestarted() && !mInjector.isFirstBoot()) {
+ // Do not report secondary users, runtime restarts or first boot/upgrade
+ if (userId == UserHandle.USER_SYSTEM
+ && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
uptimeSeconds);
@@ -1709,8 +1715,13 @@
return mService.mSystemServiceManager.isRuntimeRestarted();
}
- boolean isFirstBoot() {
- return mService.mSystemServiceManager.isFirstBoot();
+ boolean isFirstBootOrUpgrade() {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ try {
+ return pm.isFirstBoot() || pm.isUpgrade();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
void sendPreBootBroadcast(int userId, boolean quiet, final Runnable onFinish) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index df5f01d..31ef94f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5932,7 +5932,7 @@
* the whether any exposes the FLAG_ENABLE_ACCESSIBILITY_VOLUME flag
* - set to false to listen to when accessibility services are started (e.g. "TalkBack started")
*/
- private static final boolean USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME = true;
+ private static final boolean USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME = false;
private void initA11yMonitoring() {
final AccessibilityManager accessibilityManager =
@@ -6502,6 +6502,35 @@
return mRecordMonitor.getActiveRecordingConfigurations();
}
+ public void disableRingtoneSync() {
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UserManager userManager = UserManager.get(mContext);
+
+ // Disable the sync setting
+ Settings.Secure.putIntForUser(mContentResolver,
+ Settings.Secure.SYNC_PARENT_SOUNDS, 0 /* false */, callingUserId);
+
+ UserInfo parentInfo = userManager.getProfileParent(callingUserId);
+ if (parentInfo != null && parentInfo.id != callingUserId) {
+ // This is a managed profile, so we clone the ringtones from the parent profile
+ cloneRingtoneSetting(callingUserId, parentInfo.id, Settings.System.RINGTONE);
+ cloneRingtoneSetting(callingUserId, parentInfo.id,
+ Settings.System.NOTIFICATION_SOUND);
+ cloneRingtoneSetting(callingUserId, parentInfo.id, Settings.System.ALARM_ALERT);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void cloneRingtoneSetting(int userId, int parentId, String ringtoneSetting) {
+ String parentSetting = Settings.System.getStringForUser(mContentResolver, ringtoneSetting,
+ parentId);
+ Settings.System.putStringForUser(mContentResolver, ringtoneSetting, parentSetting, userId);
+ }
+
//======================
// Audio playback notification
//======================
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index e84bf40..b0e4509 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -975,8 +975,6 @@
private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
- private int mPreviousMobileType = ConnectivityManager.TYPE_NONE;
-
private static final int UPSTREAM_SETTLE_TIME_MS = 10000;
TetherMasterSM(String name, Looper looper) {
@@ -1010,43 +1008,14 @@
return false;
}
- protected boolean requestUpstreamMobileConnection(int apnType) {
- if (apnType == ConnectivityManager.TYPE_NONE) { return false; }
-
- if (apnType != mPreviousMobileType) {
- // Unregister any previous mobile upstream callback because
- // this request, if any, will be different.
- unrequestUpstreamMobileConnection();
- }
-
- if (mUpstreamNetworkMonitor.mobileNetworkRequested()) {
- // Looks like we already filed a request for this apnType.
- return true;
- }
-
- switch (apnType) {
- case ConnectivityManager.TYPE_MOBILE_DUN:
- case ConnectivityManager.TYPE_MOBILE:
- case ConnectivityManager.TYPE_MOBILE_HIPRI:
- mPreviousMobileType = apnType;
- break;
- default:
- return false;
- }
-
- // TODO: Replace this with a call to pass the current tethering
- // configuration to mUpstreamNetworkMonitor and let it handle
- // choosing APN type accordingly.
- mUpstreamNetworkMonitor.updateMobileRequiresDun(
- apnType == ConnectivityManager.TYPE_MOBILE_DUN);
-
+ protected boolean requestUpstreamMobileConnection() {
+ mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
mUpstreamNetworkMonitor.registerMobileNetworkRequest();
return true;
}
protected void unrequestUpstreamMobileConnection() {
mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
- mPreviousMobileType = ConnectivityManager.TYPE_NONE;
}
protected boolean turnOnMasterTetherSettings() {
@@ -1128,11 +1097,10 @@
case ConnectivityManager.TYPE_MOBILE_DUN:
case ConnectivityManager.TYPE_MOBILE_HIPRI:
// If we're on DUN, put our own grab on it.
- requestUpstreamMobileConnection(upType);
+ requestUpstreamMobileConnection();
break;
case ConnectivityManager.TYPE_NONE:
- if (tryCell &&
- requestUpstreamMobileConnection(preferredUpstreamMobileApn)) {
+ if (tryCell && requestUpstreamMobileConnection()) {
// We think mobile should be coming up; don't set a retry.
} else {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 11a3f11..5b539ff 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1019,8 +1019,7 @@
final int owningUid = syncAdapterInfo.uid;
final String owningPackage = syncAdapterInfo.componentName.getPackageName();
try {
- if (ActivityManager.getService().getAppStartMode(owningUid,
- owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+ if (ActivityManager.getService().isAppStartModeDisabled(owningUid, owningPackage)) {
Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":"
+ syncAdapterInfo.componentName
+ " -- package not allowed to start");
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 8673225..841a951 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -285,6 +285,7 @@
int activeColorMode) {
List<Integer> pendingColorModes = new ArrayList<>();
+ if (colorModes == null) return false;
// Build an updated list of all existing color modes.
boolean colorModesAdded = false;
for (int colorMode: colorModes) {
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index 0ec48b9..0357b1b 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -75,6 +75,11 @@
};
/**
+ * The transition time, in milliseconds, for Night Display to turn on/off.
+ */
+ private static final long TRANSITION_DURATION = 3000L;
+
+ /**
* The identity matrix, used if one of the given matrices is {@code null}.
*/
private static final float[] MATRIX_IDENTITY = new float[16];
@@ -285,8 +290,7 @@
mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
- mColorMatrixAnimator.setDuration(getContext().getResources()
- .getInteger(android.R.integer.config_longAnimTime));
+ mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.fast_out_slow_in));
mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 997222c..1feae3d 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -482,7 +482,7 @@
UserManager um = UserManager.get(mContext);
// Allow current user or profiles of the current user...
- for (int profileId : um.getEnabledProfileIds(userId)) {
+ for (int profileId : um.getEnabledProfileIds(mCurrentUserId)) {
if (profileId == userId) {
return true;
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index f42c5be..a748013 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -566,8 +566,8 @@
String tag) {
JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
try {
- if (ActivityManager.getService().getAppStartMode(uId,
- job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
+ if (ActivityManager.getService().isAppStartModeDisabled(uId,
+ job.getService().getPackageName())) {
Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ " -- package not allowed to start");
return JobScheduler.RESULT_FAILURE;
@@ -1201,9 +1201,8 @@
public void process(JobStatus job) {
if (isReadyToBeExecutedLocked(job)) {
try {
- if (ActivityManager.getService().getAppStartMode(job.getUid(),
- job.getJob().getService().getPackageName())
- == ActivityManager.APP_START_MODE_DISABLED) {
+ if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
+ job.getJob().getService().getPackageName())) {
Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+ job.getJob().toString() + " -- package not allowed to start");
mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9b37f12..3bf95ef 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -17,6 +17,7 @@
package com.android.server.media;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -36,10 +37,13 @@
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
import android.media.session.IActiveSessionsListener;
+import android.media.session.IOnMediaKeyListener;
+import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.ISessionManager;
import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -78,10 +82,11 @@
public class MediaSessionService extends SystemService implements Monitor {
private static final String TAG = "MediaSessionService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // Leave log for media key event always.
- private static final boolean DEBUG_MEDIA_KEY_EVENT = DEBUG || true;
+ // Leave log for key event always.
+ private static final boolean DEBUG_KEY_EVENT = true;
private static final int WAKELOCK_TIMEOUT = 5000;
+ private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
/* package */final IBinder mICallback = new Binder();
@@ -523,6 +528,14 @@
}
}
+ private String getCallingPackageName(int uid) {
+ String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ return packages[0];
+ }
+ return "";
+ }
+
/**
* Information about a particular user. The contents of this object is
* guarded by mLock.
@@ -534,6 +547,15 @@
private PendingIntent mLastMediaButtonReceiver;
private ComponentName mRestoredMediaButtonReceiver;
+ private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
+ private int mOnVolumeKeyLongPressListenerUid;
+ private KeyEvent mInitialDownVolumeKeyEvent;
+ private int mInitialDownVolumeStream;
+ private boolean mInitialDownMusicOnly;
+
+ private IOnMediaKeyListener mOnMediaKeyListener;
+ private int mOnMediaKeyListenerUid;
+
public UserRecord(Context context, int userId) {
mContext = context;
mUserId = userId;
@@ -564,6 +586,12 @@
String indent = prefix + " ";
pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
pw.println(indent + "Restored ButtonReceiver:" + mRestoredMediaButtonReceiver);
+ pw.println(indent + "Volume key long-press listener:" + mOnVolumeKeyLongPressListener);
+ pw.println(indent + "Volume key long-press listener package:" +
+ getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
+ pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
+ pw.println(indent + "Media key listener package: " +
+ getCallingPackageName(mOnMediaKeyListenerUid));
int size = mSessions.size();
pw.println(indent + size + " Sessions:");
for (int i = 0; i < size; i++) {
@@ -769,28 +797,10 @@
}
synchronized (mLock) {
- // If we don't have a media button receiver to fall back on
- // include non-playing sessions for dispatching
- boolean useNotPlayingSessions = true;
- for (int userId : mCurrentUserIdList) {
- UserRecord ur = mUserRecords.get(userId);
- if (ur.mLastMediaButtonReceiver != null
- || ur.mRestoredMediaButtonReceiver != null) {
- useNotPlayingSessions = false;
- break;
- }
- }
-
- if (DEBUG) {
- Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions="
- + useNotPlayingSessions);
- }
- MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession(
- mCurrentUserIdList, useNotPlayingSessions);
- if (isVoiceKey(keyEvent.getKeyCode())) {
- handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
+ if (!isGlobalPriorityActive() && isVoiceKey(keyEvent.getKeyCode())) {
+ handleVoiceKeyEventLocked(keyEvent, needWakeLock);
} else {
- dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
+ dispatchMediaKeyEventLocked(keyEvent, needWakeLock, true);
}
}
} finally {
@@ -799,13 +809,245 @@
}
@Override
+ public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
+ if (getContext().checkPermission(
+ android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
+ " permission.");
+ }
+
+ synchronized (mLock) {
+ UserRecord user = mUserRecords.get(UserHandle.getUserId(uid));
+ if (user.mOnVolumeKeyLongPressListener != null &&
+ user.mOnVolumeKeyLongPressListenerUid != uid) {
+ Log.w(TAG, "Volume key long-press listener cannot be reset by another app");
+ return;
+ }
+
+ user.mOnVolumeKeyLongPressListener = listener;
+ user.mOnVolumeKeyLongPressListenerUid = uid;
+
+ Log.d(TAG, "Volume key long-press listener "
+ + listener + " is set by " + getCallingPackageName(uid));
+
+ if (user.mOnVolumeKeyLongPressListener != null) {
+ try {
+ user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ user.mOnVolumeKeyLongPressListener = null;
+ }
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set death recipient "
+ + user.mOnVolumeKeyLongPressListener);
+ user.mOnVolumeKeyLongPressListener = null;
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Enforce SET_MEDIA_KEY_LISTENER permission.
+ if (getContext().checkPermission(
+ android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" +
+ " permission.");
+ }
+
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(uid);
+ UserRecord user = mUserRecords.get(userId);
+ if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
+ Log.w(TAG, "Media key listener cannot be reset by another app");
+ return;
+ }
+
+ user.mOnMediaKeyListener = listener;
+ user.mOnMediaKeyListenerUid = uid;
+
+ Log.d(TAG, "Media key listener " + user.mOnMediaKeyListener
+ + " is set by " + getCallingPackageName(uid));
+
+ if (user.mOnMediaKeyListener != null) {
+ try {
+ user.mOnMediaKeyListener.asBinder().linkToDeath(
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ user.mOnMediaKeyListener = null;
+ }
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
+ user.mOnMediaKeyListener = null;
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Handles the dispatching of the volume button events to one of the
+ * registered listeners. If there's a volume key long-press listener and
+ * there's no active global priority session, long-pressess will be sent to the
+ * long-press listener instead of adjusting volume.
+ *
+ * @param keyEvent a non-null KeyEvent whose key code is one of the
+ * {@link KeyEvent#KEYCODE_VOLUME_UP},
+ * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
+ * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
+ * @param stream stream type to adjust volume.
+ * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
+ */
+ @Override
+ public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
+ if (keyEvent == null ||
+ (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
+ && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
+ && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
+ Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
+ return;
+ }
+
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ if (DEBUG) {
+ Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
+ + keyEvent);
+ }
+
+ try {
+ synchronized (mLock) {
+ // Only consider full user.
+ UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+
+ if (mPriorityStack.isGlobalPriorityActive()
+ || user.mOnVolumeKeyLongPressListener == null) {
+ dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
+ } else {
+ // TODO: Consider the case when both volume up and down keys are pressed
+ // at the same time.
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ if (keyEvent.getRepeatCount() == 0) {
+ user.mInitialDownVolumeKeyEvent = keyEvent;
+ user.mInitialDownVolumeStream = stream;
+ user.mInitialDownMusicOnly = musicOnly;
+ }
+ if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
+ if (user.mInitialDownVolumeKeyEvent != null) {
+ dispatchVolumeKeyLongPressLocked(
+ user.mInitialDownVolumeKeyEvent);
+ // Mark that the key is already handled.
+ user.mInitialDownVolumeKeyEvent = null;
+ }
+ dispatchVolumeKeyLongPressLocked(keyEvent);
+ }
+ } else { // if up
+ if (user.mInitialDownVolumeKeyEvent != null
+ && user.mInitialDownVolumeKeyEvent.getDownTime()
+ == keyEvent.getDownTime()) {
+ // Short-press. Should change volume.
+ dispatchVolumeKeyEventLocked(
+ user.mInitialDownVolumeKeyEvent,
+ user.mInitialDownVolumeStream,
+ user.mInitialDownMusicOnly);
+ dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
+ } else {
+ dispatchVolumeKeyLongPressLocked(keyEvent);
+ }
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
+ // Only consider full user.
+ UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+ try {
+ user.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
+ }
+ }
+
+ private void dispatchVolumeKeyEventLocked(
+ KeyEvent keyEvent, int stream, boolean musicOnly) {
+ boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
+ boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
+ int direction = 0;
+ boolean isMute = false;
+ switch (keyEvent.getKeyCode()) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ direction = AudioManager.ADJUST_RAISE;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ direction = AudioManager.ADJUST_LOWER;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ isMute = true;
+ break;
+ }
+ if (down || up) {
+ int flags = AudioManager.FLAG_FROM_KEY;
+ if (musicOnly) {
+ // This flag is used when the screen is off to only affect active media.
+ flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
+ } else {
+ // These flags are consistent with the home screen
+ if (up) {
+ flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
+ } else {
+ flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
+ }
+ }
+ if (direction != 0) {
+ // If this is action up we want to send a beep for non-music events
+ if (up) {
+ direction = 0;
+ }
+ dispatchAdjustVolumeLocked(stream, direction, flags);
+ } else if (isMute) {
+ if (down && keyEvent.getRepeatCount() == 0) {
+ dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
+ }
+ }
+ }
+ }
+
+ @Override
public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- MediaSessionRecord session = mPriorityStack
- .getDefaultVolumeSession(mCurrentUserIdList);
- dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
+ dispatchAdjustVolumeLocked(suggestedStream, delta, flags);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -881,8 +1123,9 @@
return resolvedUserId;
}
- private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
- MediaSessionRecord session) {
+ private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
+ MediaSessionRecord session = mPriorityStack.getDefaultVolumeSession(mCurrentUserIdList);
+
boolean preferSuggestedStream = false;
if (isValidLocalStreamType(suggestedStream)
&& AudioSystem.isStreamActive(suggestedStream, 0)) {
@@ -925,13 +1168,7 @@
}
}
- private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
- MediaSessionRecord session) {
- if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
- // If the phone app has priority just give it the event
- dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
- return;
- }
+ private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
int action = keyEvent.getAction();
boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
if (action == KeyEvent.ACTION_DOWN) {
@@ -948,24 +1185,60 @@
if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
// Resend the down then send this event through
KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
- dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
- dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
+ dispatchMediaKeyEventLocked(downEvent, needWakeLock, true);
+ dispatchMediaKeyEventLocked(keyEvent, needWakeLock, true);
}
}
}
}
private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
- MediaSessionRecord session) {
+ boolean checkMediaKeyListener) {
+ // If we don't have a media button receiver to fall back on
+ // include non-playing sessions for dispatching.
+ boolean useNotPlayingSessions = true;
+ for (int userId : mCurrentUserIdList) {
+ UserRecord ur = mUserRecords.get(userId);
+ if (ur.mLastMediaButtonReceiver != null
+ || ur.mRestoredMediaButtonReceiver != null) {
+ useNotPlayingSessions = false;
+ break;
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions="
+ + useNotPlayingSessions);
+ }
+
+ MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession(
+ mCurrentUserIdList, useNotPlayingSessions);
+
+ if ((session == null
+ || !session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY))
+ && checkMediaKeyListener) {
+ // Only consider full user.
+ UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+ if (user.mOnMediaKeyListener != null) {
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Send " + keyEvent + " to media key listener");
+ }
+ try {
+ user.mOnMediaKeyListener.onMediaKey(keyEvent,
+ new MediaKeyListenerResultReceiver(keyEvent, needWakeLock));
+ return;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send " + keyEvent + " to media key listener");
+ }
+ }
+ }
if (session != null) {
- if (DEBUG_MEDIA_KEY_EVENT) {
+ if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Sending " + keyEvent + " to " + session);
}
if (needWakeLock) {
mKeyEventReceiver.aquireWakeLockLocked();
}
- // If we don't need a wakelock use -1 as the id so we
- // won't release it later
+ // If we don't need a wakelock use -1 as the id so we won't release it later.
session.sendMediaButton(keyEvent,
needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
mKeyEventReceiver, Process.SYSTEM_UID,
@@ -986,7 +1259,7 @@
mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
try {
if (user.mLastMediaButtonReceiver != null) {
- if (DEBUG_MEDIA_KEY_EVENT) {
+ if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Sending " + keyEvent
+ " to the last known pendingIntent "
+ user.mLastMediaButtonReceiver);
@@ -995,7 +1268,7 @@
needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
mediaButtonIntent, mKeyEventReceiver, mHandler);
} else {
- if (DEBUG_MEDIA_KEY_EVENT) {
+ if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
+ user.mRestoredMediaButtonReceiver);
}
@@ -1081,6 +1354,46 @@
&& streamType <= AudioManager.STREAM_NOTIFICATION;
}
+ private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
+ private KeyEvent mKeyEvent;
+ private boolean mNeedWakeLock;
+ private boolean mHandled;
+
+ private MediaKeyListenerResultReceiver(KeyEvent keyEvent, boolean needWakeLock) {
+ super(mHandler);
+ mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
+ mKeyEvent = keyEvent;
+ mNeedWakeLock = needWakeLock;
+ }
+
+ @Override
+ public void run() {
+ Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
+ dispatchMediaKeyEvent();
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
+ mHandled = true;
+ mHandler.removeCallbacks(this);
+ return;
+ }
+ dispatchMediaKeyEvent();
+ }
+
+ private void dispatchMediaKeyEvent() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+ mHandler.removeCallbacks(this);
+ synchronized (mLock) {
+ dispatchMediaKeyEventLocked(mKeyEvent, mNeedWakeLock, false);
+ }
+ }
+ }
+
private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
new file mode 100644
index 0000000..4795fbf
--- /dev/null
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -0,0 +1,59 @@
+/**
+* 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.notification;
+
+import android.content.Context;
+import android.util.Slog;
+
+/**
+ * Determines whether a badge should be shown for this notification
+ */
+public class BadgeExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "BadgeExtractor";
+ private static final boolean DBG = false;
+
+ private RankingConfig mConfig;
+
+ public void initialize(Context ctx, NotificationUsageStats usageStats) {
+ if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ }
+
+ public RankingReconsideration process(NotificationRecord record) {
+ if (record == null || record.getNotification() == null) {
+ if (DBG) Slog.d(TAG, "skipping empty notification");
+ return null;
+ }
+
+ if (mConfig == null) {
+ if (DBG) Slog.d(TAG, "missing config");
+ return null;
+ }
+ boolean appCanShowBadge =
+ mConfig.canShowBadge(record.sbn.getPackageName(), record.sbn.getUid());
+ if (!appCanShowBadge) {
+ record.setShowBadge(false);
+ } else {
+ record.setShowBadge(record.getChannel().canShowBadge() && appCanShowBadge);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void setConfig(RankingConfig config) {
+ mConfig = config;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6c66a60..45bdb9c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -32,6 +32,7 @@
import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED;
import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF;
import static android.service.notification.NotificationListenerService.REASON_SNOOZED;
+import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
@@ -50,6 +51,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
@@ -165,6 +167,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -198,6 +201,8 @@
static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
+ static final long SNOOZE_UNTIL_UNSPECIFIED = -1;
+
static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
@@ -227,13 +232,21 @@
private static final long DELAY_FOR_ASSISTANT_TIME = 100;
+ private static final String ACTION_NOTIFICATION_TIMEOUT =
+ NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
+ private static final int REQUEST_CODE_TIMEOUT = 1;
+ private static final String SCHEME_TIMEOUT = "timeout";
+ private static final String EXTRA_KEY = "key";
+
private IActivityManager mAm;
private IPackageManager mPackageManager;
+ private PackageManager mPackageManagerClient;
AudioManager mAudioManager;
AudioManagerInternal mAudioManagerInternal;
@Nullable StatusBarManagerInternal mStatusBar;
Vibrator mVibrator;
private WindowManagerInternal mWindowManagerInternal;
+ private AlarmManager mAlarmManager;
final IBinder mForegroundToken = new Binder();
private Handler mHandler;
@@ -268,6 +281,7 @@
private boolean mNotificationPulseEnabled;
// used as a mutex for access to all active notifications & listeners
+ final Object mNotificationLock = new Object();
final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
final ArrayMap<String, NotificationRecord> mNotificationsByKey =
@@ -372,7 +386,7 @@
private void loadPolicyFile() {
if (DBG) Slog.d(TAG, "loadPolicyFile");
- synchronized(mPolicyFile) {
+ synchronized (mPolicyFile) {
FileInputStream infile = null;
try {
@@ -491,7 +505,7 @@
@Override
public void onSetDisabled(int status) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mDisableNotificationEffects =
(status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
if (disableNotificationEffects(null) != null) {
@@ -519,7 +533,7 @@
@Override
public void onClearAll(int callingUid, int callingPid, int userId) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
cancelAllLocked(callingUid, callingPid, userId, REASON_DELEGATE_CANCEL_ALL, null,
/*includeCurrentProfiles*/ true);
}
@@ -527,7 +541,7 @@
@Override
public void onNotificationClick(int callingUid, int callingPid, String key) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r == null) {
Log.w(TAG, "No notification with key: " + key);
@@ -548,7 +562,7 @@
@Override
public void onNotificationActionClick(int callingUid, int callingPid, String key,
int actionIndex) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r == null) {
Log.w(TAG, "No notification with key: " + key);
@@ -584,7 +598,7 @@
@Override
public void clearEffects() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
if (DBG) Slog.d(TAG, "clearEffects");
clearSoundLocked();
clearVibrateLocked();
@@ -601,7 +615,7 @@
REASON_DELEGATE_ERROR, null);
long ident = Binder.clearCallingIdentity();
try {
- ActivityManager.getService().crashApplication(uid, initialPid, pkg,
+ ActivityManager.getService().crashApplication(uid, initialPid, pkg, -1,
"Bad notification posted from package " + pkg
+ ": " + message);
} catch (RemoteException e) {
@@ -612,7 +626,7 @@
@Override
public void onNotificationVisibilityChanged(NotificationVisibility[] newlyVisibleKeys,
NotificationVisibility[] noLongerVisibleKeys) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
for (NotificationVisibility nv : newlyVisibleKeys) {
NotificationRecord r = mNotificationsByKey.get(nv.key);
if (r == null) continue;
@@ -635,7 +649,7 @@
@Override
public void onNotificationExpansionChanged(String key,
boolean userAction, boolean expanded) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
r.stats.onExpansionChanged(userAction, expanded);
@@ -678,6 +692,29 @@
updateLightsLocked();
}
+ private final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ if (ACTION_NOTIFICATION_TIMEOUT.equals(action)) {
+ final NotificationRecord record;
+ synchronized (mNotificationLock) {
+ record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY));
+ }
+ if (record != null) {
+ cancelNotification(record.sbn.getUid(), record.sbn.getInitialPid(),
+ record.sbn.getPackageName(), record.sbn.getTag(),
+ record.sbn.getId(), 0,
+ Notification.FLAG_FOREGROUND_SERVICE, true, record.getUserId(),
+ REASON_TIMEOUT, null);
+ }
+ }
+ }
+ };
+
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -949,7 +986,8 @@
// TODO: Tests should call onStart instead once the methods above are removed.
@VisibleForTesting
- void init(IPackageManager packageManager, LightsManager lightsManager) {
+ void init(Looper looper, IPackageManager packageManager, PackageManager packageManagerClient,
+ LightsManager lightsManager, NotificationListeners notificationListeners) {
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -957,11 +995,13 @@
mAm = ActivityManager.getService();
mPackageManager = packageManager;
+ mPackageManagerClient = packageManagerClient;
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+ mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
- mHandler = new WorkerHandler();
+ mHandler = new WorkerHandler(looper);
mRankingThread.start();
String[] extractorNames;
try {
@@ -991,7 +1031,7 @@
new Intent(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT),
UserHandle.ALL, android.Manifest.permission.MANAGE_NOTIFICATIONS);
- synchronized(mNotificationList) {
+ synchronized (mNotificationLock) {
updateInterruptionFilterLocked();
}
}
@@ -1019,7 +1059,7 @@
mGroupHelper = new GroupHelper(new GroupHelper.Callback() {
@Override
public void addAutoGroup(String key) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
addAutogroupKeyLocked(key);
}
mRankingHandler.requestSort(false);
@@ -1027,7 +1067,7 @@
@Override
public void removeAutoGroup(String key) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
removeAutogroupKeyLocked(key);
}
mRankingHandler.requestSort(false);
@@ -1040,7 +1080,7 @@
@Override
public void removeAutoGroupSummary(int userId, String pkg) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
clearAutogroupSummaryLocked(userId, pkg);
}
}
@@ -1051,8 +1091,8 @@
syncBlockDb();
- // This is a MangedServices object that keeps track of the listeners.
- mListeners = new NotificationListeners();
+ // This is a ManagedServices object that keeps track of the listeners.
+ mListeners = notificationListeners;
// This is a MangedServices object that keeps track of the assistant.
mNotificationAssistants = new NotificationAssistants();
@@ -1126,6 +1166,10 @@
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
null);
+ IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
+ timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+ getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter);
+
mSettingsObserver = new SettingsObserver(mHandler);
mArchive = new Archive(resources.getInteger(
@@ -1134,7 +1178,8 @@
@Override
public void onStart() {
- init(AppGlobals.getPackageManager(), getLocalService(LightsManager.class));
+ init(Looper.myLooper(), AppGlobals.getPackageManager(), getContext().getPackageManager(),
+ getLocalService(LightsManager.class), new NotificationListeners());
publishBinderService(Context.NOTIFICATION_SERVICE, mService);
publishLocalService(NotificationManagerInternal.class, mInternalService);
}
@@ -1236,6 +1281,35 @@
sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
}
+ private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
+ boolean fromAssistant) {
+ if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+ // cancel
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
+ null);
+ }
+ if (fromAssistant) {
+ mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+ } else {
+ mRankingHelper.updateNotificationChannel(pkg, uid, channel);
+ }
+
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ for (int i = N - 1; i >= 0; --i) {
+ NotificationRecord r = mNotificationList.get(i);
+ if (channel.getId() != null && channel.getId().equals(r.getChannel().getId())) {
+ r.updateNotificationChannel(mRankingHelper.getNotificationChannel(
+ r.sbn.getPackageName(), r.getUser().getIdentifier(),
+ channel.getId(), false));
+ }
+ }
+ }
+ mRankingHandler.requestSort(true);
+ savePolicyFile();
+ }
+
private ArrayList<ComponentName> getSuppressors() {
ArrayList<ComponentName> names = new ArrayList<ComponentName>();
for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1514,6 +1588,19 @@
}
@Override
+ public boolean canShowBadge(String pkg, int uid) {
+ checkCallerIsSystem();
+ return mRankingHelper.canShowBadge(pkg, uid);
+ }
+
+ @Override
+ public void setShowBadge(String pkg, int uid, boolean showBadge) {
+ checkCallerIsSystem();
+ mRankingHelper.setShowBadge(pkg, uid, showBadge);
+ savePolicyFile();
+ }
+
+ @Override
public void createNotificationChannels(String pkg,
ParceledListSlice channelsList) throws RemoteException {
checkCallerIsSystemOrSameApp(pkg);
@@ -1559,15 +1646,8 @@
public void updateNotificationChannelForPackage(String pkg, int uid,
NotificationChannel channel) {
enforceSystemOrSystemUI("Caller not system or systemui");
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
- // cancel
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
- null);
- }
- mRankingHelper.updateNotificationChannel(pkg, uid, channel);
- mRankingHandler.requestSort(true);
- savePolicyFile();
+ Preconditions.checkNotNull(channel);
+ updateNotificationChannelInt(pkg, uid, channel, false);
}
@Override
@@ -1627,7 +1707,7 @@
// noteOp will check to make sure the callingPkg matches the uid
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
== AppOpsManager.MODE_ALLOWED) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
tmp = new StatusBarNotification[mNotificationList.size()];
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
@@ -1641,6 +1721,9 @@
/**
* Public API for getting a list of current notifications for the calling package/uid.
*
+ * Note that since notification posting is done asynchronously, this will not return
+ * notifications that are in the process of being posted.
+ *
* @returns A list of all the package's notifications, in natural order.
*/
@Override
@@ -1653,7 +1736,7 @@
final ArrayMap<String, StatusBarNotification> map
= new ArrayMap<>(mNotificationList.size() + mEnqueuedNotifications.size());
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final int N = mNotificationList.size();
for (int i = 0; i < N; i++) {
StatusBarNotification sbn = sanitizeSbn(pkg, userId,
@@ -1668,10 +1751,8 @@
map.put(sbn.getKey(), sbn);
}
}
- }
- synchronized (mEnqueuedNotifications) {
- final int N = mEnqueuedNotifications.size();
- for (int i = 0; i < N; i++) {
+ final int M = mEnqueuedNotifications.size();
+ for (int i = 0; i < M; i++) {
StatusBarNotification sbn = sanitizeSbn(pkg, userId,
mEnqueuedNotifications.get(i).sbn);
if (sbn != null) {
@@ -1696,7 +1777,6 @@
return new StatusBarNotification(
sbn.getPackageName(),
sbn.getOpPkg(),
- sbn.getNotificationChannel(),
sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
sbn.getNotification().clone(),
sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
@@ -1763,7 +1843,7 @@
final int callingPid = Binder.getCallingPid();
long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (keys != null) {
final int N = keys.length;
@@ -1826,7 +1906,7 @@
public void setNotificationsShownFromListener(INotificationListener token, String[] keys) {
long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (keys != null) {
final int N = keys.length;
@@ -1881,7 +1961,7 @@
long identity = Binder.clearCallingIdentity();
try {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, snoozeCriterionId, info);
+ snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, snoozeCriterionId, info);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1898,38 +1978,23 @@
long identity = Binder.clearCallingIdentity();
try {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, snoozeUntil, info);
+ snoozeNotificationInt(key, snoozeUntil, null, info);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
- * Allow an INotificationListener to snooze a single notification.
+ * Allows the notification assistant to un-snooze a single notification.
*
- * @param token The binder for the listener, to check that the caller is allowed
+ * @param token The binder for the assistant, to check that the caller is allowed
*/
@Override
- public void snoozeNotificationFromListener(INotificationListener token, String key) {
+ public void unsnoozeNotificationFromAssistant(INotificationListener token, String key) {
long identity = Binder.clearCallingIdentity();
try {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, info);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
- * Allow an INotificationListener to un-snooze a single notification.
- *
- * @param token The binder for the listener, to check that the caller is allowed
- */
- @Override
- public void unsnoozeNotificationFromListener(INotificationListener token, String key) {
- long identity = Binder.clearCallingIdentity();
- try {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ final ManagedServiceInfo info =
+ mNotificationAssistants.checkServiceTokenLocked(token);
unsnoozeNotificationInt(key, info);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1950,7 +2015,7 @@
final int callingPid = Binder.getCallingPid();
long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (info.supportsProfiles()) {
Log.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
@@ -1979,7 +2044,7 @@
@Override
public ParceledListSlice<StatusBarNotification> getActiveNotificationsFromListener(
INotificationListener token, String[] keys, int trim) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final boolean getKeys = keys != null;
final int N = getKeys ? keys.length : mNotificationList.size();
@@ -2000,11 +2065,41 @@
}
}
+ /**
+ * Allow an INotificationListener to request the list of outstanding snoozed notifications
+ * seen by the current user. Useful when starting up, after which point the listener
+ * callbacks should be used.
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ * @returns The return value will contain the notifications specified in keys, in that
+ * order, or if keys is null, all the notifications, in natural order.
+ */
+ @Override
+ public ParceledListSlice<StatusBarNotification> getSnoozedNotificationsFromListener(
+ INotificationListener token, int trim) {
+ synchronized (mNotificationLock) {
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ List<NotificationRecord> snoozedRecords = mSnoozeHelper.getSnoozed();
+ final int N = snoozedRecords.size();
+ final ArrayList<StatusBarNotification> list = new ArrayList<>(N);
+ for (int i=0; i < N; i++) {
+ final NotificationRecord r = snoozedRecords.get(i);
+ if (r == null) continue;
+ StatusBarNotification sbn = r.sbn;
+ if (!isVisibleToListener(sbn, info)) continue;
+ StatusBarNotification sbnToSend =
+ (trim == TRIM_FULL) ? sbn : sbn.cloneLight();
+ list.add(sbnToSend);
+ }
+ return new ParceledListSlice<>(list);
+ }
+ }
+
@Override
public void requestHintsFromListener(INotificationListener token, int hints) {
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final int disableEffectsMask = HINT_HOST_DISABLE_EFFECTS
| HINT_HOST_DISABLE_NOTIFICATION_EFFECTS
@@ -2025,7 +2120,7 @@
@Override
public int getHintsFromListener(INotificationListener token) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
return mListenerHints;
}
}
@@ -2035,7 +2130,7 @@
int interruptionFilter) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
mZenModeHelper.requestFromListener(info.component, interruptionFilter);
updateInterruptionFilterLocked();
@@ -2056,7 +2151,7 @@
@Override
public void setOnNotificationPostedTrimFromListener(INotificationListener token, int trim)
throws RemoteException {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (info == null) return;
mListeners.setOnNotificationPostedTrimLocked(info, trim);
@@ -2375,7 +2470,7 @@
enforceSystemOrSystemUI("grant notification policy access");
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mPolicyAccess.put(pkg, granted);
}
} finally {
@@ -2410,7 +2505,7 @@
Adjustment adjustment) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mEnqueuedNotifications) {
+ synchronized (mNotificationLock) {
mNotificationAssistants.checkServiceTokenLocked(token);
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
@@ -2432,7 +2527,7 @@
Adjustment adjustment) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mNotificationAssistants.checkServiceTokenLocked(token);
NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
applyAdjustment(n, adjustment);
@@ -2449,7 +2544,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mNotificationAssistants.checkServiceTokenLocked(token);
for (Adjustment adjustment : adjustments) {
NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
@@ -2490,15 +2585,9 @@
public void updateNotificationChannelFromAssistant(INotificationListener token, String pkg,
NotificationChannel channel) throws RemoteException {
ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
- // cancel
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- info.userid, REASON_CHANNEL_BANNED, null);
- }
+ Preconditions.checkNotNull(channel);
int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
- mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
- mRankingHandler.requestSort(true);
- savePolicyFile();
+ updateNotificationChannelInt(pkg, uid, channel, true);
}
@Override
@@ -2523,7 +2612,7 @@
final ArrayList<SnoozeCriterion> snoozeCriterionList =
adjustment.getSignals().getParcelableArrayList(Adjustment.KEY_SNOOZE_CRITERIA);
if (!TextUtils.isEmpty(overrideChannelId)) {
- n.setNotificationChannelOverride(mRankingHelper.getNotificationChannel(
+ n.updateNotificationChannel(mRankingHelper.getNotificationChannel(
n.sbn.getPackageName(), n.sbn.getUid(), overrideChannelId,
false /* includeDeleted */));
}
@@ -2555,9 +2644,8 @@
ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
if (summaries != null && summaries.containsKey(pkg)) {
// Clear summary.
- final NotificationRecord removed = mNotificationsByKey.get(summaries.remove(pkg));
+ final NotificationRecord removed = findNotificationByKeyLocked(summaries.remove(pkg));
if (removed != null) {
- mNotificationList.remove(removed);
cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED);
}
}
@@ -2566,7 +2654,7 @@
// Posts a 'fake' summary for a package that has exceeded the solo-notification limit.
private void createAutoGroupSummary(int userId, String pkg, String triggeringKey) {
NotificationRecord summaryRecord = null;
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
NotificationRecord notificationRecord = mNotificationsByKey.get(triggeringKey);
if (notificationRecord == null) {
// The notification could have been cancelled again already. A successive
@@ -2606,18 +2694,18 @@
final StatusBarNotification summarySbn =
new StatusBarNotification(adjustedSbn.getPackageName(),
adjustedSbn.getOpPkg(),
- adjustedSbn.getNotificationChannel(),
Integer.MAX_VALUE,
GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(),
adjustedSbn.getInitialPid(), summaryNotification,
adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY,
System.currentTimeMillis());
- summaryRecord = new NotificationRecord(getContext(), summarySbn);
+ summaryRecord = new NotificationRecord(getContext(), summarySbn,
+ notificationRecord.getChannel());
summaries.put(pkg, summarySbn.getKey());
}
}
if (summaryRecord != null) {
- synchronized (mEnqueuedNotifications) {
+ synchronized (mNotificationLock) {
mEnqueuedNotifications.add(summaryRecord);
}
mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord));
@@ -2672,7 +2760,7 @@
}
}
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
if (!zenOnly) {
N = mNotificationList.size();
if (N > 0) {
@@ -2710,19 +2798,17 @@
}
pw.println(" mArchive=" + mArchive.toString());
Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
- int i=0;
+ int j=0;
while (iter.hasNext()) {
final StatusBarNotification sbn = iter.next();
if (filter != null && !filter.matches(sbn)) continue;
pw.println(" " + sbn);
- if (++i >= 5) {
+ if (++j >= 5) {
if (iter.hasNext()) pw.println(" ...");
break;
}
}
- }
- synchronized (mEnqueuedNotifications) {
if (!zenOnly) {
N = mEnqueuedNotifications.size();
if (N > 0) {
@@ -2817,14 +2903,14 @@
public void removeForegroundServiceFlagFromNotification(String pkg, int notificationId,
int userId) {
checkCallerIsSystem();
- synchronized (mNotificationList) {
- int i = indexOfNotificationLocked(pkg, null, notificationId, userId);
- if (i < 0) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = findNotificationByListLocked(mNotificationList, pkg, null,
+ notificationId, userId);
+ if (r == null) {
Log.d(TAG, "stripForegroundServiceFlag: Could not find notification with "
+ "pkg=" + pkg + " / id=" + notificationId + " / userId=" + userId);
return;
}
- NotificationRecord r = mNotificationList.get(i);
StatusBarNotification sbn = r.sbn;
// NoMan adds flags FLAG_NO_CLEAR and FLAG_ONGOING_EVENT when it sees
// FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove FLAG_FOREGROUND_SERVICE,
@@ -2861,7 +2947,7 @@
// Fix the notification as best we can.
try {
- final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser(
+ final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
(userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, userId, notification);
@@ -2880,14 +2966,14 @@
final NotificationChannel channel = mRankingHelper.getNotificationChannelWithFallback(pkg,
callingUid, notification.getChannel(), false /* includeDeleted */);
final StatusBarNotification n = new StatusBarNotification(
- pkg, opPkg, channel, id, tag, callingUid, callingPid, notification,
+ pkg, opPkg, id, tag, callingUid, callingPid, notification,
user, null, System.currentTimeMillis());
// Limit the number of notifications that any given package except the android
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
- synchronized (mNotificationList) {
- if(mNotificationsByKey.get(n.getKey()) != null) {
+ synchronized (mNotificationLock) {
+ if (mNotificationsByKey.get(n.getKey()) != null) {
// this is an update, rate limit updates only
final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
if (appEnqueueRate > mMaxPackageEnqueueRate) {
@@ -2944,10 +3030,7 @@
Notification.PRIORITY_MAX);
// setup local book-keeping
- final NotificationRecord r = new NotificationRecord(getContext(), n);
- synchronized (mEnqueuedNotifications) {
- mEnqueuedNotifications.add(r);
- }
+ final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
mHandler.post(new EnqueueNotificationRunnable(userId, r));
idOut[0] = id;
@@ -2964,7 +3047,10 @@
@Override
public void run() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
+ mEnqueuedNotifications.add(r);
+ scheduleTimeoutLocked(r);
+
if (mSnoozeHelper.isSnoozed(userId, r.sbn.getPackageName(), r.getKey())) {
// TODO: log to event log
if (DBG) {
@@ -3059,9 +3145,9 @@
@Override
public void run() {
- try {
- NotificationRecord r = null;
- synchronized (mEnqueuedNotifications) {
+ synchronized (mNotificationLock) {
+ try {
+ NotificationRecord r = null;
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
@@ -3070,12 +3156,10 @@
break;
}
}
- }
- if (r == null) {
- Slog.e(TAG, "Cannot find enqueued record for key: " + key);
- return;
- }
- synchronized (mNotificationList) {
+ if (r == null) {
+ Slog.i(TAG, "Cannot find enqueued record for key: " + key);
+ return;
+ }
NotificationRecord old = mNotificationsByKey.get(key);
final StatusBarNotification n = r.sbn;
final Notification notification = n.getNotification();
@@ -3134,9 +3218,7 @@
}
buzzBeepBlinkLocked(r);
- }
- } finally {
- synchronized (mEnqueuedNotifications) {
+ } finally {
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
@@ -3193,8 +3275,23 @@
// notification was a summary and the new one isn't, or when the old
// notification was a summary and its group key changed.
if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
- cancelGroupChildrenLocked(old, callingUid, callingPid, null,
- REASON_GROUP_SUMMARY_CANCELED, false /* sendDelete */);
+ cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */);
+ }
+ }
+
+ @VisibleForTesting
+ void scheduleTimeoutLocked(NotificationRecord record) {
+ if (record.getNotification().getTimeout() > System.currentTimeMillis()) {
+ final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
+ REQUEST_CODE_TIMEOUT,
+ new Intent(ACTION_NOTIFICATION_TIMEOUT)
+ .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
+ .appendPath(record.getKey()).build())
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_KEY, record.getKey()),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mAlarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP, record.getNotification().getTimeout(), pi);
}
}
@@ -3463,7 +3560,7 @@
RankingReconsideration recon = (RankingReconsideration) message.obj;
recon.run();
boolean changed;
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final NotificationRecord record = mNotificationsByKey.get(recon.getKey());
if (record == null) {
return;
@@ -3491,16 +3588,20 @@
private void handleRankingSort(Message msg) {
if (!(msg.obj instanceof Boolean)) return;
boolean forceUpdate = ((Boolean) msg.obj == null) ? false : (boolean) msg.obj;
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final int N = mNotificationList.size();
+ // Any field that can change via one of the extractors or by the assistant
+ // needs to be added here.
ArrayList<String> orderBefore = new ArrayList<String>(N);
ArrayList<String> groupOverrideBefore = new ArrayList<>(N);
int[] visibilities = new int[N];
+ boolean[] showBadges = new boolean[N];
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
orderBefore.add(r.getKey());
groupOverrideBefore.add(r.sbn.getGroupKey());
visibilities[i] = r.getPackageVisibilityOverride();
+ showBadges[i] = r.canShowBadge();
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
@@ -3509,7 +3610,8 @@
if (forceUpdate
|| !orderBefore.get(i).equals(r.getKey())
|| visibilities[i] != r.getPackageVisibilityOverride()
- || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())) {
+ || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())
+ || showBadges[i] != r.canShowBadge()) {
scheduleSendRankingUpdate();
return;
}
@@ -3548,7 +3650,7 @@
}
private void handleSendRankingUpdate() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mListeners.notifyRankingUpdateLocked();
}
}
@@ -3567,19 +3669,23 @@
}
private void handleListenerHintsChanged(int hints) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mListeners.notifyListenerHintsChangedLocked(hints);
}
}
private void handleListenerInterruptionFilterChanged(int interruptionFilter) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mListeners.notifyInterruptionFilterChanged(interruptionFilter);
}
}
private final class WorkerHandler extends Handler
{
+ public WorkerHandler(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg)
{
@@ -3665,6 +3771,17 @@
}
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());
+ wasPosted = true;
+ } else {
+ mEnqueuedNotifications.remove(r);
+ wasPosted = false;
+ }
// Record caller.
recordCallerLocked(r);
@@ -3682,50 +3799,51 @@
}
}
- // status bar
- if (r.getNotification().getSmallIcon() != null) {
- r.isCanceled = true;
- mListeners.notifyRemovedLocked(r.sbn, reason);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mGroupHelper.onNotificationRemoved(r.sbn);
+ // Only cancel these if this notification actually got to be posted.
+ if (wasPosted) {
+ // status bar
+ if (r.getNotification().getSmallIcon() != null) {
+ r.isCanceled = true;
+ mListeners.notifyRemovedLocked(r.sbn, reason);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGroupHelper.onNotificationRemoved(r.sbn);
+ }
+ });
+ }
+
+ // sound
+ if (canceledKey.equals(mSoundNotificationKey)) {
+ mSoundNotificationKey = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+ if (player != null) {
+ player.stopAsync();
+ }
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- });
- }
+ }
- final String canceledKey = r.getKey();
-
- // sound
- if (canceledKey.equals(mSoundNotificationKey)) {
- mSoundNotificationKey = null;
- final long identity = Binder.clearCallingIdentity();
- try {
- final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
- if (player != null) {
- player.stopAsync();
+ // vibrate
+ if (canceledKey.equals(mVibrateNotificationKey)) {
+ mVibrateNotificationKey = null;
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mVibrator.cancel();
}
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(identity);
+ finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
- }
- // vibrate
- if (canceledKey.equals(mVibrateNotificationKey)) {
- mVibrateNotificationKey = null;
- long identity = Binder.clearCallingIdentity();
- try {
- mVibrator.cancel();
- }
- finally {
- Binder.restoreCallingIdentity(identity);
- }
+ // light
+ mLights.remove(canceledKey);
}
- // light
- mLights.remove(canceledKey);
-
// Record usage stats
// TODO: add unbundling stats?
switch (reason) {
@@ -3741,10 +3859,9 @@
break;
}
- mNotificationsByKey.remove(r.sbn.getKey());
String groupKey = r.getGroupKey();
NotificationRecord groupSummary = mSummaryByGroupKey.get(groupKey);
- if (groupSummary != null && groupSummary.getKey().equals(r.getKey())) {
+ if (groupSummary != null && groupSummary.getKey().equals(canceledKey)) {
mSummaryByGroupKey.remove(groupKey);
}
final ArrayMap<String, String> summaries = mAutobundledSummaries.get(r.sbn.getUserId());
@@ -3779,10 +3896,11 @@
if (DBG) EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag,
userId, mustHaveFlags, mustNotHaveFlags, reason, listenerName);
- synchronized (mNotificationList) {
- int index = indexOfNotificationLocked(pkg, tag, id, userId);
- if (index >= 0) {
- NotificationRecord r = mNotificationList.get(index);
+ synchronized (mNotificationLock) {
+ // Look for the notification, searching both the posted and enqueued lists.
+ NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
+ if (r != null) {
+ // The notification was found, check if it should be removed.
// Ideally we'd do this in the caller of this method. However, that would
// require the caller to also find the notification.
@@ -3797,13 +3915,13 @@
return;
}
- mNotificationList.remove(index);
-
+ // Cancel the notification.
cancelNotificationLocked(r, sendDelete, reason);
cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName,
- REASON_GROUP_SUMMARY_CANCELED, sendDelete);
+ sendDelete);
updateLightsLocked();
} else {
+ // No notification was found, assume that it is snoozed and cancel it.
final boolean wasSnoozed = mSnoozeHelper.cancel(userId, pkg, tag, id);
if (wasSnoozed) {
savePolicyFile();
@@ -3842,120 +3960,129 @@
* Cancels all notifications from a given package that have all of the
* {@code mustHaveFlags}.
*/
- boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
+ void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
ManagedServiceInfo listener) {
- String listenerName = listener == null ? null : listener.component.toShortString();
- EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
- pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
- listenerName);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ String listenerName = listener == null ? null : listener.component.toShortString();
+ EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
+ pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
+ listenerName);
- synchronized (mNotificationList) {
- final int N = mNotificationList.size();
- ArrayList<NotificationRecord> canceledNotifications = null;
- for (int i = N-1; i >= 0; --i) {
- NotificationRecord r = mNotificationList.get(i);
- if (!notificationMatchesUserId(r, userId)) {
- continue;
- }
- // Don't remove notifications to all, if there's no package name specified
- if (r.getUserId() == UserHandle.USER_ALL && pkg == null) {
- continue;
- }
- if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) {
- continue;
- }
- if ((r.getFlags() & mustNotHaveFlags) != 0) {
- continue;
- }
- if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
- continue;
- }
- if (channelId != null && !channelId.equals(r.getChannel().getId())) {
- continue;
- }
- if (canceledNotifications == null) {
- canceledNotifications = new ArrayList<>();
- }
- canceledNotifications.add(r);
+ // Why does this parameter exist? Do we actually want to execute the above if doit
+ // is false?
if (!doit) {
- return true;
+ return;
}
- mNotificationList.remove(i);
- cancelNotificationLocked(r, false, reason);
- }
- mSnoozeHelper.cancel(userId, pkg);
- if (doit && canceledNotifications != null) {
- final int M = canceledNotifications.size();
- for (int i = 0; i < M; i++) {
- cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
- listenerName, REASON_GROUP_SUMMARY_CANCELED, false /* sendDelete */);
+
+ synchronized (mNotificationLock) {
+ FlagChecker flagChecker = (int flags) -> {
+ if ((flags & mustHaveFlags) != mustHaveFlags) {
+ return false;
+ }
+ if ((flags & mustNotHaveFlags) != 0) {
+ return false;
+ }
+ return true;
+ };
+
+ cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+ pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+ false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
+ listenerName);
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
+ callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
+ flagChecker, false /*includeCurrentProfiles*/, userId,
+ false /*sendDelete*/, reason, listenerName);
+ mSnoozeHelper.cancel(userId, pkg);
}
}
- if (canceledNotifications != null) {
- updateLightsLocked();
+ });
+ }
+
+ private interface FlagChecker {
+ // Returns false if these flags do not pass the defined flag test.
+ public boolean apply(int flags);
+ }
+
+ private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
+ int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
+ String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
+ boolean sendDelete, int reason, String listenerName) {
+ ArrayList<NotificationRecord> canceledNotifications = null;
+ for (int i = notificationList.size() - 1; i >= 0; --i) {
+ NotificationRecord r = notificationList.get(i);
+ if (includeCurrentProfiles) {
+ if (!notificationMatchesCurrentProfiles(r, userId)) {
+ continue;
+ }
+ } else if (!notificationMatchesUserId(r, userId)) {
+ continue;
}
- return canceledNotifications != null;
+ // Don't remove notifications to all, if there's no package name specified
+ if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == UserHandle.USER_ALL) {
+ continue;
+ }
+ if (!flagChecker.apply(r.getFlags())) {
+ continue;
+ }
+ if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
+ continue;
+ }
+ if (channelId != null && !channelId.equals(r.getChannel().getId())) {
+ continue;
+ }
+
+ if (canceledNotifications == null) {
+ canceledNotifications = new ArrayList<>();
+ }
+ canceledNotifications.add(r);
+ cancelNotificationLocked(r, sendDelete, reason);
+ }
+ if (canceledNotifications != null) {
+ final int M = canceledNotifications.size();
+ for (int i = 0; i < M; i++) {
+ cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
+ listenerName, false /* sendDelete */);
+ }
+ updateLightsLocked();
}
}
- void snoozeNotificationInt(String key, String snoozeCriterionId, ManagedServiceInfo listener) {
+ void snoozeNotificationInt(String key, long until, String snoozeCriterionId,
+ ManagedServiceInfo listener) {
String listenerName = listener == null ? null : listener.component.toShortString();
- // TODO: write to event log
- if (DBG) {
- Slog.d(TAG, String.format("snooze event(%s, %s, %s)",
- key, snoozeCriterionId, listenerName));
- }
- synchronized (mNotificationList) {
- final NotificationRecord r = mNotificationsByKey.get(key);
- if (r != null) {
- mNotificationList.remove(r);
- cancelNotificationLocked(r, false, REASON_SNOOZED);
- mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn, snoozeCriterionId);
- updateLightsLocked();
- mSnoozeHelper.snooze(r);
- savePolicyFile();
- }
- }
- }
-
- void snoozeNotificationInt(String key, long until, ManagedServiceInfo listener) {
- String listenerName = listener == null ? null : listener.component.toShortString();
- // TODO: write to event log
- if (DBG) {
- Slog.d(TAG, String.format("snooze event(%s, %d, %s)", key, until, listenerName));
- }
- if (until < System.currentTimeMillis()) {
+ if (until < System.currentTimeMillis() && snoozeCriterionId == null) {
return;
}
- synchronized (mNotificationList) {
- final NotificationRecord r = mNotificationsByKey.get(key);
- if (r != null) {
- mNotificationList.remove(r);
- cancelNotificationLocked(r, false, REASON_SNOOZED);
- updateLightsLocked();
- mSnoozeHelper.snooze(r, until);
- savePolicyFile();
- }
- }
- }
-
- void snoozeNotificationInt(String key, ManagedServiceInfo listener) {
- String listenerName = listener == null ? null : listener.component.toShortString();
// TODO: write to event log
if (DBG) {
- Slog.d(TAG, String.format("snooze event(%s, %s)", key, listenerName));
+ Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, until, snoozeCriterionId,
+ listenerName));
}
- synchronized (mNotificationList) {
- final NotificationRecord r = mNotificationsByKey.get(key);
- if (r != null) {
- mNotificationList.remove(r);
- cancelNotificationLocked(r, false, REASON_SNOOZED);
- updateLightsLocked();
- mSnoozeHelper.snooze(r);
- savePolicyFile();
+ // Needs to post so that it can cancel notifications not yet enqueued.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mNotificationLock) {
+ final NotificationRecord r = findNotificationByKeyLocked(key);
+ if (r != null) {
+ cancelNotificationLocked(r, false, REASON_SNOOZED);
+ updateLightsLocked();
+ if (snoozeCriterionId != null) {
+ mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn,
+ snoozeCriterionId);
+ mSnoozeHelper.snooze(r);
+ } else {
+ mSnoozeHelper.snooze(r, until);
+ }
+ savePolicyFile();
+ }
+ }
}
- }
+ });
}
void unsnoozeNotificationInt(String key, ManagedServiceInfo listener) {
@@ -3970,47 +4097,40 @@
void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
ManagedServiceInfo listener, boolean includeCurrentProfiles) {
- String listenerName = listener == null ? null : listener.component.toShortString();
- EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
- null, userId, 0, 0, reason, listenerName);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mNotificationLock) {
+ String listenerName =
+ listener == null ? null : listener.component.toShortString();
+ EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
+ null, userId, 0, 0, reason, listenerName);
- ArrayList<NotificationRecord> canceledNotifications = null;
- final int N = mNotificationList.size();
- for (int i=N-1; i>=0; i--) {
- NotificationRecord r = mNotificationList.get(i);
- if (includeCurrentProfiles) {
- if (!notificationMatchesCurrentProfiles(r, userId)) {
- continue;
- }
- } else {
- if (!notificationMatchesUserId(r, userId)) {
- continue;
+ FlagChecker flagChecker = (int flags) -> {
+ if ((flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR))
+ != 0) {
+ return false;
+ }
+ return true;
+ };
+
+ cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+ null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
+ includeCurrentProfiles, userId, true /*sendDelete*/, reason,
+ listenerName);
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
+ callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
+ flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
+ reason, listenerName);
+ mSnoozeHelper.cancel(userId, includeCurrentProfiles);
}
}
-
- if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT
- | Notification.FLAG_NO_CLEAR)) == 0) {
- mNotificationList.remove(i);
- cancelNotificationLocked(r, true, reason);
- // Make a note so we can cancel children later.
- if (canceledNotifications == null) {
- canceledNotifications = new ArrayList<>();
- }
- canceledNotifications.add(r);
- }
- }
- mSnoozeHelper.cancel(userId, includeCurrentProfiles);
- int M = canceledNotifications != null ? canceledNotifications.size() : 0;
- for (int i = 0; i < M; i++) {
- cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
- listenerName, REASON_GROUP_SUMMARY_CANCELED, false /* sendDelete */);
- }
- updateLightsLocked();
+ });
}
// Warning: The caller is responsible for invoking updateLightsLocked().
private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
- String listenerName, int reason, boolean sendDelete) {
+ String listenerName, boolean sendDelete) {
Notification n = r.getNotification();
if (!n.isGroupSummary()) {
return;
@@ -4024,16 +4144,26 @@
return;
}
- final int N = mNotificationList.size();
- for (int i = N - 1; i >= 0; i--) {
- NotificationRecord childR = mNotificationList.get(i);
- StatusBarNotification childSbn = childR.sbn;
+ cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
+ sendDelete);
+ cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
+ listenerName, sendDelete);
+ }
+
+ private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
+ NotificationRecord parentNotification, int callingUid, int callingPid,
+ String listenerName, boolean sendDelete) {
+ final String pkg = parentNotification.sbn.getPackageName();
+ final int userId = parentNotification.getUserId();
+ final int reason = REASON_GROUP_SUMMARY_CANCELED;
+ for (int i = notificationList.size() - 1; i >= 0; i--) {
+ final NotificationRecord childR = notificationList.get(i);
+ final StatusBarNotification childSbn = childR.sbn;
if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
- childR.getGroupKey().equals(r.getGroupKey())
+ childR.getGroupKey().equals(parentNotification.getGroupKey())
&& (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
childSbn.getTag(), userId, 0, 0, reason, listenerName);
- mNotificationList.remove(i);
cancelNotificationLocked(childR, sendDelete, reason);
}
}
@@ -4082,19 +4212,49 @@
}
}
- // lock on mNotificationList
- int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
+ // Searches both enqueued and posted notifications by key.
+ // 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);
+ }
+ }
+ final int M = mEnqueuedNotifications.size();
+ for (int i = 0; i < M; i++) {
+ if (key.equals(mEnqueuedNotifications.get(i).getKey())) {
+ return mEnqueuedNotifications.get(i);
+ }
+ }
+ return null;
+ }
+
+ private NotificationRecord findNotificationLocked(String pkg, String tag, int id, int userId) {
+ NotificationRecord r;
+ if ((r = findNotificationByListLocked(mNotificationList, pkg, tag, id, userId)) != null) {
+ return r;
+ }
+ if ((r = findNotificationByListLocked(mEnqueuedNotifications, pkg, tag, id, userId))
+ != null) {
+ return r;
+ }
+ return null;
+ }
+
+ private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
+ String pkg, String tag, int id, int userId)
{
- ArrayList<NotificationRecord> list = mNotificationList;
final int len = list.size();
- for (int i=0; i<len; i++) {
+ for (int i = 0; i < len; i++) {
NotificationRecord r = list.get(i);
if (notificationMatchesUserId(r, userId) && r.sbn.getId() == id &&
TextUtils.equals(r.sbn.getTag(), tag) && r.sbn.getPackageName().equals(pkg)) {
- return i;
+ return r;
}
}
- return -1;
+ return null;
}
// lock on mNotificationList
@@ -4109,7 +4269,7 @@
}
private void updateNotificationPulse() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
updateLightsLocked();
}
}
@@ -4189,9 +4349,10 @@
Bundle visibilityOverrides = new Bundle();
Bundle suppressedVisualEffects = new Bundle();
Bundle explanation = new Bundle();
- Bundle overrideChannels = new Bundle();
+ Bundle channels = new Bundle();
Bundle overridePeople = new Bundle();
Bundle snoozeCriteria = new Bundle();
+ Bundle showBadge = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.sbn, info)) {
@@ -4213,9 +4374,10 @@
visibilityOverrides.putInt(key, record.getPackageVisibilityOverride());
}
overrideGroupKeys.putString(key, record.sbn.getOverrideGroupKey());
- overrideChannels.putParcelable(key, record.getChannel());
+ channels.putParcelable(key, record.getChannel());
overridePeople.putStringArrayList(key, record.getPeopleOverride());
snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
+ showBadge.putBoolean(key, record.canShowBadge());
}
final int M = keys.size();
String[] keysAr = keys.toArray(new String[M]);
@@ -4226,7 +4388,7 @@
}
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
- overrideChannels, overridePeople, snoozeCriteria);
+ channels, overridePeople, snoozeCriteria, showBadge);
}
private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
@@ -4413,7 +4575,7 @@
public void onServiceAdded(ManagedServiceInfo info) {
final INotificationListener listener = (INotificationListener) info.service;
final NotificationRankingUpdate update;
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
update = makeRankingUpdateLocked(info);
}
try {
@@ -4608,12 +4770,12 @@
}
}
- private boolean isListenerPackage(String packageName) {
+ public boolean isListenerPackage(String packageName) {
if (packageName == null) {
return false;
}
// TODO: clean up locking object later
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
for (final ManagedServiceInfo serviceInfo : mServices) {
if (packageName.equals(serviceInfo.component.getPackageName())) {
return true;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e8c3d97..8998128 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,7 +25,6 @@
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -42,6 +41,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
+import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.EventLogTags;
@@ -114,12 +114,14 @@
private Uri mSound;
private long[] mVibration;
private AudioAttributes mAttributes;
- private NotificationChannel mOverrideChannel;
+ private NotificationChannel mChannel;
private ArrayList<String> mPeopleOverride;
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ private boolean mShowBadge;
@VisibleForTesting
- public NotificationRecord(Context context, StatusBarNotification sbn)
+ public NotificationRecord(Context context, StatusBarNotification sbn,
+ NotificationChannel channel)
{
this.sbn = sbn;
mOriginalFlags = sbn.getNotification().flags;
@@ -128,6 +130,7 @@
mUpdateTimeMs = mCreationTimeMs;
mContext = context;
stats = new NotificationUsageStats.SingleNotificationStats();
+ mChannel = channel;
mPreChannelsNotification = isPreChannelsNotification();
mSound = calculateSound();
mVibration = calculateVibration();
@@ -154,7 +157,7 @@
private Uri calculateSound() {
final Notification n = sbn.getNotification();
- Uri sound = sbn.getNotificationChannel().getSound();
+ Uri sound = mChannel.getSound();
if (mPreChannelsNotification && (getChannel().getUserLockedFields()
& NotificationChannel.USER_LOCKED_SOUND) == 0) {
@@ -316,6 +319,7 @@
pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d",
notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
+ pw.println(prefix + " timeout=" + TimeUtils.formatForLogging(notification.getTimeout()));
if (notification.actions != null && notification.actions.length > 0) {
pw.println(prefix + " actions={");
final int N = notification.actions.length;
@@ -386,7 +390,8 @@
pw.println(prefix + " mSound= " + mSound);
pw.println(prefix + " mVibration= " + mVibration);
pw.println(prefix + " mAttributes= " + mAttributes);
- pw.println(prefix + " overrideChannel=" + getChannel());
+ pw.println(prefix + " mShowBadge=" + mShowBadge);
+ pw.println(prefix + " channel=" + getChannel());
if (getPeopleOverride() != null) {
pw.println(prefix + " overridePeople= " + TextUtils.join(",", getPeopleOverride()));
}
@@ -640,16 +645,24 @@
}
public NotificationChannel getChannel() {
- return mOverrideChannel == null ? sbn.getNotificationChannel() : mOverrideChannel;
+ return mChannel;
}
- protected void setNotificationChannelOverride(NotificationChannel channel) {
- mOverrideChannel = channel;
- if (mOverrideChannel != null) {
+ protected void updateNotificationChannel(NotificationChannel channel) {
+ if (channel != null) {
+ mChannel = channel;
calculateImportance();
}
}
+ public void setShowBadge(boolean showBadge) {
+ mShowBadge = showBadge;
+ }
+
+ public boolean canShowBadge() {
+ return mShowBadge;
+ }
+
public Uri getSound() {
return mSound;
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index c2cef09..492d5c6 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -22,6 +22,8 @@
void setImportance(String packageName, int uid, int importance);
int getImportance(String packageName, int uid);
+ void setShowBadge(String packageName, int uid, boolean showBadge);
+ boolean canShowBadge(String packageName, int uid);
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index e44fb7f..1861bcb 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -67,10 +67,12 @@
private static final String ATT_PRIORITY = "priority";
private static final String ATT_VISIBILITY = "visibility";
private static final String ATT_IMPORTANCE = "importance";
+ private static final String ATT_SHOW_BADGE = "show_badge";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
+ private static final boolean DEFAULT_SHOW_BADGE = true;
private final NotificationSignalExtractor[] mSignalExtractors;
private final NotificationComparator mPreliminaryComparator;
@@ -169,7 +171,8 @@
Record r = getOrCreateRecord(name, uid,
safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
- safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
+ safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
+ safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
// Channels
final int innerDepth = parser.getDepth();
@@ -215,11 +218,11 @@
private Record getOrCreateRecord(String pkg, int uid) {
return getOrCreateRecord(pkg, uid,
- DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY);
+ DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
}
private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
- int visibility) {
+ int visibility, boolean showBadge) {
final String key = recordKey(pkg, uid);
Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(key);
if (r == null) {
@@ -229,6 +232,7 @@
r.importance = importance;
r.priority = priority;
r.visibility = visibility;
+ r.showBadge = showBadge;
createDefaultChannelIfMissing(r);
if (r.uid == Record.UNKNOWN_UID) {
mRestoredWithoutUids.put(pkg, r);
@@ -298,7 +302,7 @@
}
final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE
|| r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY
- || r.channels.size() > 0;
+ || r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -311,6 +315,7 @@
if (r.visibility != DEFAULT_VISIBILITY) {
out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
}
+ out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
if (!forBackup) {
out.attribute(null, ATT_UID, Integer.toString(r.uid));
@@ -396,6 +401,12 @@
return Collections.binarySearch(notificationList, target, mFinalComparator);
}
+ private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+ final String value = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(value)) return defValue;
+ return Boolean.parseBoolean(value);
+ }
+
private static int safeInt(XmlPullParser parser, String att, int defValue) {
final String val = parser.getAttributeValue(null, att);
return tryParseInt(val, defValue);
@@ -419,6 +430,17 @@
}
@Override
+ public boolean canShowBadge(String packageName, int uid) {
+ return getOrCreateRecord(packageName, uid).showBadge;
+ }
+
+ @Override
+ public void setShowBadge(String packageName, int uid, boolean showBadge) {
+ getOrCreateRecord(packageName, uid).showBadge = showBadge;
+ updateConfig();
+ }
+
+ @Override
public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp) {
Preconditions.checkNotNull(pkg);
@@ -454,6 +476,9 @@
if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
}
+ if (!r.showBadge) {
+ channel.setShowBadge(false);
+ }
r.channels.put(channel.getId(), channel);
updateConfig();
}
@@ -672,7 +697,7 @@
final Record r = records.valueAt(i);
if (filter == null || filter.matches(r.pkg)) {
pw.print(prefix);
- pw.print(" ");
+ pw.print(" AppSettings: ");
pw.print(r.pkg);
pw.print(" (");
pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
@@ -689,6 +714,8 @@
pw.print(" visibility=");
pw.print(Notification.visibilityToString(r.visibility));
}
+ pw.print(" showBadge=");
+ pw.print(Boolean.toString(r.showBadge));
pw.println();
for (NotificationChannel channel : r.channels.values()) {
pw.print(prefix);
@@ -725,6 +752,9 @@
if (r.visibility != DEFAULT_VISIBILITY) {
record.put("visibility", Notification.visibilityToString(r.visibility));
}
+ if (r.showBadge != DEFAULT_SHOW_BADGE) {
+ record.put("showBadge", Boolean.valueOf(r.showBadge));
+ }
for (NotificationChannel channel : r.channels.values()) {
record.put("channel", channel.toJson());
}
@@ -838,6 +868,7 @@
int importance = DEFAULT_IMPORTANCE;
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
+ boolean showBadge = DEFAULT_SHOW_BADGE;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
}
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index e14700a..f2aff11 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -41,6 +41,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -98,6 +99,26 @@
return Collections.EMPTY_LIST;
}
+ protected List<NotificationRecord> getSnoozed() {
+ List<NotificationRecord> snoozedForUser = new ArrayList<>();
+ int[] userIds = mUserProfiles.getCurrentProfileIds();
+ final int N = userIds.length;
+ for (int i = 0; i < N; i++) {
+ final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
+ mSnoozedNotifications.get(userIds[i]);
+ if (snoozedPkgs != null) {
+ final int M = snoozedPkgs.size();
+ for (int j = 0; j < M; j++) {
+ final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
+ if (records != null) {
+ snoozedForUser.addAll(records.values());
+ }
+ }
+ }
+ }
+ return snoozedForUser;
+ }
+
/**
* Snoozes a notification and schedules an alarm to repost at that time.
*/
diff --git a/services/core/java/com/android/server/pm/EphemeralResolver.java b/services/core/java/com/android/server/pm/EphemeralResolver.java
index d735e72..96a0d18 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolver.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolver.java
@@ -78,6 +78,7 @@
int sequence) {
final String packageName;
final String splitName;
+ final int versionCode;
if (ephemeralResolveInfo != null) {
final ArrayList<EphemeralResolveInfo> ephemeralResolveInfoList =
new ArrayList<EphemeralResolveInfo>(1);
@@ -91,13 +92,16 @@
&& ephemeralIntentInfo.resolveInfo != null) {
packageName = ephemeralIntentInfo.resolveInfo.getPackageName();
splitName = ephemeralIntentInfo.splitName;
+ versionCode = ephemeralIntentInfo.resolveInfo.getVersionCode();
} else {
packageName = null;
splitName = null;
+ versionCode = -1;
}
} else {
packageName = null;
splitName = null;
+ versionCode = -1;
}
final Intent installerIntent = buildEphemeralInstallerIntent(
requestObj.launchIntent,
@@ -107,6 +111,7 @@
requestObj.userId,
packageName,
splitName,
+ versionCode,
requestObj.responseObj.token,
false /*needsPhaseTwo*/);
installerIntent.setComponent(new ComponentName(
@@ -123,7 +128,7 @@
*/
public static Intent buildEphemeralInstallerIntent(Intent launchIntent, Intent origIntent,
String callingPackage, String resolvedType, int userId, String ephemeralPackageName,
- String ephemeralSplitName, String token, boolean needsPhaseTwo) {
+ String ephemeralSplitName, int versionCode, String token, boolean needsPhaseTwo) {
// Construct the intent that launches the ephemeral installer
int flags = launchIntent.getFlags();
final Intent intent = new Intent();
@@ -181,6 +186,7 @@
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, ephemeralPackageName);
intent.putExtra(Intent.EXTRA_SPLIT_NAME, ephemeralSplitName);
+ intent.putExtra(Intent.EXTRA_VERSION_CODE, versionCode);
}
return intent;
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 0130e30..fc66bb3 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -295,6 +295,15 @@
}
}
+ public void removeIdmap(String overlayApkPath) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.removeIdmap(overlayApkPath);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public void rmdex(String codePath, String instructionSet) throws InstallerException {
assertValidInstructionSet(instructionSet);
if (!checkBeforeRemote()) return;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a74e141..b6611eb 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -219,11 +219,12 @@
/**
* Checks if the caller is in the same group as the userToCheck.
*/
- private void ensureInUserProfiles(UserHandle userToCheck, String message) {
- ensureInUserProfiles(userToCheck.getIdentifier(), message);
+ private void ensureInUserProfiles(
+ String callingPackage, UserHandle userToCheck, String message) {
+ ensureInUserProfiles(callingPackage, userToCheck.getIdentifier(), message);
}
- private void ensureInUserProfiles(int targetUserId, String message) {
+ private void ensureInUserProfiles(String callingPackage, int targetUserId, String message) {
final int callingUserId = injectCallingUserId();
if (targetUserId == callingUserId) return;
@@ -232,7 +233,12 @@
try {
UserInfo callingUserInfo = mUm.getUserInfo(callingUserId);
if (callingUserInfo.isManagedProfile()) {
- throw new SecurityException(message + " for another profile " + targetUserId);
+ // TODO: Make it SecurityException. See b/34650921
+ // throw new SecurityException(message + " for another profile " + targetUserId);
+
+ // TODO: Report caller package name.
+ Slog.wtfStack(TAG, message + " by " + callingPackage + " for another profile "
+ + targetUserId + " from " + callingUserId);
}
UserInfo targetUserInfo = mUm.getUserInfo(targetUserId);
@@ -281,9 +287,10 @@
}
@Override
- public ParceledListSlice<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
+ public ParceledListSlice<ResolveInfo> getLauncherActivities(String callingPackage,
+ String packageName, UserHandle user)
throws RemoteException {
- return queryActivitiesForUser(
+ return queryActivitiesForUser(callingPackage,
new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setPackage(packageName),
@@ -291,9 +298,10 @@
}
@Override
- public ActivityInfo resolveActivity(ComponentName component, UserHandle user)
+ public ActivityInfo resolveActivity(
+ String callingPackage, ComponentName component, UserHandle user)
throws RemoteException {
- ensureInUserProfiles(user, "Cannot resolve activity");
+ ensureInUserProfiles(callingPackage, user, "Cannot resolve activity");
if (!isUserEnabled(user)) {
return null;
}
@@ -311,15 +319,16 @@
}
@Override
- public ParceledListSlice getShortcutConfigActivities(String packageName, UserHandle user)
+ public ParceledListSlice getShortcutConfigActivities(
+ String callingPackage, String packageName, UserHandle user)
throws RemoteException {
- return queryActivitiesForUser(
+ return queryActivitiesForUser(callingPackage,
new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName), user);
}
- private ParceledListSlice<ResolveInfo> queryActivitiesForUser(Intent intent,
- UserHandle user) {
- ensureInUserProfiles(user, "Cannot retrieve activities");
+ private ParceledListSlice<ResolveInfo> queryActivitiesForUser(String callingPackage,
+ Intent intent, UserHandle user) {
+ ensureInUserProfiles(callingPackage, user, "Cannot retrieve activities");
if (!isUserEnabled(user)) {
return null;
}
@@ -358,9 +367,9 @@
}
@Override
- public boolean isPackageEnabled(String packageName, UserHandle user)
+ public boolean isPackageEnabled(String callingPackage, String packageName, UserHandle user)
throws RemoteException {
- ensureInUserProfiles(user, "Cannot check package");
+ ensureInUserProfiles(callingPackage, user, "Cannot check package");
if (!isUserEnabled(user)) {
return false;
}
@@ -379,9 +388,10 @@
}
@Override
- public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user)
+ public ApplicationInfo getApplicationInfo(
+ String callingPackage, String packageName, int flags, UserHandle user)
throws RemoteException {
- ensureInUserProfiles(user, "Cannot check package");
+ ensureInUserProfiles(callingPackage, user, "Cannot check package");
if (!isUserEnabled(user)) {
return null;
}
@@ -403,7 +413,7 @@
private void ensureShortcutPermission(@NonNull String callingPackage, int userId) {
verifyCallingPackage(callingPackage);
- ensureInUserProfiles(userId, "Cannot access shortcuts");
+ ensureInUserProfiles(callingPackage, userId, "Cannot access shortcuts");
if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
callingPackage)) {
@@ -479,7 +489,7 @@
public boolean startShortcut(String callingPackage, String packageName, String shortcutId,
Rect sourceBounds, Bundle startActivityOptions, int userId) {
verifyCallingPackage(callingPackage);
- ensureInUserProfiles(userId, "Cannot start activity");
+ ensureInUserProfiles(callingPackage, userId, "Cannot start activity");
if (!isUserEnabled(userId)) {
throw new IllegalStateException("Cannot start a shortcut for disabled profile "
@@ -530,9 +540,10 @@
}
@Override
- public boolean isActivityEnabled(ComponentName component, UserHandle user)
+ public boolean isActivityEnabled(
+ String callingPackage, ComponentName component, UserHandle user)
throws RemoteException {
- ensureInUserProfiles(user, "Cannot check component");
+ ensureInUserProfiles(callingPackage , user, "Cannot check component");
if (!isUserEnabled(user)) {
return false;
}
@@ -551,9 +562,10 @@
}
@Override
- public void startActivityAsUser(ComponentName component, Rect sourceBounds,
+ public void startActivityAsUser(String callingPackage,
+ ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
- ensureInUserProfiles(user, "Cannot start activity");
+ ensureInUserProfiles(callingPackage, user, "Cannot start activity");
if (!isUserEnabled(user)) {
throw new IllegalStateException("Cannot start activity for disabled profile " + user);
}
@@ -604,9 +616,9 @@
}
@Override
- public void showAppDetailsAsUser(ComponentName component, Rect sourceBounds,
- Bundle opts, UserHandle user) throws RemoteException {
- ensureInUserProfiles(user, "Cannot show app details");
+ public void showAppDetailsAsUser(String callingPackage, ComponentName component,
+ Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException {
+ ensureInUserProfiles(callingPackage, user, "Cannot show app details");
if (!isUserEnabled(user)) {
throw new IllegalStateException("Cannot show app details for disabled profile "
+ user);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6044561..63a5d14 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9391,8 +9391,7 @@
}
// A package name must be unique; don't allow duplicates
- if (mPackages.containsKey(pkg.packageName)
- || mSharedLibraries.containsKey(pkg.packageName)) {
+ if (mPackages.containsKey(pkg.packageName)) {
throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
"Application package " + pkg.packageName
+ " already installed. Skipping duplicate.");
@@ -17704,6 +17703,13 @@
return false;
}
+ try {
+ // update shared libraries for the newly re-installed system package
+ updateSharedLibrariesLPr(newPkg, null);
+ } catch (PackageManagerException e) {
+ Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
+ }
+
prepareAppDataAfterInstallLIF(newPkg);
// writer
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index aa421b1..49b96b0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -587,6 +587,7 @@
boolean listSystem = false, listThirdParty = false;
boolean listInstaller = false;
boolean showUid = false;
+ boolean showVersionCode = false;
int uid = -1;
int userId = UserHandle.USER_SYSTEM;
try {
@@ -620,6 +621,9 @@
case "-3":
listThirdParty = true;
break;
+ case "--show-versioncode":
+ showVersionCode = true;
+ break;
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
@@ -664,8 +668,11 @@
pw.print(info.applicationInfo.sourceDir);
pw.print("=");
}
- pw.print(info.packageName); pw.print( " versionCode:"
- + info.applicationInfo.versionCode);
+ pw.print(info.packageName);
+ if (showVersionCode) {
+ pw.print(" versionCode:");
+ pw.print(info.applicationInfo.versionCode);
+ }
if (listInstaller) {
pw.print(" installer=");
pw.print(mInterface.getInstallerPackageName(info.packageName));
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index ae709fe..56d679e 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1529,10 +1529,11 @@
if (UserHandle.getUserId(callingUid) != userId) {
throw new SecurityException("Invalid user-ID");
}
- if (injectGetPackageUid(packageName, userId) == callingUid) {
- return; // Caller is valid.
+ if (injectGetPackageUid(packageName, userId) != callingUid) {
+ throw new SecurityException("Calling package name mismatch");
}
- throw new SecurityException("Calling package name mismatch");
+ Preconditions.checkState(!isEphemeralApp(packageName, userId),
+ "Ephemeral apps can't use ShortcutManager");
}
// Overridden in unit tests to execute r synchronously.
@@ -3073,6 +3074,10 @@
return (ai != null) && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
}
+ private static boolean isEphemeralApp(@Nullable ApplicationInfo ai) {
+ return (ai != null) && ai.isEphemeralApp();
+ }
+
private static boolean isInstalled(@Nullable PackageInfo pi) {
return (pi != null) && isInstalled(pi.applicationInfo);
}
@@ -3097,6 +3102,10 @@
return getApplicationInfo(packageName, userId) != null;
}
+ boolean isEphemeralApp(String packageName, int userId) {
+ return isEphemeralApp(getApplicationInfo(packageName, userId));
+ }
+
@Nullable
XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1eb8b94..984120f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -33,12 +33,10 @@
import android.app.KeyguardManager;
import android.app.PendingIntent;
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.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
@@ -2347,6 +2345,12 @@
}
@Override
+ public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+ checkManageOrCreateUsersPermission("Only the system can remove users");
+ return removeUserUnchecked(userHandle);
+ }
+
+ @Override
public UserInfo createUser(String name, int flags) {
checkManageOrCreateUsersPermission(flags);
return createUserInternal(name, flags, UserHandle.USER_NULL);
@@ -3133,8 +3137,6 @@
applyUserRestrictionsLR(userId);
}
}
-
- maybeInitializeDemoMode(userId);
}
/**
@@ -3173,29 +3175,6 @@
scheduleWriteUser(userData);
}
- private void maybeInitializeDemoMode(int userId) {
- if (UserManager.isDeviceInDemoMode(mContext) && userId != UserHandle.USER_SYSTEM) {
- String demoLauncher =
- mContext.getResources().getString(
- com.android.internal.R.string.config_demoModeLauncherComponent);
- if (!TextUtils.isEmpty(demoLauncher)) {
- ComponentName componentToEnable = ComponentName.unflattenFromString(demoLauncher);
- String demoLauncherPkg = componentToEnable.getPackageName();
- try {
- final IPackageManager iPm = AppGlobals.getPackageManager();
- iPm.setComponentEnabledSetting(componentToEnable,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0,
- /* userId= */ userId);
- iPm.setApplicationEnabledSetting(demoLauncherPkg,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0,
- /* userId= */ userId, null);
- } catch (RemoteException re) {
- // Internal, shouldn't happen
- }
- }
- }
- }
-
/**
* Returns the next available user id, filling in any holes in the ids.
*/
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c25ad46..f36054f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5896,9 +5896,8 @@
&& (result & ACTION_PASS_TO_USER) == 0) {
// If we are in call but we decided not to pass the key to
// the application, just pass it to the session service.
-
- MediaSessionLegacyHelper.getHelper(mContext)
- .sendVolumeKeyEvent(event, false);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
break;
}
}
@@ -5911,8 +5910,8 @@
// If we aren't passing to the user and no one else
// handled it send it to the session manager to
// figure out.
- MediaSessionLegacyHelper.getHelper(mContext)
- .sendVolumeKeyEvent(event, true);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
}
break;
}
diff --git a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
index 22299df..0094ab5 100644
--- a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
+++ b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
@@ -18,6 +18,7 @@
import android.content.pm.PackageStats;
import android.os.Environment;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
@@ -118,7 +119,7 @@
long appSizeSum = 0L;
long cacheSizeSum = 0L;
boolean isExternal = Environment.isExternalStorageEmulated();
- for (Map.Entry<String, PackageStats> entry : mergePackagesAcrossUsers().entrySet()) {
+ for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) {
PackageStats stat = entry.getValue();
long appSize = stat.codeSize + stat.dataSize;
long cacheSize = stat.cacheSize;
@@ -141,14 +142,17 @@
}
/**
- * A given package may exist for multiple users with distinct sizes. This function merges
- * the duplicated packages together and sums up their sizes to get the actual totals for the
- * package.
+ * A given package may exist for multiple users with distinct sizes. This function filters
+ * the packages that do not belong to user 0 out to ensure that we get good stats for a subset.
* @return A mapping of package name to merged package stats.
*/
- private ArrayMap<String, PackageStats> mergePackagesAcrossUsers() {
+ private ArrayMap<String, PackageStats> filterOnlyPrimaryUser() {
ArrayMap<String, PackageStats> packageMap = new ArrayMap<>();
for (PackageStats stat : mPackageStats) {
+ if (stat.userHandle != UserHandle.USER_SYSTEM) {
+ continue;
+ }
+
PackageStats existingStats = packageMap.get(stat.packageName);
if (existingStats != null) {
existingStats.cacheSize += stat.cacheSize;
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 02b46ec..4fd51b2 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -35,6 +35,7 @@
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.Log;
+import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewZygote;
@@ -271,6 +272,12 @@
}
@Override
+ public List<UserPackage> getPackageInfoForProviderAllUsers(Context context,
+ WebViewProviderInfo configInfo) {
+ return UserPackage.getPackageInfosAllUsers(context, configInfo.packageName, PACKAGE_FLAGS);
+ }
+
+ @Override
public int getMultiProcessSetting(Context context) {
return Settings.Global.getInt(context.getContentResolver(),
Settings.Global.WEBVIEW_MULTIPROCESS, 0);
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index fd137eb..b06f829 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -20,8 +20,11 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
+import android.webkit.UserPackage;
import android.webkit.WebViewProviderInfo;
+import java.util.List;
+
/**
* System interface for the WebViewUpdateService.
* This interface provides a way to test the WebView preparation mechanism - during normal use this
@@ -49,6 +52,14 @@
public boolean systemIsDebuggable();
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException;
+ /**
+ * Get the PackageInfos of all users for the package represented by {@param configInfo}.
+ * @return an array of UserPackages for a certain package, each UserPackage being belonging to a
+ * certain user. The returned array can contain null PackageInfos if the given package
+ * is uninstalled for some user.
+ */
+ public List<UserPackage> getPackageInfoForProviderAllUsers(Context context,
+ WebViewProviderInfo configInfo);
public int getMultiProcessSetting(Context context);
public void setMultiProcessSetting(Context context, int value);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 311570e..4a105e1 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -94,6 +94,9 @@
case Intent.ACTION_USER_ADDED:
mImpl.handleNewUser(userId);
break;
+ case Intent.ACTION_USER_REMOVED:
+ mImpl.handleUserRemoved(userId);
+ break;
}
}
};
@@ -112,6 +115,7 @@
IntentFilter userAddedFilter = new IntentFilter();
userAddedFilter.addAction(Intent.ACTION_USER_ADDED);
+ userAddedFilter.addAction(Intent.ACTION_USER_REMOVED);
getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL,
userAddedFilter, null /* broadcast permission */, null /* handler */);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index edfb11c..f016830 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -24,6 +24,7 @@
import android.os.UserHandle;
import android.util.Base64;
import android.util.Slog;
+import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
@@ -100,32 +101,41 @@
private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
for (WebViewProviderInfo provider : providers) {
if (provider.availableByDefault && !provider.isFallback) {
- try {
- PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(provider);
- if (isInstalledPackage(packageInfo) && isEnabledPackage(packageInfo)
- && mWebViewUpdater.isValidProvider(provider, packageInfo)) {
- return true;
- }
- } catch (NameNotFoundException e) {
- // A non-existent provider is neither valid nor enabled
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages) &&
+ // Checking validity of the package for the system user (rather than all
+ // users) since package validity depends not on the user but on the package
+ // itself.
+ mWebViewUpdater.isValidProvider(provider,
+ userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo())) {
+ return true;
}
}
}
return false;
}
- /**
- * Called when a new user has been added to update the state of its fallback package.
- */
void handleNewUser(int userId) {
- if (!mSystemInterface.isFallbackLogicEnabled()) return;
+ handleUserChange();
+ }
- WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
- WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
- if (fallbackProvider == null) return;
+ void handleUserRemoved(int userId) {
+ handleUserChange();
+ }
- mSystemInterface.enablePackageForUser(fallbackProvider.packageName,
- !existsValidNonFallbackProvider(webviewProviders), userId);
+ /**
+ * Called when a user was added or removed to ensure fallback logic and WebView preparation are
+ * triggered. This has to be done since the WebView package we use depends on the enabled-state
+ * of packages for all users (so adding or removing a user might cause us to change package).
+ */
+ private void handleUserChange() {
+ if (mSystemInterface.isFallbackLogicEnabled()) {
+ updateFallbackState(mSystemInterface.getWebViewPackages());
+ }
+ // Potentially trigger package-changing logic.
+ mWebViewUpdater.updateCurrentWebViewPackage(null);
}
void notifyRelroCreationCompleted() {
@@ -141,7 +151,7 @@
}
WebViewProviderInfo[] getValidWebViewPackages() {
- return mWebViewUpdater.getValidAndInstalledWebViewPackages();
+ return mWebViewUpdater.getValidWebViewPackages();
}
WebViewProviderInfo[] getWebViewPackages() {
@@ -160,7 +170,7 @@
if (!mSystemInterface.isFallbackLogicEnabled()) return;
WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
- updateFallbackState(webviewProviders, true);
+ updateFallbackState(webviewProviders);
}
/**
@@ -185,35 +195,23 @@
}
}
if (!changedPackageAvailableByDefault) return;
- updateFallbackState(webviewProviders, false);
+ updateFallbackState(webviewProviders);
}
- private void updateFallbackState(WebViewProviderInfo[] webviewProviders, boolean isBoot) {
+ private void updateFallbackState(WebViewProviderInfo[] webviewProviders) {
// If there exists a valid and enabled non-fallback package - disable the fallback
// package, otherwise, enable it.
WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
if (fallbackProvider == null) return;
boolean existsValidNonFallbackProvider = existsValidNonFallbackProvider(webviewProviders);
- boolean isFallbackEnabled = false;
- try {
- isFallbackEnabled = isEnabledPackage(
- mSystemInterface.getPackageInfoForProvider(fallbackProvider));
- } catch (NameNotFoundException e) {
- // No fallback package installed -> early out.
- return;
- }
-
- if (existsValidNonFallbackProvider
- // During an OTA the primary user's WebView state might differ from other users', so
- // ignore the state of that user during boot.
- && (isFallbackEnabled || isBoot)) {
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext, fallbackProvider);
+ if (existsValidNonFallbackProvider && !isDisabledForAllUsers(userPackages)) {
mSystemInterface.uninstallAndDisablePackageForAllUsers(mContext,
fallbackProvider.packageName);
} else if (!existsValidNonFallbackProvider
- // During an OTA the primary user's WebView state might differ from other users', so
- // ignore the state of that user during boot.
- && (!isFallbackEnabled || isBoot)) {
+ && !isInstalledAndEnabledForAllUsers(userPackages)) {
// Enable the fallback package for all users.
mSystemInterface.enablePackageForAllUsers(mContext,
fallbackProvider.packageName, true);
@@ -376,38 +374,8 @@
* or the replacement are done).
*/
public String changeProviderAndSetting(String newProviderName) {
- PackageInfo oldPackage = null;
- PackageInfo newPackage = null;
- boolean providerChanged = false;
- synchronized(mLock) {
- oldPackage = mCurrentWebViewPackage;
- mSystemInterface.updateUserSetting(mContext, newProviderName);
-
- try {
- newPackage = findPreferredWebViewPackage();
- providerChanged = (oldPackage == null)
- || !newPackage.packageName.equals(oldPackage.packageName);
- } catch (WebViewPackageMissingException e) {
- mCurrentWebViewPackage = null;
- Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView " +
- "package " + e);
- // If we don't perform the user change but don't have an installed WebView
- // package, we will have changed the setting and it will be used when a package
- // is available.
- return "";
- }
- // Perform the provider change if we chose a new provider
- if (providerChanged) {
- onWebViewProviderChanged(newPackage);
- }
- }
- // Kill apps using the old provider only if we changed provider
- if (providerChanged && oldPackage != null) {
- mSystemInterface.killPackageDependents(oldPackage.packageName);
- }
- // Return the new provider, this is not necessarily the one we were asked to switch to
- // But the persistent setting will now be pointing to the provider we were asked to
- // switch to anyway
+ PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
+ if (newPackage == null) return "";
return newPackage.packageName;
}
@@ -437,15 +405,14 @@
}
}
- private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos(boolean onlyInstalled) {
+ private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
List<ProviderAndPackageInfo> providers = new ArrayList<>();
for(int n = 0; n < allProviders.length; n++) {
try {
PackageInfo packageInfo =
mSystemInterface.getPackageInfoForProvider(allProviders[n]);
- if ((!onlyInstalled || isInstalledPackage(packageInfo))
- && isValidProvider(allProviders[n], packageInfo)) {
+ if (isValidProvider(allProviders[n], packageInfo)) {
providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
}
} catch (NameNotFoundException e) {
@@ -458,9 +425,8 @@
/**
* Fetch only the currently valid WebView packages.
**/
- public WebViewProviderInfo[] getValidAndInstalledWebViewPackages() {
- ProviderAndPackageInfo[] providersAndPackageInfos =
- getValidWebViewPackagesAndInfos(true /* only fetch installed packages */);
+ public WebViewProviderInfo[] getValidWebViewPackages() {
+ ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
WebViewProviderInfo[] providers =
new WebViewProviderInfo[providersAndPackageInfos.length];
for(int n = 0; n < providersAndPackageInfos.length; n++) {
@@ -487,39 +453,49 @@
*
*/
private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
- ProviderAndPackageInfo[] providers =
- getValidWebViewPackagesAndInfos(false /* onlyInstalled */);
+ ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
- // If the user has chosen provider, use that
+ // If the user has chosen provider, use that (if it's installed and enabled for all
+ // users).
for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.packageName.equals(userChosenProvider)
- && isInstalledPackage(providerAndPackage.packageInfo)
- && isEnabledPackage(providerAndPackage.packageInfo)) {
- return providerAndPackage.packageInfo;
+ if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ providerAndPackage.provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages)) {
+ return providerAndPackage.packageInfo;
+ }
}
}
// User did not choose, or the choice failed; use the most stable provider that is
- // installed and enabled for the device owner, and available by default (not through
+ // installed and enabled for all users, and available by default (not through
// user choice).
for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.availableByDefault
- && isInstalledPackage(providerAndPackage.packageInfo)
- && isEnabledPackage(providerAndPackage.packageInfo)) {
- return providerAndPackage.packageInfo;
+ if (providerAndPackage.provider.availableByDefault) {
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ providerAndPackage.provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages)) {
+ return providerAndPackage.packageInfo;
+ }
}
}
// Could not find any installed and enabled package either, use the most stable and
// default-available provider.
+ // TODO(gsennton) remove this when we have a functional WebView stub.
for (ProviderAndPackageInfo providerAndPackage : providers) {
if (providerAndPackage.provider.availableByDefault) {
return providerAndPackage.packageInfo;
}
}
+ // This should never happen during normal operation (only with modified system images).
mAnyWebViewInstalled = false;
throw new WebViewPackageMissingException("Could not find a loadable WebView package");
}
@@ -702,6 +678,48 @@
mAnyWebViewInstalled));
}
}
+
+ /**
+ * Update the current WebView package.
+ * @param newProviderName the package to switch to, null if no package has been explicitly
+ * chosen.
+ */
+ public PackageInfo updateCurrentWebViewPackage(String newProviderName) {
+ PackageInfo oldPackage = null;
+ PackageInfo newPackage = null;
+ boolean providerChanged = false;
+ synchronized(mLock) {
+ oldPackage = mCurrentWebViewPackage;
+
+ if (newProviderName != null) {
+ mSystemInterface.updateUserSetting(mContext, newProviderName);
+ }
+
+ try {
+ newPackage = findPreferredWebViewPackage();
+ providerChanged = (oldPackage == null)
+ || !newPackage.packageName.equals(oldPackage.packageName);
+ } catch (WebViewPackageMissingException e) {
+ // If updated the Setting but don't have an installed WebView package, the
+ // Setting will be used when a package is available.
+ mCurrentWebViewPackage = null;
+ Slog.e(TAG, "Couldn't find WebView package to use " + e);
+ return null;
+ }
+ // Perform the provider change if we chose a new provider
+ if (providerChanged) {
+ onWebViewProviderChanged(newPackage);
+ }
+ }
+ // Kill apps using the old provider only if we changed provider
+ if (providerChanged && oldPackage != null) {
+ mSystemInterface.killPackageDependents(oldPackage.packageName);
+ }
+ // Return the new provider, this is not necessarily the one we were asked to switch to,
+ // but the persistent setting will now be pointing to the provider we were asked to
+ // switch to anyway.
+ return newPackage;
+ }
}
private static boolean providerHasValidSignature(WebViewProviderInfo provider,
@@ -730,20 +748,27 @@
}
/**
- * Returns whether the given package is enabled.
- * This state can be changed by the user from Settings->Apps
+ * Return true iff {@param packageInfos} point to only installed and enabled packages.
+ * The given packages {@param packageInfos} should all be pointing to the same package, but each
+ * PackageInfo representing a different user's package.
*/
- private static boolean isEnabledPackage(PackageInfo packageInfo) {
- return packageInfo.applicationInfo.enabled;
+ private static boolean isInstalledAndEnabledForAllUsers(
+ List<UserPackage> userPackages) {
+ for (UserPackage userPackage : userPackages) {
+ if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
+ return false;
+ }
+ }
+ return true;
}
- /**
- * Return true if the package is installed and not hidden
- */
- private static boolean isInstalledPackage(PackageInfo packageInfo) {
- return (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
- && ((packageInfo.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0));
+ private static boolean isDisabledForAllUsers(List<UserPackage> userPackages) {
+ for (UserPackage userPackage : userPackages) {
+ if (userPackage.getPackageInfo() != null && userPackage.isEnabledPackage()) {
+ return false;
+ }
+ }
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 0436139..27e0f29 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -56,7 +56,7 @@
private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
private final IApplicationToken mToken;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler;
private final Runnable mOnWindowsDrawn = () -> {
if (mListener == null) {
@@ -186,6 +186,7 @@
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
WindowManagerService service) {
super(listener, service);
+ mHandler = new Handler(service.mH.getLooper());
mToken = token;
synchronized(mWindowMap) {
AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index ac9859d..079dc8f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -375,6 +375,7 @@
// affected.
mService.getDefaultDisplayContentLocked().getDockedDividerController()
.notifyAppVisibilityChanged();
+ mService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible);
}
}
@@ -674,7 +675,7 @@
// well there is no point now.
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Nulling last startingData");
startingData = null;
- } else if (mChildren.size() == 1 && startingSurface != null) {
+ } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
// If this is the last window except for a starting transition window,
// we need to get rid of the starting transition.
if (getController() != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3958510..3a74ded 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -57,6 +57,7 @@
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
@@ -69,6 +70,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
@@ -634,6 +636,13 @@
*/
DisplayContent(Display display, WindowManagerService service,
WindowLayersController layersController, WallpaperController wallpaperController) {
+
+ if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
+ throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+ + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
+ + " new=" + display);
+ }
+
mDisplay = display;
mDisplayId = display.getDisplayId();
mLayersController = layersController;
@@ -957,75 +966,43 @@
out.set(mContentRect);
}
- /**
- * Adds the stack to this display.
- * @see WindowManagerService#addStackToDisplay(int, int, boolean)
- */
- Rect addStackToDisplay(int stackId, boolean onTop) {
- boolean attachedToDisplay = false;
- TaskStack stack = mService.mStackIdToStack.get(stackId);
- if (stack == null) {
- if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
- + mDisplayId);
+ TaskStack addStackToDisplay(int stackId, boolean onTop) {
+ if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
+ + mDisplayId);
- stack = getStackById(stackId);
- if (stack != null) {
- // It's already attached to the display...clear mDeferRemoval and move stack to
- // appropriate z-order on display as needed.
- stack.mDeferRemoval = false;
- // We're not moving the display to front when we're adding stacks, only when
- // requested to change the position of stack explicitly.
- mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
- false /* includingParents */);
- attachedToDisplay = true;
- } else {
- stack = new TaskStack(mService, stackId);
- }
-
- mService.mStackIdToStack.put(stackId, stack);
- if (stackId == DOCKED_STACK_ID) {
- mDividerControllerLocked.notifyDockedStackExistsChanged(true);
- }
+ TaskStack stack = getStackById(stackId);
+ if (stack != null) {
+ // It's already attached to the display...clear mDeferRemoval and move stack to
+ // appropriate z-order on display as needed.
+ stack.mDeferRemoval = false;
+ // We're not moving the display to front when we're adding stacks, only when
+ // requested to change the position of stack explicitly.
+ mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
+ false /* includingParents */);
} else {
- final DisplayContent currentDC = stack.getDisplayContent();
- if (currentDC != null) {
- throw new IllegalStateException("Trying to add stackId=" + stackId
- + "to displayId=" + mDisplayId + ", but it's already attached to displayId="
- + currentDC.getDisplayId());
- }
- }
-
- if (!attachedToDisplay) {
+ stack = new TaskStack(mService, stackId);
mTaskStackContainers.addStackToDisplay(stack, onTop);
}
- if (stack.getRawFullscreen()) {
- return null;
+ if (stackId == DOCKED_STACK_ID) {
+ mDividerControllerLocked.notifyDockedStackExistsChanged(true);
}
- final Rect bounds = new Rect();
- stack.getRawBounds(bounds);
- return bounds;
+ return stack;
}
- /** Removes the stack from the display and prepares for changing the parent. */
- private void removeStackFromDisplay(TaskStack stack) {
- mTaskStackContainers.removeStackFromDisplay(stack);
- }
-
- /** Moves the stack to this display and returns the updated bounds. */
- Rect moveStackToDisplay(TaskStack stack) {
- final DisplayContent currentDisplayContent = stack.getDisplayContent();
- if (currentDisplayContent == null) {
+ void moveStackToDisplay(TaskStack stack) {
+ final DisplayContent prevDc = stack.getDisplayContent();
+ if (prevDc == null) {
throw new IllegalStateException("Trying to move stackId=" + stack.mStackId
+ " which is not currently attached to any display");
}
- if (stack.getDisplayContent().getDisplayId() == mDisplayId) {
+ if (prevDc.getDisplayId() == mDisplayId) {
throw new IllegalArgumentException("Trying to move stackId=" + stack.mStackId
+ " to its current displayId=" + mDisplayId);
}
- currentDisplayContent.removeStackFromDisplay(stack);
- return addStackToDisplay(stack.mStackId, true /* onTop */);
+ prevDc.mTaskStackContainers.removeStackFromDisplay(stack);
+ mTaskStackContainers.addStackToDisplay(stack, true /* onTop */);
}
@Override
@@ -1172,7 +1149,7 @@
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
mDimLayerController.close();
- if (mDisplayId == DEFAULT_DISPLAY) {
+ if (mDisplayId == DEFAULT_DISPLAY && mService.canDispatchPointerEvents()) {
mService.unregisterPointerEventListener(mTapDetector);
mService.unregisterPointerEventListener(mService.mMousePositionTracker);
}
@@ -1250,7 +1227,7 @@
final WindowState imeWin = mService.mInputMethodWindow;
final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
&& !mDividerControllerLocked.isImeHideRequested();
- final boolean dockVisible = mService.isStackVisibleLocked(DOCKED_STACK_ID);
+ final boolean dockVisible = isStackVisible(DOCKED_STACK_ID);
final TaskStack imeTargetStack = mService.getImeFocusStackLocked();
final int imeDockSide = (dockVisible && imeTargetStack != null) ?
imeTargetStack.getDockSide() : DOCKED_INVALID;
@@ -1447,7 +1424,7 @@
* @return The docked stack, but only if it is visible, and {@code null} otherwise.
*/
TaskStack getDockedStackLocked() {
- final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+ final TaskStack stack = getStackById(DOCKED_STACK_ID);
return (stack != null && stack.isVisible()) ? stack : null;
}
@@ -1456,7 +1433,7 @@
* visible.
*/
TaskStack getDockedStackIgnoringVisibility() {
- return mService.mStackIdToStack.get(DOCKED_STACK_ID);
+ return getStackById(DOCKED_STACK_ID);
}
/** Find the visible, touch-deliverable window under the given point */
@@ -1532,6 +1509,9 @@
}
}
+ // TODO: This should probably be called any time a visual change is made to the hierarchy like
+ // moving containers or resizing them. Need to investigate the best way to have it automatically
+ // happen so we don't run into issues with programmers forgetting to do it.
void layoutAndAssignWindowLayersIfNeeded() {
mService.mWindowsChanged = true;
setLayoutNeeded();
@@ -2282,7 +2262,7 @@
final boolean foundTargetWs =
(w.mAppToken != null && w.mAppToken.token == appToken)
|| (mScreenshotApplicationState.appWin != null && wallpaperOnly);
- if (foundTargetWs && w.isDisplayedLw() && winAnim.getShown()) {
+ if (foundTargetWs && winAnim.getShown()) {
mScreenshotApplicationState.screenshotReady = true;
}
@@ -2441,6 +2421,27 @@
}
}
+ void setExitingTokensHasVisible(boolean hasVisible) {
+ for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
+ mExitingTokens.get(i).hasVisible = hasVisible;
+ }
+
+ // Initialize state of exiting applications.
+ mTaskStackContainers.setExitingTokensHasVisible(hasVisible);
+ }
+
+ void removeExistingTokensIfPossible() {
+ for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
+ final WindowToken token = mExitingTokens.get(i);
+ if (!token.hasVisible) {
+ mExitingTokens.remove(i);
+ }
+ }
+
+ // Time to remove any exiting applications?
+ mTaskStackContainers.removeExistingAppTokensIfPossible();
+ }
+
static final class TaskForResizePointSearchResult {
boolean searchDone;
Task taskForResize;
@@ -2650,10 +2651,38 @@
return false;
}
+ void setExitingTokensHasVisible(boolean hasVisible) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+ for (int j = appTokens.size() - 1; j >= 0; --j) {
+ appTokens.get(j).hasVisible = hasVisible;
+ }
+ }
+ }
+
+ void removeExistingAppTokensIfPossible() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+ for (int j = appTokens.size() - 1; j >= 0; --j) {
+ final AppWindowToken token = appTokens.get(j);
+ if (!token.hasVisible && !mService.mClosingApps.contains(token)
+ && (!token.mIsExiting || token.isEmpty())) {
+ // Make sure there is no animation running on this token, so any windows
+ // associated with it will be removed as soon as their animations are
+ // complete.
+ token.mAppAnimator.clearAnimation();
+ token.mAppAnimator.animating = false;
+ if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+ "performLayout: App token exiting now removed" + token);
+ token.removeIfPossible();
+ }
+ }
+ }
+ }
+
@Override
int getOrientation() {
- if (mService.isStackVisibleLocked(DOCKED_STACK_ID)
- || mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
+ if (isStackVisible(DOCKED_STACK_ID) || isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
// Apps and their containers are not allowed to specify an orientation while the
// docked or freeform stack is visible...except for the home stack/task if the
// docked stack is minimized and it actually set something.
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index ed1e2d9..aaf724e 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -457,8 +457,7 @@
void registerDockedStackListener(IDockedStackListener listener) {
mDockedStackListeners.register(listener);
notifyDockedDividerVisibilityChanged(wasVisible());
- notifyDockedStackExistsChanged(
- mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID) != null);
+ notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
notifyDockedStackMinimizedChanged(mMinimizedDock, 0 /* animDuration */);
notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
@@ -466,7 +465,7 @@
void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
mService.openSurfaceTransaction();
- final TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId);
+ final TaskStack stack = mDisplayContent.getStackById(targetStackId);
final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
boolean visibleAndValid = visible && stack != null && dockedStack != null;
if (visibleAndValid) {
@@ -544,8 +543,8 @@
if (homeTask == null || !isWithinDisplay(homeTask)) {
return;
}
- final TaskStack fullscreenStack
- = mService.mStackIdToStack.get(FULLSCREEN_WORKSPACE_STACK_ID);
+ final TaskStack fullscreenStack =
+ mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
final boolean homeBehind = (fullscreenStack != null && fullscreenStack.isVisible())
|| (homeStack.hasMultipleTaskWithHomeTaskNotTop());
@@ -739,7 +738,7 @@
}
private boolean animateForMinimizedDockedStack(long now) {
- final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+ final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
if (!mAnimationStarted) {
mAnimationStarted = true;
mAnimationStartTime = now;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0cc6c70..22abf30 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -382,6 +382,17 @@
}
}
+ TaskStack getStackById(int stackId) {
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final DisplayContent dc = mChildren.get(i);
+ final TaskStack stack = dc.getStackById(stackId);
+ if (stack != null) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
void setSecureSurfaceState(int userId, boolean disabled) {
forAllWindows((w) -> {
if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) {
@@ -535,18 +546,7 @@
final int numDisplays = mChildren.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
- for (i = displayContent.mExitingTokens.size() - 1; i >= 0; i--) {
- displayContent.mExitingTokens.get(i).hasVisible = false;
- }
- }
-
- for (int stackNdx = mService.mStackIdToStack.size() - 1; stackNdx >= 0; --stackNdx) {
- // Initialize state of exiting applications.
- final AppTokenList exitingAppTokens =
- mService.mStackIdToStack.valueAt(stackNdx).mExitingAppTokens;
- for (int tokenNdx = exitingAppTokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- exitingAppTokens.get(tokenNdx).hasVisible = false;
- }
+ displayContent.setExitingTokensHasVisible(false);
}
mHoldScreen = null;
@@ -681,33 +681,7 @@
// Time to remove any exiting tokens?
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
- ArrayList<WindowToken> exitingTokens = displayContent.mExitingTokens;
- for (i = exitingTokens.size() - 1; i >= 0; i--) {
- WindowToken token = exitingTokens.get(i);
- if (!token.hasVisible) {
- exitingTokens.remove(i);
- }
- }
- }
-
- // Time to remove any exiting applications?
- for (int stackNdx = mService.mStackIdToStack.size() - 1; stackNdx >= 0; --stackNdx) {
- // Initialize state of exiting applications.
- final AppTokenList exitingAppTokens =
- mService.mStackIdToStack.valueAt(stackNdx).mExitingAppTokens;
- for (i = exitingAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken token = exitingAppTokens.get(i);
- if (!token.hasVisible && !mService.mClosingApps.contains(token) &&
- (!token.mIsExiting || token.isEmpty())) {
- // Make sure there is no animation running on this token, so any windows
- // associated with it will be removed as soon as their animations are complete
- token.mAppAnimator.clearAnimation();
- token.mAppAnimator.animating = false;
- if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
- "performLayout: App token exiting now removed" + token);
- token.removeIfPossible();
- }
- }
+ displayContent.removeExistingTokensIfPossible();
}
if (wallpaperDestroyed) {
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
new file mode 100644
index 0000000..9a6f3eb5
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.app.RemoteAction;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+import android.util.SparseArray;
+import com.android.server.UiThread;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+/**
+ * Controller for the stack container. This is created by activity manager to link activity stacks
+ * to the stack container they use in window manager.
+ *
+ * Test class: {@link StackWindowControllerTests}
+ */
+public class StackWindowController
+ extends WindowContainerController<TaskStack, StackWindowListener> {
+
+ final int mStackId;
+
+ private final H mHandler;
+
+ public StackWindowController(int stackId, StackWindowListener listener,
+ int displayId, boolean onTop, Rect outBounds) {
+ this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
+ }
+
+ @VisibleForTesting
+ public StackWindowController(int stackId, StackWindowListener listener,
+ int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
+ super(listener, service);
+ mStackId = stackId;
+ mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
+
+ synchronized (mWindowMap) {
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ throw new IllegalArgumentException("Trying to add stackId=" + stackId
+ + " to unknown displayId=" + displayId);
+ }
+
+ final TaskStack stack = dc.addStackToDisplay(stackId, onTop);
+ stack.setController(this);
+ getRawBounds(outBounds);
+ }
+ }
+
+ @Override
+ public void removeContainer() {
+ synchronized (mWindowMap) {
+ if (mContainer != null) {
+ mContainer.removeIfPossible();
+ super.removeContainer();
+ }
+ }
+ }
+
+ public void reparent(int displayId, Rect outStackBounds) {
+ synchronized (mWindowMap) {
+ if (mContainer == null) {
+ throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
+ + " to displayId=" + displayId);
+ }
+
+ final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
+ if (targetDc == null) {
+ throw new IllegalArgumentException("Trying to move stackId=" + mStackId
+ + " to unknown displayId=" + displayId);
+ }
+
+ targetDc.moveStackToDisplay(mContainer);
+ getRawBounds(outStackBounds);
+ }
+ }
+
+ public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds,
+ Configuration overrideConfig) {
+ synchronized (mWindowMap) {
+ if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
+ + " at " + position);
+ if (child.mContainer == null) {
+ if (DEBUG_STACK) Slog.i(TAG_WM,
+ "positionChildAt: could not find task=" + this);
+ return;
+ }
+ if (mContainer == null) {
+ if (DEBUG_STACK) Slog.i(TAG_WM,
+ "positionChildAt: could not find stack for task=" + mContainer);
+ return;
+ }
+ child.mContainer.positionAt(position, bounds, overrideConfig);
+ mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+ }
+ }
+
+ public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) {
+ if (child == null) {
+ // TODO: Fix the call-points that cause this to happen.
+ return;
+ }
+
+ synchronized(mWindowMap) {
+ final Task childTask = child.mContainer;
+ if (childTask == null) {
+ Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found");
+ return;
+ }
+ mContainer.positionChildAt(POSITION_TOP, childTask, includingParents);
+
+ if (mService.mAppTransition.isTransitionSet()) {
+ childTask.setSendingToBottom(false);
+ }
+ mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+ }
+ }
+
+ public void positionChildAtBottom(TaskWindowContainerController child) {
+ if (child == null) {
+ // TODO: Fix the call-points that cause this to happen.
+ return;
+ }
+
+ synchronized(mWindowMap) {
+ final Task childTask = child.mContainer;
+ if (childTask == null) {
+ Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
+ return;
+ }
+ mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */);
+
+ if (mService.mAppTransition.isTransitionSet()) {
+ childTask.setSendingToBottom(true);
+ }
+ mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+ }
+ }
+
+ /**
+ * Re-sizes a stack and its containing tasks.
+ *
+ * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
+ * @param configs Configurations for tasks in the resized stack, keyed by task id.
+ * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
+ * @return True if the stack is now fullscreen.
+ */
+ public boolean resize(Rect bounds, SparseArray<Configuration> configs,
+ SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
+ synchronized (mWindowMap) {
+ if (mContainer == null) {
+ throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
+ }
+ // We might trigger a configuration change. Save the current task bounds for freezing.
+ mContainer.prepareFreezingTaskBounds();
+ if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
+ && mContainer.isVisible()) {
+ mContainer.getDisplayContent().setLayoutNeeded();
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+ }
+ return mContainer.getRawFullscreen();
+ }
+ }
+
+ // TODO: This and similar methods should be separated into PinnedStackWindowContainerController
+ public void animateResizePinnedStack(final Rect bounds, final int animationDuration) {
+ synchronized (mWindowMap) {
+ if (mContainer == null) {
+ throw new IllegalArgumentException("Pinned stack container not found :(");
+ }
+ final Rect originalBounds = new Rect();
+ mContainer.getBounds(originalBounds);
+ mContainer.setAnimatingBounds(bounds);
+ UiThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ mService.mBoundsAnimationController.animateBounds(
+ mContainer, originalBounds, bounds, animationDuration);
+ }
+ });
+ }
+ }
+
+ /** Sets the current picture-in-picture aspect ratio. */
+ public void setPictureInPictureAspectRatio(float aspectRatio) {
+ synchronized (mWindowMap) {
+ if (!mService.mSupportsPictureInPicture || mContainer == null) {
+ return;
+ }
+
+ final int displayId = mContainer.getDisplayContent().getDisplayId();
+ final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
+ animateResizePinnedStack(toBounds, -1 /* duration */);
+ }
+ }
+
+ public void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) {
+ synchronized (mWindowMap) {
+ if (mContainer != null) {
+ mContainer.getStackDockedModeBoundsLocked(outBounds, ignoreVisibility);
+ return;
+ }
+ outBounds.setEmpty();
+ }
+ }
+
+ public void prepareFreezingTaskBounds() {
+ synchronized (mWindowMap) {
+ if (mContainer == null) {
+ throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
+ + " not found.");
+ }
+ mContainer.prepareFreezingTaskBounds();
+ }
+ }
+
+ /** Sets the current picture-in-picture actions. */
+ public void setPictureInPictureActions(List<RemoteAction> actions) {
+ synchronized (mWindowMap) {
+ if (!mService.mSupportsPictureInPicture || mContainer == null) {
+ return;
+ }
+
+ mContainer.getDisplayContent().getPinnedStackController().setActions(actions);
+ }
+ }
+
+ private void getRawBounds(Rect outBounds) {
+ if (mContainer.getRawFullscreen()) {
+ outBounds.setEmpty();
+ } else {
+ mContainer.getRawBounds(outBounds);
+ }
+ }
+
+ public void getBounds(Rect outBounds) {
+ synchronized (mWindowMap) {
+ if (mContainer != null) {
+ mContainer.getBounds(outBounds);
+ return;
+ }
+ outBounds.setEmpty();
+ }
+ }
+
+ public Rect getBoundsForNewConfiguration() {
+ synchronized(mWindowMap) {
+ final Rect outBounds = new Rect();
+ mContainer.getBoundsForNewConfiguration(outBounds);
+ return outBounds;
+ }
+ }
+
+ void requestResize(Rect bounds) {
+ mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
+ }
+
+ @Override
+ public String toString() {
+ return "{StackWindowController stackId=" + mStackId + "}";
+ }
+
+ private static final class H extends Handler {
+
+ static final int REQUEST_RESIZE = 0;
+
+ private final WeakReference<StackWindowController> mController;
+
+ H(WeakReference<StackWindowController> controller, Looper looper) {
+ super(looper);
+ mController = controller;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final StackWindowController controller = mController.get();
+ final StackWindowListener listener = (controller != null)
+ ? controller.mListener : null;
+ if (listener == null) {
+ return;
+ }
+ switch (msg.what) {
+ case REQUEST_RESIZE:
+ listener.requestResize((Rect) msg.obj);
+ break;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/StackWindowListener.java b/services/core/java/com/android/server/wm/StackWindowListener.java
new file mode 100644
index 0000000..c763c17
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StackWindowListener.java
@@ -0,0 +1,29 @@
+/*
+ * 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.wm;
+
+import android.graphics.Rect;
+
+/**
+ * Interface used by the creator of {@link StackWindowController} to listen to changes with
+ * the stack container.
+ */
+public interface StackWindowListener extends WindowContainerListener {
+
+ /** Called when the stack container would like its controller to resize. */
+ void requestResize(Rect bounds);
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 680d0f2..d96e1ef 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -27,9 +27,9 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
import android.app.ActivityManager.StackId;
+import android.app.ActivityManager.TaskDescription;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -81,6 +81,10 @@
// Resize mode of the task. See {@link ActivityInfo#resizeMode}
private int mResizeMode;
+ // Whether the task supports picture-in-picture.
+ // See {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE}
+ private boolean mSupportsPictureInPicture;
+
// Whether the task is currently being drag-resized
private boolean mDragResizing;
private int mDragResizeMode;
@@ -90,8 +94,11 @@
// Whether this task is an on-top launcher task, which is determined by the root activity.
private boolean mIsOnTopLauncher;
+ private TaskDescription mTaskDescription;
+
Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
- Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode, boolean homeTask,
+ Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode,
+ boolean supportsPictureInPicture, boolean homeTask, TaskDescription taskDescription,
TaskWindowContainerController controller) {
mTaskId = taskId;
mStack = stack;
@@ -99,9 +106,11 @@
mService = service;
mIsOnTopLauncher = isOnTopLauncher;
mResizeMode = resizeMode;
+ mSupportsPictureInPicture = supportsPictureInPicture;
mHomeTask = homeTask;
setController(controller);
setBounds(bounds, overrideConfig);
+ mTaskDescription = taskDescription;
}
DisplayContent getDisplayContent() {
@@ -323,7 +332,8 @@
}
boolean isResizeable() {
- return ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks;
+ return ActivityInfo.isResizeableMode(mResizeMode) || mSupportsPictureInPicture
+ || mService.mForceResizableTasks;
}
/**
@@ -556,11 +566,10 @@
displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
- // Post message to inform activity manager of the bounds change simulating a one-way
- // call. We do this to prevent a deadlock between window manager lock and activity
- // manager lock been held.
- mService.mH.obtainMessage(RESIZE_TASK, mTaskId,
- RESIZE_MODE_SYSTEM_SCREEN_ROTATION, mBounds).sendToTarget();
+ final TaskWindowContainerController controller = getController();
+ if (controller != null) {
+ controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
+ }
}
}
@@ -647,6 +656,14 @@
}
}
+ void setTaskDescription(TaskDescription taskDescription) {
+ mTaskDescription = taskDescription;
+ }
+
+ TaskDescription getTaskDescription() {
+ return mTaskDescription;
+ }
+
@Override
boolean fillsParent() {
return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId);
@@ -688,6 +705,5 @@
pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
wtoken.dump(pw, triplePrefix);
}
-
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 15878f6..2b74f84 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -26,6 +26,8 @@
import android.util.ArraySet;
import android.view.WindowManagerPolicy.StartingSurface;
+import com.google.android.collect.Sets;
+
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
@@ -66,10 +68,27 @@
if (!ENABLE_TASK_SNAPSHOTS) {
return;
}
+ handleClosingApps(mService.mClosingApps);
+ }
+
+
+ /**
+ * Called when the visibility of an app changes outside of the regular app transition flow.
+ */
+ void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
+ if (!ENABLE_TASK_SNAPSHOTS) {
+ return;
+ }
+ if (!visible) {
+ handleClosingApps(Sets.newArraySet(appWindowToken));
+ }
+ }
+
+ private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
// We need to take a snapshot of the task if and only if all activities of the task are
// either closing or hidden.
- getClosingTasks(mService.mClosingApps, mTmpTasks);
+ getClosingTasks(closingApps, mTmpTasks);
for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
final Task task = mTmpTasks.valueAt(i);
if (!canSnapshotTask(task)) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 4a09423..cfcbbd0 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -26,12 +26,16 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.app.ActivityManager.TaskDescription;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.GraphicBuffer;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Slog;
@@ -43,6 +47,7 @@
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy.StartingSurface;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.view.BaseIWindow;
/**
@@ -61,6 +66,7 @@
private final WindowManagerService mService;
private boolean mHasDrawn;
private boolean mReportNextDraw;
+ private Paint mFillBackgroundPaint = new Paint();
static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
GraphicBuffer snapshot) {
@@ -73,6 +79,7 @@
final Rect tmpRect = new Rect();
final Rect tmpFrame = new Rect();
final Configuration tmpConfiguration = new Configuration();
+ int fillBackgroundColor = Color.WHITE;
synchronized (service.mWindowMap) {
layoutParams.type = TYPE_APPLICATION_STARTING;
layoutParams.format = snapshot.getFormat();
@@ -90,6 +97,12 @@
layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
layoutParams.setTitle(String.format(TITLE_FORMAT, token.mTask.mTaskId));
+ if (token.mTask != null) {
+ final TaskDescription taskDescription = token.mTask.getTaskDescription();
+ if (taskDescription != null) {
+ fillBackgroundColor = taskDescription.getBackgroundColor();
+ }
+ }
}
try {
final int res = session.addToDisplay(window, window.mSeq, layoutParams,
@@ -103,7 +116,7 @@
// Local call.
}
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
- surface);
+ surface, fillBackgroundColor);
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
@@ -116,11 +129,14 @@
return snapshotSurface;
}
- private TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface) {
+ @VisibleForTesting
+ TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
+ int fillBackgroundColor) {
mService = service;
mSession = WindowManagerGlobal.getWindowSession();
mWindow = window;
mSurface = surface;
+ mFillBackgroundPaint.setColor(fillBackgroundColor);
}
@Override
@@ -136,7 +152,9 @@
// TODO: Just wrap the buffer here without any copying.
final Canvas c = mSurface.lockHardwareCanvas();
- c.drawBitmap(Bitmap.createHardwareBitmap(snapshot), 0, 0, null);
+ final Bitmap b = Bitmap.createHardwareBitmap(snapshot);
+ fillEmptyBackground(c, b);
+ c.drawBitmap(b, 0, 0, null);
mSurface.unlockCanvasAndPost(c);
final boolean reportNextDraw;
synchronized (mService.mWindowMap) {
@@ -149,6 +167,21 @@
mSurface.release();
}
+ @VisibleForTesting
+ void fillEmptyBackground(Canvas c, Bitmap b) {
+ final boolean fillHorizontally = c.getWidth() > b.getWidth();
+ final boolean fillVertically = c.getHeight() > b.getHeight();
+ if (fillHorizontally) {
+ c.drawRect(b.getWidth(), 0, c.getWidth(), fillVertically
+ ? b.getHeight()
+ : c.getHeight(),
+ mFillBackgroundPaint);
+ }
+ if (fillVertically) {
+ c.drawRect(0, b.getHeight(), c.getWidth(), c.getHeight(), mFillBackgroundPaint);
+ }
+ }
+
private void reportDrawn() {
synchronized (mService.mWindowMap) {
mReportNextDraw = false;
@@ -160,7 +193,7 @@
}
}
- private static Handler sHandler = new Handler() {
+ private static Handler sHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 53292bb..d3eae8c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -35,17 +35,12 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK;
import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
import android.app.ActivityManager.StackId;
-import android.app.IActivityManager;
import android.content.res.Configuration;
-import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Debug;
import android.os.RemoteException;
import android.util.EventLog;
import android.util.Slog;
@@ -53,11 +48,9 @@
import android.view.DisplayInfo;
import android.view.Surface;
-import android.view.WindowManagerPolicy;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.internal.policy.DockedDividerUtils;
-import com.android.internal.policy.PipSnapAlgorithm;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
@@ -206,14 +199,6 @@
}
}
- boolean isFullscreenBounds(Rect bounds) {
- if (mDisplayContent == null || bounds == null) {
- return true;
- }
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
- return mTmpRect.equals(bounds);
- }
-
/**
* Overrides the adjusted bounds, i.e. sets temporary layout bounds which are different from
* the normal task bounds.
@@ -658,7 +643,7 @@
final Rect oldBounds = new Rect(mBounds);
Rect bounds = null;
- final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+ final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility();
if (mStackId == DOCKED_STACK_ID
|| (dockedStack != null && StackId.isResizeableByDockedStack(mStackId)
&& !dockedStack.fillsParent())) {
@@ -697,7 +682,7 @@
return;
}
- final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+ final TaskStack dockedStack = mDisplayContent.getDockedStackIgnoringVisibility();
if (dockedStack == null) {
// Not sure why you are calling this method when there is no docked stack...
throw new IllegalStateException(
@@ -805,8 +790,12 @@
final Rect bounds = new Rect();
getStackDockedModeBoundsLocked(bounds, true /*ignoreVisibility*/);
- mService.mH.obtainMessage(RESIZE_STACK, DOCKED_STACK_ID,
- 1 /*allowResizeInDockedMode*/, bounds).sendToTarget();
+ getController().requestResize(bounds);
+ }
+
+ @Override
+ StackWindowController getController() {
+ return (StackWindowController) super.getController();
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 3c438ca..11667c0 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.app.ActivityManager.TaskDescription;
import android.app.ActivityManager.TaskSnapshot;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -26,6 +27,8 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.ref.WeakReference;
+
import static com.android.server.EventLogTags.WM_TASK_CREATED;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
@@ -42,44 +45,40 @@
public class TaskWindowContainerController
extends WindowContainerController<Task, TaskWindowContainerListener> {
- private static final int REPORT_SNAPSHOT_CHANGED = 0;
-
private final int mTaskId;
-
- private final Handler mHandler = new Handler(Looper.getMainLooper()) {
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case REPORT_SNAPSHOT_CHANGED:
- if (mListener != null) {
- mListener.onSnapshotChanged((TaskSnapshot) msg.obj);
- }
- break;
- }
- }
- };
+ private final H mHandler;
public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
- int stackId, int userId, Rect bounds, Configuration overrideConfig, int resizeMode,
- boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers) {
- super(listener, WindowManagerService.getInstance());
+ StackWindowController stackController, int userId, Rect bounds,
+ Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+ boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
+ TaskDescription taskDescription) {
+ this(taskId, listener, stackController, userId, bounds, overrideConfig, resizeMode,
+ supportsPictureInPicture, homeTask, isOnTopLauncher, toTop, showForAllUsers,
+ taskDescription, WindowManagerService.getInstance());
+ }
+
+ public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
+ StackWindowController stackController, int userId, Rect bounds,
+ Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+ boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
+ TaskDescription taskDescription, WindowManagerService service) {
+ super(listener, service);
mTaskId = taskId;
+ mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
synchronized(mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId
- + " stackId=" + stackId + " bounds=" + bounds);
+ + " stack=" + stackController + " bounds=" + bounds);
- // TODO: Pass controller for the stack to get the container object when stack is
- // switched to use controller.
- final TaskStack stack = mService.mStackIdToStack.get(stackId);
+ final TaskStack stack = stackController.mContainer;
if (stack == null) {
- throw new IllegalArgumentException("TaskWindowContainerController: invalid stackId="
- + stackId);
+ throw new IllegalArgumentException("TaskWindowContainerController: invalid stack="
+ + stackController);
}
- EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId);
+ EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode,
- homeTask, isOnTopLauncher);
+ supportsPictureInPicture, homeTask, isOnTopLauncher, taskDescription);
final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
stack.addTask(task, position, showForAllUsers, true /* moveParents */);
}
@@ -87,10 +86,10 @@
@VisibleForTesting
Task createTask(int taskId, TaskStack stack, int userId, Rect bounds,
- Configuration overrideConfig, int resizeMode, boolean homeTask,
- boolean isOnTopLauncher) {
+ Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+ boolean homeTask, boolean isOnTopLauncher, TaskDescription taskDescription) {
return new Task(taskId, stack, userId, mService, bounds, overrideConfig, isOnTopLauncher,
- resizeMode, homeTask, this);
+ resizeMode, supportsPictureInPicture, homeTask, taskDescription, this);
}
@Override
@@ -122,21 +121,22 @@
}
}
- public void reparent(int stackId, int position) {
+ public void reparent(StackWindowController stackController, int position) {
synchronized (mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId
- + " to stackId=" + stackId + " at " + position);
+ + " to stack=" + stackController + " at " + position);
if (mContainer == null) {
if (DEBUG_STACK) Slog.i(TAG_WM,
"reparent: could not find taskId=" + mTaskId);
return;
}
- final TaskStack stack = mService.mStackIdToStack.get(stackId);
+ final TaskStack stack = stackController.mContainer;
if (stack == null) {
- throw new IllegalArgumentException("reparent: could not find stackId=" + stackId);
+ throw new IllegalArgumentException("reparent: could not find stack="
+ + stackController);
}
mContainer.reparent(stack, position);
- mService.mWindowPlacerLocked.performSurfacePlacement();
+ mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
}
@@ -156,68 +156,11 @@
}
if (mContainer.resizeLocked(bounds, overrideConfig, forced) && relayout) {
- mContainer.getDisplayContent().setLayoutNeeded();
- mService.mWindowPlacerLocked.performSurfacePlacement();
+ mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
}
}
- // TODO: Move to positionChildAt() in stack controller once we have a stack controller.
- public void positionAt(int position, Rect bounds, Configuration overrideConfig) {
- synchronized (mWindowMap) {
- if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning taskId=" + mTaskId
- + " at " + position);
- if (mContainer == null) {
- if (DEBUG_STACK) Slog.i(TAG_WM,
- "positionAt: could not find taskId=" + mTaskId);
- return;
- }
- final TaskStack stack = mContainer.mStack;
- if (stack == null) {
- if (DEBUG_STACK) Slog.i(TAG_WM,
- "positionAt: could not find stack for task=" + mContainer);
- return;
- }
- mContainer.positionAt(position, bounds, overrideConfig);
- final DisplayContent displayContent = stack.getDisplayContent();
- displayContent.setLayoutNeeded();
- mService.mWindowPlacerLocked.performSurfacePlacement();
- }
- }
-
- // TODO: Replace with moveChildToTop in stack controller?
- public void moveToTop(boolean includingParents) {
- synchronized(mWindowMap) {
- if (mContainer == null) {
- Slog.e(TAG_WM, "moveToTop: taskId=" + mTaskId + " not found");
- return;
- }
- final TaskStack stack = mContainer.mStack;
- stack.positionChildAt(POSITION_TOP, mContainer, includingParents);
-
- if (mService.mAppTransition.isTransitionSet()) {
- mContainer.setSendingToBottom(false);
- }
- stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
- }
- }
-
- // TODO: Replace with moveChildToBottom in stack controller?
- public void moveToBottom() {
- synchronized(mWindowMap) {
- if (mContainer == null) {
- Slog.e(TAG_WM, "moveTaskToBottom: taskId=" + mTaskId + " not found");
- return;
- }
- final TaskStack stack = mContainer.mStack;
- stack.positionChildAt(POSITION_BOTTOM, mContainer, false /* includingParents */);
- if (mService.mAppTransition.isTransitionSet()) {
- mContainer.setSendingToBottom(true);
- }
- stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
- }
- }
-
public void getBounds(Rect bounds) {
synchronized (mWindowMap) {
if (mContainer != null) {
@@ -263,12 +206,57 @@
}
}
+ public void setTaskDescription(TaskDescription taskDescription) {
+ synchronized (mWindowMap) {
+ if (mContainer == null) {
+ Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found.");
+ return;
+ }
+ mContainer.setTaskDescription(taskDescription);
+ }
+ }
+
void reportSnapshotChanged(TaskSnapshot snapshot) {
- mHandler.obtainMessage(REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
+ mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
+ }
+
+ void requestResize(Rect bounds, int resizeMode) {
+ mHandler.obtainMessage(H.REQUEST_RESIZE, resizeMode, 0, bounds).sendToTarget();
}
@Override
public String toString() {
return "{TaskWindowContainerController taskId=" + mTaskId + "}";
}
+
+ private static final class H extends Handler {
+
+ static final int REPORT_SNAPSHOT_CHANGED = 0;
+ static final int REQUEST_RESIZE = 1;
+
+ private final WeakReference<TaskWindowContainerController> mController;
+
+ H(WeakReference<TaskWindowContainerController> controller, Looper looper) {
+ super(looper);
+ mController = controller;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final TaskWindowContainerController controller = mController.get();
+ final TaskWindowContainerListener listener = (controller != null)
+ ? controller.mListener : null;
+ if (listener == null) {
+ return;
+ }
+ switch (msg.what) {
+ case REPORT_SNAPSHOT_CHANGED:
+ listener.onSnapshotChanged((TaskSnapshot) msg.obj);
+ break;
+ case REQUEST_RESIZE:
+ listener.requestResize((Rect) msg.obj, msg.arg1);
+ break;
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
index 61b202d..af67de3 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
@@ -17,14 +17,17 @@
package com.android.server.wm;
import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Rect;
/**
- * Interface used by the creator of the controller to listen to changes with the container.
+ * Interface used by the creator of {@link TaskWindowContainerController} to listen to changes with
+ * the task container.
*/
public interface TaskWindowContainerListener extends WindowContainerListener {
- /**
- * Called when the snapshot of this task has changed.
- */
+ /** Called when the snapshot of this task has changed. */
void onSnapshotChanged(TaskSnapshot snapshot);
+
+ /** Called when the task container would like its controller to resize. */
+ void requestResize(Rect bounds, int resizeMode);
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index feceb8e..c32e689 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -447,7 +447,7 @@
private void findWallpaperTarget(DisplayContent dc) {
mFindResults.reset();
- if (mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
+ if (dc.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
// In freeform mode we set the wallpaper as its own target, so we don't need an
// additional window to make it visible.
mFindResults.setUseTopWallpaperAsTarget(true);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c8f4bd2..1987f90 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -706,12 +706,7 @@
final WindowAnimator mAnimator;
- private final BoundsAnimationController mBoundsAnimationController;
-
- /** All of the TaskStacks in the window manager, unordered. For an ordered list call
- * DisplayContent.getStacks(). */
- // TODO: Don't believe this is needed with the WindowContainer model.
- SparseArray<TaskStack> mStackIdToStack = new SparseArray<>();
+ final BoundsAnimationController mBoundsAnimationController;
private final PointerEventDispatcher mPointerEventDispatcher;
@@ -2573,16 +2568,6 @@
}
}
- @Override
- public Rect getBoundsForNewConfiguration(int stackId) {
- synchronized(mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(stackId);
- final Rect outBounds = new Rect();
- stack.getBoundsForNewConfiguration(outBounds);
- return outBounds;
- }
- }
-
void setFocusTaskRegionLocked() {
final Task focusedTask = mFocusedApp != null ? mFocusedApp.mTask : null;
if (focusedTask != null) {
@@ -2827,11 +2812,6 @@
}
}
- boolean isStackVisibleLocked(int stackId) {
- final TaskStack stack = mStackIdToStack.get(stackId);
- return (stack != null && stack.isVisible());
- }
-
public void setDockedStackCreateState(int mode, Rect bounds) {
synchronized (mWindowMap) {
setDockedStackCreateStateLocked(mode, bounds);
@@ -2880,17 +2860,19 @@
}
final Rect stackBounds;
- final DisplayContent displayContent;
- final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent == null) {
+ return null;
+ }
+
+ final TaskStack stack = displayContent.getStackById(PINNED_STACK_ID);
if (stack != null) {
// If the stack exists, then use its final bounds to calculate the new aspect ratio
// bounds.
- displayContent = stack.getDisplayContent();
stackBounds = new Rect();
stack.getAnimatingBounds(stackBounds);
} else {
// Otherwise, just calculate the aspect ratio bounds from the default bounds
- displayContent = mRoot.getDisplayContent(displayId);
stackBounds = displayContent.getPinnedStackController().getDefaultBounds();
}
return displayContent.getPinnedStackController().getAspectRatioBounds(stackBounds,
@@ -2898,119 +2880,10 @@
}
}
- /**
- * Sets the current picture-in-picture aspect ratio.
- */
- public void setPictureInPictureAspectRatio(float aspectRatio) {
- synchronized (mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
- if (!mSupportsPictureInPicture || stack == null) {
- return;
- }
-
- final int displayId = stack.getDisplayContent().getDisplayId();
- final Rect toBounds = getPictureInPictureBounds(displayId, aspectRatio);
- animateResizePinnedStack(toBounds, -1 /* duration */);
- }
- }
-
- /**
- * Sets the current picture-in-picture actions.
- */
- public void setPictureInPictureActions(List<RemoteAction> actions) {
- synchronized (mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
- if (!mSupportsPictureInPicture || stack == null) {
- return;
- }
-
- stack.getDisplayContent().getPinnedStackController().setActions(actions);
- }
- }
-
- /**
- * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with
- * specified stackId.
- * @param stackId The unique identifier of the new stack.
- * @param displayId The unique identifier of the DisplayContent.
- * @param onTop If true the stack will be place at the top of the display,
- * else at the bottom.
- * @return The bounds that the stack has after adding. null means fullscreen.
- */
- public Rect addStackToDisplay(int stackId, int displayId, boolean onTop) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mWindowMap) {
- final DisplayContent dc = mRoot.getDisplayContent(displayId);
- if (dc == null) {
- throw new IllegalArgumentException("Trying to add stackId=" + stackId
- + " to unknown displayId=" + displayId);
- }
-
- return dc.addStackToDisplay(stackId, onTop);
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- /**
- * Move a TaskStack from current DisplayContent to specified one.
- * @param stackId The unique identifier of the new stack.
- * @param displayId The unique identifier of the new display.
- */
- public Rect moveStackToDisplay(int stackId, int displayId) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mWindowMap) {
- TaskStack stack = mStackIdToStack.get(stackId);
- if (stack == null) {
- throw new IllegalArgumentException("Trying to move unknown stackId=" + stackId
- + " to displayId=" + displayId);
- }
-
- final DisplayContent targetDisplayContent = mRoot.getDisplayContent(displayId);
- if (targetDisplayContent == null) {
- throw new IllegalArgumentException("Trying to move stackId=" + stackId
- + " to unknown displayId=" + displayId);
- }
-
- return targetDisplayContent.moveStackToDisplay(stack);
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- /**
- * Remove a TaskStack completely.
- * @param stackId The unique identifier of the stack.
- */
- public void removeStack(int stackId) {
- synchronized (mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(stackId);
- if (stack != null) {
- stack.removeIfPossible();
- mStackIdToStack.remove(stackId);
- }
- }
- }
-
- public void getStackDockedModeBounds(int stackId, Rect bounds, boolean ignoreVisibility) {
- synchronized (mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(stackId);
- if (stack != null) {
- stack.getStackDockedModeBoundsLocked(bounds, ignoreVisibility);
- return;
- }
- bounds.setEmpty();
- }
- }
-
@Override
public void getStackBounds(int stackId, Rect bounds) {
synchronized (mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(stackId);
+ final TaskStack stack = mRoot.getStackById(stackId);
if (stack != null) {
stack.getBounds(bounds);
return;
@@ -3035,43 +2908,6 @@
}
/**
- * Re-sizes a stack and its containing tasks.
- * @param stackId Id of stack to resize.
- * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
- * @param configs Configurations for tasks in the resized stack, keyed by task id.
- * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
- * @return True if the stack is now fullscreen.
- * */
- public boolean resizeStack(int stackId, Rect bounds,
- SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
- SparseArray<Rect> taskTempInsetBounds) {
- synchronized (mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(stackId);
- if (stack == null) {
- throw new IllegalArgumentException("resizeStack: stackId " + stackId
- + " not found.");
- }
- if (stack.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
- && stack.isVisible()) {
- stack.getDisplayContent().setLayoutNeeded();
- mWindowPlacerLocked.performSurfacePlacement();
- }
- return stack.getRawFullscreen();
- }
- }
-
- public void prepareFreezingTaskBounds(int stackId) {
- synchronized (mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(stackId);
- if (stack == null) {
- throw new IllegalArgumentException("prepareFreezingTaskBounds: stackId " + stackId
- + " not found.");
- }
- stack.prepareFreezingTaskBounds();
- }
- }
-
- /**
* Starts deferring layout passes. Useful when doing multiple changes but to optimize
* performance, only one layout pass should be done. This can be called multiple times, and
* layouting will be resumed once the last caller has called {@link #continueSurfaceLayout}
@@ -3505,8 +3341,9 @@
// Notify whether the docked stack exists for the current user
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- displayContent.mDividerControllerLocked
- .notifyDockedStackExistsChanged(hasDockedTasksForUser(newUserId));
+ final TaskStack stack = displayContent.getDockedStackIgnoringVisibility();
+ displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
+ stack != null && stack.hasTaskForUser(newUserId));
// If the display is already prepared, update the density.
// Otherwise, we'll update it when it's prepared.
@@ -3519,15 +3356,6 @@
}
}
- /** Returns whether there is a docked task for the current user. */
- boolean hasDockedTasksForUser(int userId) {
- final TaskStack stack = mStackIdToStack.get(DOCKED_STACK_ID);
- if (stack == null) {
- return false;
- }
- return stack.hasTaskForUser(userId);
- }
-
/* Called by WindowState */
boolean isCurrentProfileLocked(int userId) {
if (userId == mCurrentUserId) return true;
@@ -5417,8 +5245,6 @@
public static final int UPDATE_DOCKED_STACK_DIVIDER = 41;
- public static final int RESIZE_STACK = 42;
- public static final int RESIZE_TASK = 43;
public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44;
public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
@@ -5844,23 +5670,6 @@
}
}
break;
- case RESIZE_TASK: {
- try {
- mActivityManager.resizeTask(msg.arg1, (Rect) msg.obj, msg.arg2);
- } catch (RemoteException e) {
- // This will not happen since we are in the same process.
- }
- }
- break;
- case RESIZE_STACK: {
- try {
- mActivityManager.resizeStack(
- msg.arg1, (Rect) msg.obj, msg.arg2 == 1, false, false, -1);
- } catch (RemoteException e) {
- // This will not happen since we are in the same process.
- }
- }
- break;
case WINDOW_REPLACEMENT_TIMEOUT: {
synchronized (mWindowMap) {
for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) {
@@ -7646,26 +7455,6 @@
}
}
- public void animateResizePinnedStack(final Rect bounds, final int animationDuration) {
- synchronized (mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
- if (stack == null) {
- Slog.w(TAG, "animateResizePinnedStack: stackId " + PINNED_STACK_ID + " not found.");
- return;
- }
- final Rect originalBounds = new Rect();
- stack.getBounds(originalBounds);
- stack.setAnimatingBounds(bounds);
- UiThread.getHandler().post(new Runnable() {
- @Override
- public void run() {
- mBoundsAnimationController.animateBounds(
- stack, originalBounds, bounds, animationDuration);
- }
- });
- }
- }
-
public void setForceResizableTasks(boolean forceResizableTasks) {
synchronized (mWindowMap) {
mForceResizableTasks = forceResizableTasks;
@@ -8123,7 +7912,8 @@
@Override
public boolean isStackVisible(int stackId) {
synchronized (mWindowMap) {
- return WindowManagerService.this.isStackVisibleLocked(stackId);
+ final DisplayContent dc = getDefaultDisplayContentLocked();
+ return dc.isStackVisible(stackId);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 10aebe6..13358da8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2304,6 +2304,7 @@
final WindowState win = mService.windowForClientLocked(mSession, mClient, false);
Slog.i(TAG, "WIN DEATH: " + win);
if (win != null) {
+ final DisplayContent dc = getDisplayContent();
if (win.mAppToken != null && win.mAppToken.findMainWindow() == win) {
mService.mTaskSnapshotController.onAppDied(win.mAppToken);
}
@@ -2313,7 +2314,7 @@
// just in case they have the divider at an unstable position. Better
// also reset drag resizing state, because the owner can't do it
// anymore.
- final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+ final TaskStack stack = dc.getDockedStackIgnoringVisibility();
if (stack != null) {
stack.resetDockedStackToMiddle();
}
diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp
index 2fd0603..74af639 100644
--- a/services/core/jni/com_android_server_lights_LightsService.cpp
+++ b/services/core/jni/com_android_server_lights_LightsService.cpp
@@ -81,7 +81,7 @@
// TODO(b/31632518)
if (gLight == nullptr) {
- gLight = ILight::getService("light");
+ gLight = ILight::getService();
}
if (gLight == nullptr) {
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index b2372a3..fab309b 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -110,7 +110,7 @@
static void nativeInit(JNIEnv* env, jobject obj) {
gPowerManagerServiceObj = env->NewGlobalRef(obj);
- gPowerHal = IPower::getService("power");
+ gPowerHal = IPower::getService();
if (gPowerHal == nullptr) {
ALOGE("Couldn't load PowerHAL module");
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3808f3e..420cfe9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -33,6 +33,8 @@
import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER;
import static android.app.admin.DevicePolicyManager.CODE_USER_NOT_RUNNING;
import static android.app.admin.DevicePolicyManager.CODE_USER_SETUP_COMPLETED;
+import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -259,6 +261,12 @@
private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER
= "application-restrictions-manager";
+ // Comprehensive list of delegations.
+ private static final String DELEGATIONS[] = {
+ DELEGATION_CERT_INSTALL,
+ DELEGATION_APP_RESTRICTIONS
+ };
+
/**
* System property whose value is either "true" or "false", indicating whether
* device owner is present.
@@ -468,12 +476,11 @@
ComponentName mRestrictionsProvider;
- String mDelegatedCertInstallerPackage;
+ // Map of delegate package to delegation scopes
+ final ArrayMap<String, List<String>> mDelegationMap = new ArrayMap<>();
boolean doNotAskCredentialsOnBoot = false;
- String mApplicationRestrictionsManagingPackage;
-
Set<String> mAffiliationIds = new ArraySet<>();
long mLastSecurityLogRetrievalTime = -1;
@@ -1397,7 +1404,7 @@
}
private void handlePackagesChanged(String packageName, int userHandle) {
- boolean removed = false;
+ boolean removedAdmin = false;
if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
DevicePolicyData policy = getUserData(userHandle);
synchronized (this) {
@@ -1413,32 +1420,36 @@
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userHandle) == null) {
- removed = true;
+ removedAdmin = true;
policy.mAdminList.remove(i);
policy.mAdminMap.remove(aa.info.getComponent());
}
}
} catch (RemoteException re) {
- // Shouldn't happen
+ // Shouldn't happen.
}
}
- if (removed) {
+ if (removedAdmin) {
validatePasswordOwnerLocked(policy);
- saveSettingsLocked(policy.mUserHandle);
}
- // Check if delegated cert installer or app restrictions managing packages are removed.
- if (isRemovedPackage(packageName, policy.mDelegatedCertInstallerPackage, userHandle)) {
- policy.mDelegatedCertInstallerPackage = null;
- saveSettingsLocked(policy.mUserHandle);
+ boolean removedDelegate = false;
+
+ // Check if a delegate was removed.
+ for (int i = policy.mDelegationMap.size() - 1; i >= 0; i--) {
+ final String delegatePackage = policy.mDelegationMap.keyAt(i);
+ if (isRemovedPackage(packageName, delegatePackage, userHandle)) {
+ policy.mDelegationMap.removeAt(i);
+ removedDelegate = true;
+ }
}
- if (isRemovedPackage(
- packageName, policy.mApplicationRestrictionsManagingPackage, userHandle)) {
- policy.mApplicationRestrictionsManagingPackage = null;
+
+ // Persist updates if the removed package was an admin or delegate.
+ if (removedAdmin || removedDelegate) {
saveSettingsLocked(policy.mUserHandle);
}
}
- if (removed) {
+ if (removedAdmin) {
// The removed admin might have disabled camera, so update user restrictions.
pushUserRestrictions(userHandle);
}
@@ -2377,13 +2388,19 @@
out.attribute(null, ATTR_PERMISSION_POLICY,
Integer.toString(policy.mPermissionPolicy));
}
- if (policy.mDelegatedCertInstallerPackage != null) {
- out.attribute(null, ATTR_DELEGATED_CERT_INSTALLER,
- policy.mDelegatedCertInstallerPackage);
- }
- if (policy.mApplicationRestrictionsManagingPackage != null) {
- out.attribute(null, ATTR_APPLICATION_RESTRICTIONS_MANAGER,
- policy.mApplicationRestrictionsManagingPackage);
+
+ // Serialize delegations.
+ for (int i = 0; i < policy.mDelegationMap.size(); ++i) {
+ final String delegatePackage = policy.mDelegationMap.keyAt(i);
+ final List<String> scopes = policy.mDelegationMap.valueAt(i);
+
+ // Every "delegation" tag serializes the information of one delegate-scope pair.
+ for (String scope : scopes) {
+ out.startTag(null, "delegation");
+ out.attribute(null, "delegatePackage", delegatePackage);
+ out.attribute(null, "scope", scope);
+ out.endTag(null, "delegation");
+ }
}
final int N = policy.mAdminList.size();
@@ -2567,10 +2584,36 @@
if (!TextUtils.isEmpty(permissionPolicy)) {
policy.mPermissionPolicy = Integer.parseInt(permissionPolicy);
}
- policy.mDelegatedCertInstallerPackage = parser.getAttributeValue(null,
- ATTR_DELEGATED_CERT_INSTALLER);
- policy.mApplicationRestrictionsManagingPackage = parser.getAttributeValue(null,
- ATTR_APPLICATION_RESTRICTIONS_MANAGER);
+ // Check for delegation compatibility with pre-O.
+ // TODO(edmanp) remove in P.
+ {
+ final String certDelegate = parser.getAttributeValue(null,
+ ATTR_DELEGATED_CERT_INSTALLER);
+ if (certDelegate != null) {
+ List<String> scopes = policy.mDelegationMap.get(certDelegate);
+ if (scopes == null) {
+ scopes = new ArrayList<>();
+ policy.mDelegationMap.put(certDelegate, scopes);
+ }
+ if (!scopes.contains(DELEGATION_CERT_INSTALL)) {
+ scopes.add(DELEGATION_CERT_INSTALL);
+ needsRewrite = true;
+ }
+ }
+ final String appRestrictionsDelegate = parser.getAttributeValue(null,
+ ATTR_APPLICATION_RESTRICTIONS_MANAGER);
+ if (appRestrictionsDelegate != null) {
+ List<String> scopes = policy.mDelegationMap.get(appRestrictionsDelegate);
+ if (scopes == null) {
+ scopes = new ArrayList<>();
+ policy.mDelegationMap.put(appRestrictionsDelegate, scopes);
+ }
+ if (!scopes.contains(DELEGATION_APP_RESTRICTIONS)) {
+ scopes.add(DELEGATION_APP_RESTRICTIONS);
+ needsRewrite = true;
+ }
+ }
+ }
type = parser.next();
int outerDepth = parser.getDepth();
@@ -2605,6 +2648,23 @@
} catch (RuntimeException e) {
Slog.w(LOG_TAG, "Failed loading admin " + name, e);
}
+ } else if ("delegation".equals(tag)) {
+ // Parse delegation info.
+ final String delegatePackage = parser.getAttributeValue(null,
+ "delegatePackage");
+ final String scope = parser.getAttributeValue(null, "scope");
+
+ // Get a reference to the scopes list for the delegatePackage.
+ List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+ // Or make a new list if none was found.
+ if (scopes == null) {
+ scopes = new ArrayList<>();
+ policy.mDelegationMap.put(delegatePackage, scopes);
+ }
+ // Add the new scope to the list of delegatePackage if it's not already there.
+ if (!scopes.contains(scope)) {
+ scopes.add(scope);
+ }
} else if ("failed-password-attempts".equals(tag)) {
policy.mFailedPasswordAttempts = Integer.parseInt(
parser.getAttributeValue(null, "value"));
@@ -4527,9 +4587,9 @@
}
@Override
- public void enforceCanManageCaCerts(ComponentName who) {
+ public void enforceCanManageCaCerts(ComponentName who, String callerPackage) {
if (who == null) {
- if (!isCallerDelegatedCertInstaller()) {
+ if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
}
} else {
@@ -4543,35 +4603,6 @@
}
}
- private void enforceCanManageInstalledKeys(ComponentName who) {
- if (who == null) {
- if (!isCallerDelegatedCertInstaller()) {
- throw new SecurityException("who == null, but caller is not cert installer");
- }
- } else {
- enforceProfileOrDeviceOwner(who);
- }
- }
-
- private boolean isCallerDelegatedCertInstaller() {
- final int callingUid = mInjector.binderGetCallingUid();
- final int userHandle = UserHandle.getUserId(callingUid);
- synchronized (this) {
- final DevicePolicyData policy = getUserData(userHandle);
- if (policy.mDelegatedCertInstallerPackage == null) {
- return false;
- }
-
- try {
- int uid = mInjector.getPackageManager().getPackageUidAsUser(
- policy.mDelegatedCertInstallerPackage, userHandle);
- return uid == callingUid;
- } catch (NameNotFoundException e) {
- return false;
- }
- }
- }
-
@Override
public boolean approveCaCert(String alias, int userId, boolean approval) {
enforceManageUsers();
@@ -4613,8 +4644,9 @@
}
@Override
- public boolean installCaCert(ComponentName admin, byte[] certBuffer) throws RemoteException {
- enforceCanManageCaCerts(admin);
+ public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer)
+ throws RemoteException {
+ enforceCanManageCaCerts(admin, callerPackage);
byte[] pemCert;
try {
@@ -4656,8 +4688,8 @@
}
@Override
- public void uninstallCaCerts(ComponentName admin, String[] aliases) {
- enforceCanManageCaCerts(admin);
+ public void uninstallCaCerts(ComponentName admin, String callerPackage, String[] aliases) {
+ enforceCanManageCaCerts(admin, callerPackage);
final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
final long id = mInjector.binderClearCallingIdentity();
@@ -4681,9 +4713,11 @@
}
@Override
- public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, byte[] chain,
- String alias, boolean requestAccess) {
- enforceCanManageInstalledKeys(who);
+ public boolean installKeyPair(ComponentName who, String callerPackage, byte[] privKey,
+ byte[] cert, byte[] chain, String alias, boolean requestAccess) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_CERT_INSTALL);
+
final int callingUid = mInjector.binderGetCallingUid();
final long id = mInjector.binderClearCallingIdentity();
@@ -4714,8 +4748,9 @@
}
@Override
- public boolean removeKeyPair(ComponentName who, String alias) {
- enforceCanManageInstalledKeys(who);
+ public boolean removeKeyPair(ComponentName who, String callerPackage, String alias) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_CERT_INSTALL);
final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
final long id = Binder.clearCallingIdentity();
@@ -4800,33 +4835,267 @@
}.execute();
}
+ /**
+ * Set the scopes of a device owner or profile owner delegate.
+ *
+ * @param who the device owner or profile owner.
+ * @param delegatePackage the name of the delegate package.
+ * @param scopes the list of delegation scopes to be given to the delegate package.
+ */
@Override
- public void setCertInstallerPackage(ComponentName who, String installerPackage)
- throws SecurityException {
- int userHandle = UserHandle.getCallingUserId();
+ public void setDelegatedScopes(ComponentName who, String delegatePackage,
+ List<String> scopes) throws SecurityException {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty");
+ Preconditions.checkCollectionElementsNotNull(scopes, "Scopes");
+ // Remove possible duplicates.
+ scopes = new ArrayList(new ArraySet(scopes));
+ // Ensure given scopes are valid.
+ if (scopes.retainAll(Arrays.asList(DELEGATIONS))) {
+ throw new IllegalArgumentException("Unexpected delegation scopes");
+ }
+
+ // Retrieve the user ID of the calling process.
+ final int userId = mInjector.userHandleGetCallingUserId();
synchronized (this) {
+ // Ensure calling process is device/profile owner.
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (getTargetSdk(who.getPackageName(), userHandle) >= Build.VERSION_CODES.N) {
- if (installerPackage != null &&
- !isPackageInstalledForUser(installerPackage, userHandle)) {
- throw new IllegalArgumentException("Package " + installerPackage
+ // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N).
+ if (scopes.size() == 1 && scopes.get(0).equals(DELEGATION_CERT_INSTALL) ||
+ getTargetSdk(who.getPackageName(), userId) >= Build.VERSION_CODES.N) {
+ // Throw when the delegate package is not installed.
+ if (!isPackageInstalledForUser(delegatePackage, userId)) {
+ throw new IllegalArgumentException("Package " + delegatePackage
+ " is not installed on the current user");
}
}
- DevicePolicyData policy = getUserData(userHandle);
- policy.mDelegatedCertInstallerPackage = installerPackage;
- saveSettingsLocked(userHandle);
+
+ // Set the new delegate in user policies.
+ final DevicePolicyData policy = getUserData(userId);
+ if (!scopes.isEmpty()) {
+ policy.mDelegationMap.put(delegatePackage, new ArrayList<>(scopes));
+ } else {
+ // Remove any delegation info if the given scopes list is empty.
+ policy.mDelegationMap.remove(delegatePackage);
+ }
+
+ // Notify delegate package of updates.
+ final Intent intent = new Intent(
+ DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED);
+ // Only call receivers registered in the manifest (don’t wake app if not running).
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ // Limit components this intent resolves to to the delegate package.
+ intent.setPackage(delegatePackage);
+ // Include the list of delegated scopes as an extra.
+ intent.putExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes.toArray());
+ // Send the broadcast.
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+
+ // Persist updates.
+ saveSettingsLocked(userId);
+ }
+ }
+
+ /**
+ * Get the delegation scopes given to a delegate package by a device owner or profile owner.
+ *
+ * A DO/PO can get the scopes of any package. A non DO/PO package can get its own scopes by
+ * passing in {@code null} as the {@code who} parameter and its own name as the
+ * {@code delegatepackage}.
+ *
+ * @param who the device owner or profile owner, or {@code null} if the caller is
+ * {@code delegatePackage}.
+ * @param delegatePackage the name of the delegate package whose scopes are to be retrieved.
+ * @return a list of the delegation scopes currently given to {@code delegatePackage}.
+ */
+ @Override
+ @NonNull
+ public List<String> getDelegatedScopes(ComponentName who,
+ String delegatePackage) throws SecurityException {
+ Preconditions.checkNotNull(delegatePackage, "Delegate package is null");
+
+ // Retrieve the user ID of the calling process.
+ final int callingUid = mInjector.binderGetCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ synchronized (this) {
+ // Ensure calling process is device/profile owner.
+ if (who != null) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Or ensure calling process is delegatePackage itself.
+ } else {
+ int uid = 0;
+ try {
+ uid = mInjector.getPackageManager()
+ .getPackageUidAsUser(delegatePackage, userId);
+ } catch(NameNotFoundException e) {
+ }
+ if (uid != callingUid) {
+ throw new SecurityException("Caller with uid " + callingUid + " is not "
+ + delegatePackage);
+ }
+ }
+ final DevicePolicyData policy = getUserData(userId);
+ // Retrieve the scopes assigned to delegatePackage, or null if no scope was given.
+ final List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+ return scopes == null ? Collections.EMPTY_LIST : scopes;
+ }
+ }
+
+ /**
+ * Get a list of packages that were given a specific delegation scopes by a device owner or
+ * profile owner.
+ *
+ * @param who the device owner or profile owner.
+ * @param scope the scope whose delegates are to be retrieved.
+ * @return a list of the delegate packages currently given the {@code scope} delegation.
+ */
+ @NonNull
+ public List<String> getDelegatePackages(ComponentName who, String scope)
+ throws SecurityException {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkNotNull(scope, "Scope is null");
+ if (!Arrays.asList(DELEGATIONS).contains(scope)) {
+ throw new IllegalArgumentException("Unexpected delegation scope: " + scope);
+ }
+
+ // Retrieve the user ID of the calling process.
+ final int userId = mInjector.userHandleGetCallingUserId();
+ synchronized (this) {
+ // Ensure calling process is device/profile owner.
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final DevicePolicyData policy = getUserData(userId);
+
+ // Create a list to hold the resulting delegate packages.
+ final List<String> delegatePackagesWithScope = new ArrayList<>();
+ // Add all delegations containing scope to the result list.
+ for (int i = 0; i < policy.mDelegationMap.size(); i++) {
+ if (policy.mDelegationMap.valueAt(i).contains(scope)) {
+ delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i));
+ }
+ }
+ return delegatePackagesWithScope;
+ }
+ }
+
+ /**
+ * Check whether a caller application has been delegated a given scope via
+ * {@link #setDelegatedScopes} to access privileged APIs on the behalf of a profile owner or
+ * device owner.
+ * <p>
+ * This is done by checking that {@code callerPackage} was granted {@code scope} delegation and
+ * then comparing the calling UID with the UID of {@code callerPackage} as reported by
+ * {@link PackageManager#getPackageUidAsUser}.
+ *
+ * @param callerPackage the name of the package that is trying to invoke a function in the DPMS.
+ * @param scope the delegation scope to be checked.
+ * @return {@code true} if the calling process is a delegate of {@code scope}.
+ */
+ private boolean isCallerDelegate(String callerPackage, String scope) {
+ Preconditions.checkNotNull(callerPackage, "callerPackage is null");
+ if (!Arrays.asList(DELEGATIONS).contains(scope)) {
+ throw new IllegalArgumentException("Unexpected delegation scope: " + scope);
+ }
+
+ // Retrieve the UID and user ID of the calling process.
+ final int callingUid = mInjector.binderGetCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ synchronized (this) {
+ // Retrieve user policy data.
+ final DevicePolicyData policy = getUserData(userId);
+ // Retrieve the list of delegation scopes granted to callerPackage.
+ final List<String> scopes = policy.mDelegationMap.get(callerPackage);
+ // Check callingUid only if callerPackage has the required scope delegation.
+ if (scopes != null && scopes.contains(scope)) {
+ try {
+ // Retrieve the expected UID for callerPackage.
+ final int uid = mInjector.getPackageManager()
+ .getPackageUidAsUser(callerPackage, userId);
+ // Return true if the caller is actually callerPackage.
+ return uid == callingUid;
+ } catch (NameNotFoundException e) {
+ // Ignore.
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Throw a security exception if a ComponentName is given and it is not a device/profile owner
+ * or if the calling process is not a delegate of the given scope.
+ *
+ * @param who the device owner of profile owner, or null if {@code callerPackage} is a
+ * {@code scope} delegate.
+ * @param callerPackage the name of the calling package. Required if {@code who} is
+ * {@code null}.
+ * @param reqPolicy the policy used in the API whose access permission is being checked.
+ * @param scoppe the delegation scope corresponding to the API being checked.
+ * @throws SecurityException if {@code who} is given and is not an owner for {@code reqPolicy};
+ * or when {@code who} is {@code null} and {@code callerPackage} is not a delegate
+ * of {@code scope}.
+ */
+ private void enforceCanManageScope(ComponentName who, String callerPackage, int reqPolicy,
+ String scope) {
+ // If a ComponentName is given ensure it is a device or profile owner according to policy.
+ if (who != null) {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, reqPolicy);
+ }
+ // If no ComponentName is given ensure calling process has scope delegation.
+ } else if (!isCallerDelegate(callerPackage, scope)) {
+ throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid()
+ + " is not a delegate of scope " + scope + ".");
+ }
+ }
+
+ /**
+ * Helper function to preserve delegation behavior pre-O when using the deprecated functions
+ * {@code #setCertInstallerPackage} and {@code #setApplicationRestrictionsManagingPackage}.
+ */
+ private void setDelegatedScopePreO(ComponentName who,
+ String delegatePackage, String scope) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ final int userId = mInjector.userHandleGetCallingUserId();
+ synchronized(this) {
+ // Ensure calling process is device/profile owner.
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final DevicePolicyData policy = getUserData(userId);
+
+ if (delegatePackage != null) {
+ // Set package as a delegate for scope if it is not already one.
+ List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+ if (scopes == null) {
+ scopes = new ArrayList<>();
+ }
+ if (!scopes.contains(scope)) {
+ scopes.add(scope);
+ setDelegatedScopes(who, delegatePackage, scopes);
+ }
+ }
+
+ // Clear any existing scope delegates.
+ for (int i = 0; i < policy.mDelegationMap.size(); i++) {
+ final String currentPackage = policy.mDelegationMap.keyAt(i);
+ final List<String> currentScopes = policy.mDelegationMap.valueAt(i);
+
+ if (!currentPackage.equals(delegatePackage) && currentScopes.remove(scope)) {
+ setDelegatedScopes(who, currentPackage, currentScopes);
+ }
+ }
}
}
@Override
+ public void setCertInstallerPackage(ComponentName who, String installerPackage)
+ throws SecurityException {
+ setDelegatedScopePreO(who, installerPackage, DELEGATION_CERT_INSTALL);
+ }
+
+ @Override
public String getCertInstallerPackage(ComponentName who) throws SecurityException {
- int userHandle = UserHandle.getCallingUserId();
- synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- DevicePolicyData policy = getUserData(userHandle);
- return policy.mDelegatedCertInstallerPackage;
- }
+ final List<String> delegatePackages = getDelegatePackages(who, DELEGATION_CERT_INSTALL);
+ return delegatePackages.size() > 0 ? delegatePackages.get(0) : null;
}
/**
@@ -5587,7 +5856,7 @@
}
/**
- * Set whether auto time is required by the specified admin (must be device owner).
+ * Set whether auto time is required by the specified admin (must be device or profile owner).
*/
@Override
public void setAutoTimeRequired(ComponentName who, boolean required) {
@@ -5598,7 +5867,7 @@
final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
ActiveAdmin admin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
if (admin.requireAutoTime != required) {
admin.requireAutoTime = required;
saveSettingsLocked(userHandle);
@@ -5617,7 +5886,7 @@
}
/**
- * Returns whether or not auto time is required by the device owner.
+ * Returns whether or not auto time is required by the device owner or any profile owner.
*/
@Override
public boolean getAutoTimeRequired() {
@@ -5626,7 +5895,20 @@
}
synchronized (this) {
ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- return (deviceOwner != null) ? deviceOwner.requireAutoTime : false;
+ if (deviceOwner != null && deviceOwner.requireAutoTime) {
+ // If the device owner enforces auto time, we don't need to check the PO's
+ return true;
+ }
+
+ // Now check to see if any profile owner on any user enforces auto time
+ for (Integer userId : mOwners.getProfileOwnerKeys()) {
+ ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+ if (profileOwner != null && profileOwner.requireAutoTime) {
+ return true;
+ }
+ }
+
+ return false;
}
}
@@ -6110,7 +6392,8 @@
try {
// TODO Send to system too?
mContext.sendBroadcastAsUser(
- new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
+ new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND),
UserHandle.of(userId));
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -6146,6 +6429,13 @@
}
}
+ private boolean isProfileOwnerPackage(String packageName, int userId) {
+ synchronized (this) {
+ return mOwners.hasProfileOwner(userId)
+ && mOwners.getProfileOwnerPackage(userId).equals(packageName);
+ }
+ }
+
public boolean isProfileOwner(ComponentName who, int userId) {
final ComponentName profileOwner = getProfileOwner(userId);
return who != null && who.equals(profileOwner);
@@ -6253,6 +6543,7 @@
clearDeviceOwnerLocked(admin, deviceOwnerUserId);
removeActiveAdminLocked(deviceOwnerComponent, deviceOwnerUserId);
Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mContext.sendBroadcastAsUser(intent, UserHandle.of(deviceOwnerUserId));
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -6387,11 +6678,11 @@
}
private void clearUserPoliciesLocked(int userId) {
- // Reset some of the user-specific policies
- DevicePolicyData policy = getUserData(userId);
+ // Reset some of the user-specific policies.
+ final DevicePolicyData policy = getUserData(userId);
policy.mPermissionPolicy = DevicePolicyManager.PERMISSION_POLICY_PROMPT;
- policy.mDelegatedCertInstallerPackage = null;
- policy.mApplicationRestrictionsManagingPackage = null;
+ // Clear delegations.
+ policy.mDelegationMap.clear();
policy.mStatusBarDisabled = false;
policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
saveSettingsLocked(userId);
@@ -6975,66 +7266,31 @@
@Override
public boolean setApplicationRestrictionsManagingPackage(ComponentName admin,
String packageName) {
- Preconditions.checkNotNull(admin, "ComponentName is null");
-
- final int userHandle = mInjector.userHandleGetCallingUserId();
- synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (packageName != null && !isPackageInstalledForUser(packageName, userHandle)) {
- return false;
- }
- DevicePolicyData policy = getUserData(userHandle);
- policy.mApplicationRestrictionsManagingPackage = packageName;
- saveSettingsLocked(userHandle);
- return true;
+ try {
+ setDelegatedScopePreO(admin, packageName, DELEGATION_APP_RESTRICTIONS);
+ } catch (IllegalArgumentException e) {
+ return false;
}
+ return true;
}
@Override
public String getApplicationRestrictionsManagingPackage(ComponentName admin) {
- Preconditions.checkNotNull(admin, "ComponentName is null");
-
- final int userHandle = mInjector.userHandleGetCallingUserId();
- synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- DevicePolicyData policy = getUserData(userHandle);
- return policy.mApplicationRestrictionsManagingPackage;
- }
+ final List<String> delegatePackages = getDelegatePackages(admin,
+ DELEGATION_APP_RESTRICTIONS);
+ return delegatePackages.size() > 0 ? delegatePackages.get(0) : null;
}
@Override
- public boolean isCallerApplicationRestrictionsManagingPackage() {
- final int callingUid = mInjector.binderGetCallingUid();
- final int userHandle = UserHandle.getUserId(callingUid);
- synchronized (this) {
- final DevicePolicyData policy = getUserData(userHandle);
- if (policy.mApplicationRestrictionsManagingPackage == null) {
- return false;
- }
-
- try {
- int uid = mInjector.getPackageManager().getPackageUidAsUser(
- policy.mApplicationRestrictionsManagingPackage, userHandle);
- return uid == callingUid;
- } catch (NameNotFoundException e) {
- return false;
- }
- }
- }
-
- private void enforceCanManageApplicationRestrictions(ComponentName who) {
- if (who != null) {
- enforceProfileOrDeviceOwner(who);
- } else if (!isCallerApplicationRestrictionsManagingPackage()) {
- throw new SecurityException(
- "No admin component given, and caller cannot manage application restrictions "
- + "for other apps.");
- }
+ public boolean isCallerApplicationRestrictionsManagingPackage(String callerPackage) {
+ return isCallerDelegate(callerPackage, DELEGATION_APP_RESTRICTIONS);
}
@Override
- public void setApplicationRestrictions(ComponentName who, String packageName, Bundle settings) {
- enforceCanManageApplicationRestrictions(who);
+ public void setApplicationRestrictions(ComponentName who, String callerPackage,
+ String packageName, Bundle settings) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_APP_RESTRICTIONS);
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
final long id = mInjector.binderClearCallingIdentity();
@@ -7724,8 +7980,10 @@
}
@Override
- public Bundle getApplicationRestrictions(ComponentName who, String packageName) {
- enforceCanManageApplicationRestrictions(who);
+ public Bundle getApplicationRestrictions(ComponentName who, String callerPackage,
+ String packageName) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_APP_RESTRICTIONS);
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
final long id = mInjector.binderClearCallingIdentity();
@@ -9119,21 +9377,25 @@
final long ident = mInjector.binderClearCallingIdentity();
try {
final UserHandle callingUserHandle = UserHandle.of(callingUserId);
- if (mUserManager.hasUserRestriction(
- UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle)) {
- // The DO can initiate provisioning if the restriction was set by the DO.
- if (!isDeviceOwnerPackage(packageName, callingUserId)
- || isAdminAffectedByRestriction(mOwners.getDeviceOwnerComponent(),
- UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
- // Caller is not DO or the restriction was set by the system.
+ final ComponentName ownerAdmin = getOwnerComponent(packageName, callingUserId);
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+ callingUserHandle)) {
+ // An admin can initiate provisioning if it has set the restriction.
+ if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
return CODE_ADD_MANAGED_PROFILE_DISALLOWED;
}
}
-
- // TODO: Allow it if the caller is the DO? DO could just call removeUser() before
- // provisioning, so not strictly required...
- boolean canRemoveProfile = !mUserManager.hasUserRestriction(
- UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, callingUserHandle);
+ boolean canRemoveProfile = true;
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ callingUserHandle)) {
+ // We can remove a profile if the admin itself has set the restriction.
+ if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ callingUserId)) {
+ canRemoveProfile = false;
+ }
+ }
if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) {
return CODE_CANNOT_ADD_MANAGED_PROFILE;
}
@@ -9143,6 +9405,16 @@
return CODE_OK;
}
+ private ComponentName getOwnerComponent(String packageName, int userId) {
+ if (isDeviceOwnerPackage(packageName, userId)) {
+ return mOwners.getDeviceOwnerComponent();
+ }
+ if (isProfileOwnerPackage(packageName, userId)) {
+ return mOwners.getProfileOwnerComponent(userId);
+ }
+ return null;
+ }
+
private int checkManagedUserProvisioningPreCondition(int callingUserId) {
if (!hasFeatureManagedUsers()) {
return CODE_MANAGED_USERS_NOT_SUPPORTED;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bedc6e1..c3ef23b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -280,7 +280,7 @@
Slog.i(TAG, "Entered the Android system server!");
int uptimeMillis = (int) SystemClock.elapsedRealtime();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
- if (!mRuntimeRestart && !mFirstBoot) {
+ if (!mRuntimeRestart) {
MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
}
@@ -349,7 +349,6 @@
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);
mSystemServiceManager.setRuntimeRestarted(mRuntimeRestart);
- mSystemServiceManager.setFirstBoot(mFirstBoot);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// Prepare the thread pool for init tasks that can be parallelized
SystemServerInitThreadPool.get();
@@ -376,7 +375,7 @@
if (StrictMode.conditionallyEnableDebugLogging()) {
Slog.i(TAG, "Enabled StrictMode for system server main thread.");
}
- if (!mRuntimeRestart && !mFirstBoot) {
+ if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
int uptimeMillis = (int) SystemClock.elapsedRealtime();
MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
final int MAX_UPTIME_MILLIS = 60 * 1000;
@@ -391,6 +390,10 @@
throw new RuntimeException("Main thread loop unexpectedly exited");
}
+ private boolean isFirstBootOrUpgrade() {
+ return mPackageManagerService.isFirstBoot() || mPackageManagerService.isUpgrade();
+ }
+
private void reportWtf(String msg, Throwable e) {
Slog.w(TAG, "***********************************************");
Slog.wtf(TAG, "BOOT FAILURE " + msg, e);
@@ -535,7 +538,7 @@
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
traceEnd();
- if (!mRuntimeRestart && !mFirstBoot) {
+ if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
MetricsLogger.histogram(null, "boot_package_manager_init_ready",
(int) SystemClock.elapsedRealtime());
}
@@ -936,6 +939,12 @@
traceEnd();
}
+ if (!disableNonCoreServices) {
+ traceBeginAndSlog("StartFontServiceManager");
+ mSystemServiceManager.startService(FontManagerService.Lifecycle.class);
+ traceEnd();
+ }
+
if (!disableNonCoreServices && !disableTextServices) {
traceBeginAndSlog("StartTextServicesManager");
mSystemServiceManager.startService(TextServicesManagerService.Lifecycle.class);
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 785c3fa..af19acb 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -58,6 +58,7 @@
import android.provider.CallLog;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.KeyValueListParser;
import android.util.Slog;
import com.android.internal.os.BackgroundThread;
@@ -105,7 +106,8 @@
private static final String DEMO_SESSION_COUNT = "retail_demo_session_count";
private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration";
- boolean mDeviceInDemoMode = false;
+ boolean mDeviceInDemoMode;
+ boolean mIsCarrierDemoMode;
int mCurrentUserId = UserHandle.USER_SYSTEM;
long mUserInactivityTimeout;
long mWarningDialogTimeout;
@@ -135,7 +137,8 @@
if (!mDeviceInDemoMode) {
return;
}
- switch (intent.getAction()) {
+ final String action = intent.getAction();
+ switch (action) {
case Intent.ACTION_SCREEN_OFF:
mHandler.removeMessages(MSG_TURN_SCREEN_ON);
mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY);
@@ -166,7 +169,7 @@
mInjector.acquireWakeLock();
break;
case MSG_INACTIVITY_TIME_OUT:
- if (isDemoLauncherDisabled()) {
+ if (!mIsCarrierDemoMode && isDemoLauncherDisabled()) {
Slog.i(TAG, "User inactivity timeout reached");
showInactivityCountdownDialog();
}
@@ -177,12 +180,30 @@
}
removeMessages(MSG_START_NEW_SESSION);
removeMessages(MSG_INACTIVITY_TIME_OUT);
- if (mCurrentUserId != UserHandle.USER_SYSTEM) {
+ if (!mIsCarrierDemoMode && mCurrentUserId != UserHandle.USER_SYSTEM) {
logSessionDuration();
}
- final UserInfo demoUser = mInjector.getUserManager().createUser(DEMO_USER_NAME,
- UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
- if (demoUser != null) {
+
+ final UserManager um = mInjector.getUserManager();
+ UserInfo demoUser = null;
+ if (mIsCarrierDemoMode) {
+ // Re-use the existing demo user in carrier demo mode.
+ for (UserInfo user : um.getUsers()) {
+ if (user.isDemo()) {
+ demoUser = user;
+ break;
+ }
+ }
+ }
+
+ if (demoUser == null) {
+ // User in carrier demo mode should survive reboots.
+ final int flags = UserInfo.FLAG_DEMO
+ | (mIsCarrierDemoMode ? 0 : UserInfo.FLAG_EPHEMERAL);
+ demoUser = um.createUser(DEMO_USER_NAME, flags);
+ }
+
+ if (demoUser != null && mCurrentUserId != demoUser.id) {
setupDemoUser(demoUser);
mInjector.switchUser(demoUser.id);
}
@@ -211,7 +232,7 @@
}
public void register() {
- ContentResolver cr = mInjector.getContentResolver();
+ final ContentResolver cr = mInjector.getContentResolver();
cr.registerContentObserver(mDeviceDemoModeUri, false, this, UserHandle.USER_SYSTEM);
cr.registerContentObserver(mDeviceProvisionedUri, false, this, UserHandle.USER_SYSTEM);
cr.registerContentObserver(mRetailDemoConstantsUri, false, this,
@@ -224,30 +245,31 @@
refreshTimeoutConstants();
return;
}
- if (mDeviceDemoModeUri.equals(uri)) {
- mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
- if (mDeviceInDemoMode) {
+
+ // If device is provisioned and left demo mode - run the cleanup in demo folder
+ if (isDeviceProvisioned()) {
+ if (UserManager.isDeviceInDemoMode(getContext())) {
startDemoMode();
} else {
mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0");
+
+ // Run on the bg thread to not block the fg thread
+ BackgroundThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (!deletePreloadsFolderContents()) {
+ Slog.w(TAG, "Failed to delete preloads folder contents");
+ }
+ }
+ });
+
+ stopDemoMode();
+
if (mInjector.isWakeLockHeld()) {
mInjector.releaseWakeLock();
}
}
}
- // If device is provisioned and left demo mode - run the cleanup in demo folder
- if (!mDeviceInDemoMode && isDeviceProvisioned()) {
- // Run on the bg thread to not block the fg thread
- BackgroundThread.getHandler().post(new Runnable() {
- @Override
- public void run() {
- if (!deletePreloadsFolderContents()) {
- Slog.w(TAG, "Failed to delete preloads folder contents");
- }
- }
- });
- stopDemoMode();
- }
}
private void refreshTimeoutConstants() {
@@ -300,23 +322,22 @@
}
boolean isDemoLauncherDisabled() {
- IPackageManager pm = mInjector.getIPackageManager();
int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
- String demoLauncherComponent = getContext().getResources()
- .getString(R.string.config_demoModeLauncherComponent);
try {
- enabledState = pm.getComponentEnabledSetting(
- ComponentName.unflattenFromString(demoLauncherComponent),
- mCurrentUserId);
- } catch (RemoteException exc) {
- Slog.e(TAG, "Unable to talk to Package Manager", exc);
+ final IPackageManager iPm = mInjector.getIPackageManager();
+ final String demoLauncherComponent =
+ getContext().getString(R.string.config_demoModeLauncherComponent);
+ enabledState = iPm.getComponentEnabledSetting(
+ ComponentName.unflattenFromString(demoLauncherComponent), mCurrentUserId);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error retrieving demo launcher enabled setting", re);
}
return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
}
private void setupDemoUser(UserInfo userInfo) {
- UserManager um = mInjector.getUserManager();
- UserHandle user = UserHandle.of(userInfo.id);
+ final UserManager um = mInjector.getUserManager();
+ final UserHandle user = UserHandle.of(userInfo.id);
um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user);
@@ -327,6 +348,7 @@
um.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, user);
// Disallow rebooting in safe mode - controlled by user 0
um.setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, UserHandle.SYSTEM);
+
Settings.Secure.putIntForUser(mInjector.getContentResolver(),
Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
Settings.Global.putInt(mInjector.getContentResolver(),
@@ -334,6 +356,47 @@
grantRuntimePermissionToCamera(user);
clearPrimaryCallLog();
+
+ if (!mIsCarrierDemoMode) {
+ // Enable demo launcher.
+ final String demoLauncher = getContext().getString(
+ R.string.config_demoModeLauncherComponent);
+ if (!TextUtils.isEmpty(demoLauncher)) {
+ final ComponentName componentToEnable =
+ ComponentName.unflattenFromString(demoLauncher);
+ final String packageName = componentToEnable.getPackageName();
+ try {
+ final IPackageManager iPm = AppGlobals.getPackageManager();
+ iPm.setComponentEnabledSetting(componentToEnable,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id);
+ iPm.setApplicationEnabledSetting(packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null);
+ } catch (RemoteException re) {
+ // Internal, shouldn't happen
+ }
+ }
+ } else {
+ // Set the carrier demo mode setting for the demo user.
+ final String carrierDemoModeSetting = getContext().getString(
+ R.string.config_carrierDemoModeSetting);
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ carrierDemoModeSetting, 1, userInfo.id);
+
+ // Enable packages for carrier demo mode.
+ final String packageList = getContext().getString(
+ R.string.config_carrierDemoModePackages);
+ final String[] packageNames = packageList == null ? new String[0]
+ : TextUtils.split(packageList, ",");
+ final IPackageManager iPm = AppGlobals.getPackageManager();
+ for (String packageName : packageNames) {
+ try {
+ iPm.setApplicationEnabledSetting(packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error enabling application: " + packageName, re);
+ }
+ }
+ }
}
private void grantRuntimePermissionToCamera(UserHandle user) {
@@ -385,13 +448,17 @@
}
private void registerBroadcastReceiver() {
- if (mBroadcastReceiver == null) {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(ACTION_RESET_DEMO);
- mBroadcastReceiver = new IntentReceiver();
- getContext().registerReceiver(mBroadcastReceiver, filter);
+ if (mBroadcastReceiver != null) {
+ return;
}
+
+ final IntentFilter filter = new IntentFilter();
+ if (!mIsCarrierDemoMode) {
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ }
+ filter.addAction(ACTION_RESET_DEMO);
+ mBroadcastReceiver = new IntentReceiver();
+ getContext().registerReceiver(mBroadcastReceiver, filter);
}
private void unregisterBroadcastReceiver() {
@@ -427,6 +494,8 @@
}
private void startDemoMode() {
+ mDeviceInDemoMode = true;
+
mPreloadAppsInstaller = mInjector.getPreloadAppsInstaller();
mInjector.initializeWakeLock();
if (mCameraIdsWithFlash == null) {
@@ -434,6 +503,12 @@
}
registerBroadcastReceiver();
+ final String carrierDemoModeSetting =
+ getContext().getString(R.string.config_carrierDemoModeSetting);
+ mIsCarrierDemoMode = !TextUtils.isEmpty(carrierDemoModeSetting)
+ && (Settings.Secure.getInt(getContext().getContentResolver(),
+ carrierDemoModeSetting, 0) == 1);
+
mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
@@ -471,13 +546,12 @@
public void onBootPhase(int bootPhase) {
switch (bootPhase) {
case PHASE_THIRD_PARTY_APPS_CAN_START:
- SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+ final SettingsObserver settingsObserver = new SettingsObserver(mHandler);
settingsObserver.register();
settingsObserver.refreshTimeoutConstants();
break;
case PHASE_BOOT_COMPLETED:
if (UserManager.isDeviceInDemoMode(getContext())) {
- mDeviceInDemoMode = true;
startDemoMode();
}
break;
@@ -497,33 +571,39 @@
Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
return;
}
- if (!mInjector.isWakeLockHeld()) {
+ if (!mIsCarrierDemoMode && !mInjector.isWakeLockHeld()) {
mInjector.acquireWakeLock();
}
mCurrentUserId = userId;
mInjector.getActivityManagerInternal().updatePersistentConfigurationForUser(
mInjector.getSystemUsersConfiguration(), userId);
+
mInjector.turnOffAllFlashLights(mCameraIdsWithFlash);
muteVolumeStreams();
if (!mInjector.getWifiManager().isWifiEnabled()) {
mInjector.getWifiManager().setWifiEnabled(true);
}
+
// Disable lock screen for demo users.
mInjector.getLockPatternUtils().setLockScreenDisabled(true, userId);
- mInjector.getNotificationManager().notifyAsUser(TAG,
- 1, mInjector.createResetNotification(), UserHandle.of(userId));
- synchronized (mActivityLock) {
- mUserUntouched = true;
- }
- mInjector.logSessionCount(1);
- mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mPreloadAppsInstaller.installApps(userId);
+ if (!mIsCarrierDemoMode) {
+ // Show reset notification (except in carrier demo mode).
+ mInjector.getNotificationManager().notifyAsUser(TAG,
+ 1, mInjector.createResetNotification(), UserHandle.of(userId));
+
+ synchronized (mActivityLock) {
+ mUserUntouched = true;
}
- });
+ mInjector.logSessionCount(1);
+ mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mPreloadAppsInstaller.installApps(userId);
+ }
+ });
+ }
}
private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
@@ -531,7 +611,7 @@
@Override
public void onUserActivity() {
- if (!mDeviceInDemoMode) {
+ if (!mDeviceInDemoMode || mIsCarrierDemoMode) {
return;
}
long timeOfActivity = SystemClock.uptimeMillis();
@@ -682,7 +762,7 @@
}
boolean isWakeLockHeld() {
- return mWakeLock.isHeld();
+ return mWakeLock != null && mWakeLock.isHeld();
}
void acquireWakeLock() {
diff --git a/services/tests/notification/AndroidManifest.xml b/services/tests/notification/AndroidManifest.xml
index 1ed8ed0..92f155f 100644
--- a/services/tests/notification/AndroidManifest.xml
+++ b/services/tests/notification/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java
new file mode 100644
index 0000000..b26bac3
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.notification;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BadgeExtractorTest {
+
+ @Mock RankingConfig mConfig;
+
+ private String mPkg = "com.android.server.notification";
+ private int mId = 1001;
+ private String mTag = null;
+ private int mUid = 1000;
+ private int mPid = 2000;
+ private UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private NotificationRecord getNotificationRecord(NotificationChannel channel) {
+ final Builder builder = new Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setDefaults(Notification.DEFAULT_SOUND);
+
+ Notification n = builder.build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
+ mPid, n, mUser, null, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+ return r;
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ //
+ // Tests
+ //
+
+ @Test
+ public void testAppYesChannelNo() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ channel.setShowBadge(false);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertFalse(r.canShowBadge());
+ }
+
+ @Test
+ public void testAppNoChannelYes() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_HIGH);
+ channel.setShowBadge(true);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertFalse(r.canShowBadge());
+ }
+
+ @Test
+ public void testAppYesChannelYes() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ channel.setShowBadge(true);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertTrue(r.canShowBadge());
+ }
+
+ @Test
+ public void testAppNoChannelNo() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ channel.setShowBadge(false);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertFalse(r.canShowBadge());
+ }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index ad436724a..468a26b 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -230,9 +230,9 @@
n.flags |= Notification.FLAG_INSISTENT;
}
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, id, mTag, mUid,
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn);
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
mService.addNotification(r);
return r;
}
diff --git a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
index f48d785..936531b 100644
--- a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
@@ -70,9 +70,7 @@
if (groupKey != null) {
nb.setGroup(groupKey);
}
- NotificationChannel channel =
- new NotificationChannel("test", "test", NotificationManager.IMPORTANCE_LOW);
- return new StatusBarNotification(pkg, pkg, channel, id, tag, 0, 0, nb.build(), user, null,
+ return new StatusBarNotification(pkg, pkg, id, tag, 0, 0, nb.build(), user, null,
System.currentTimeMillis());
}
diff --git a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
index eee9cf1..f8a32bb 100644
--- a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
@@ -69,9 +69,9 @@
.setDefaults(Notification.DEFAULT_SOUND);
Notification n = builder.build();
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, mId, mTag, mUid,
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn);
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
return r;
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
index 403b65c..aa08b41 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
@@ -105,8 +105,8 @@
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordMinCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
- callPkg, getDefaultChannel(), 1, "minCall", callUid, callUid, n1,
- new UserHandle(userId), "", 2000));
+ callPkg, 1, "minCall", callUid, callUid, n1,
+ new UserHandle(userId), "", 2000), getDefaultChannel());
mRecordMinCall.setUserImportance(NotificationManager.IMPORTANCE_MIN);
Notification n2 = new Notification.Builder(mContext)
@@ -114,8 +114,8 @@
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordHighCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
- callPkg, getDefaultChannel(), 1, "highcall", callUid, callUid, n2,
- new UserHandle(userId), "", 1999));
+ callPkg, 1, "highcall", callUid, callUid, n2,
+ new UserHandle(userId), "", 1999), getDefaultChannel());
mRecordHighCall.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
Notification n3 = new Notification.Builder(mContext)
@@ -124,43 +124,43 @@
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "media", uid2, uid2, n3, new UserHandle(userId),
- "", 1499));
+ pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId),
+ "", 1499), getDefaultChannel());
mRecordDefaultMedia.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n4 = new Notification.Builder(mContext)
.setStyle(new Notification.MessagingStyle("sender!")).build();
mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId),
- "", 1599));
+ pkg2, 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId),
+ "", 1599), getDefaultChannel());
mRecordInlineReply.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
mRecordInlineReply.setPackagePriority(Notification.PRIORITY_MAX);
Notification n5 = new Notification.Builder(mContext)
.setCategory(Notification.CATEGORY_MESSAGE).build();
mRecordSms = new NotificationRecord(mContext, new StatusBarNotification(smsPkg,
- smsPkg, getDefaultChannel(), 1, "sms", smsUid, smsUid, n5, new UserHandle(userId),
- "", 1299));
+ smsPkg, 1, "sms", smsUid, smsUid, n5, new UserHandle(userId),
+ "", 1299), getDefaultChannel());
mRecordSms.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n6 = new Notification.Builder(mContext).build();
mRecordStarredContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "starred", uid2, uid2, n6, new UserHandle(userId),
- "", 1259));
+ pkg2, 1, "starred", uid2, uid2, n6, new UserHandle(userId),
+ "", 1259), getDefaultChannel());
mRecordStarredContact.setContactAffinity(ValidateNotificationPeople.STARRED_CONTACT);
mRecordStarredContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n7 = new Notification.Builder(mContext).build();
mRecordContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "contact", uid2, uid2, n7, new UserHandle(userId),
- "", 1259));
+ pkg2, 1, "contact", uid2, uid2, n7, new UserHandle(userId),
+ "", 1259), getDefaultChannel());
mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
mRecordContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n8 = new Notification.Builder(mContext).build();
mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
- "", 1258));
+ pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
+ "", 1258), getDefaultChannel());
mRecordUrgent.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
Notification n9 = new Notification.Builder(mContext)
@@ -169,15 +169,15 @@
|Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordCheater = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "cheater", uid2, uid2, n9, new UserHandle(userId),
- "", 9258));
+ pkg2, 1, "cheater", uid2, uid2, n9, new UserHandle(userId),
+ "", 9258), getDefaultChannel());
mRecordCheater.setUserImportance(NotificationManager.IMPORTANCE_LOW);
Notification n10 = new Notification.Builder(mContext)
.setStyle(new Notification.InboxStyle().setSummaryText("message!")).build();
mRecordEmail = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "email", uid2, uid2, n10, new UserHandle(userId),
- "", 1599));
+ pkg2, 1, "email", uid2, uid2, n10, new UserHandle(userId),
+ "", 1599), getDefaultChannel());
mRecordEmail.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
index b6166f6..f0f4c4d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -59,6 +59,7 @@
assertEquals(getChannel(key, i), ranking.getChannel());
assertEquals(getPeople(key, i), ranking.getAdditionalPeople());
assertEquals(getSnoozeCriteria(key, i), ranking.getSnoozeCriteria());
+ assertEquals(getShowBadge(i), ranking.canShowBadge());
}
}
@@ -68,9 +69,10 @@
Bundle overrideGroupKeys = new Bundle();
Bundle suppressedVisualEffects = new Bundle();
Bundle explanation = new Bundle();
- Bundle overrideChannels = new Bundle();
+ Bundle channels = new Bundle();
Bundle overridePeople = new Bundle();
Bundle snoozeCriteria = new Bundle();
+ Bundle showBadge = new Bundle();
int[] importance = new int[mKeys.length];
for (int i = 0; i < mKeys.length; i++) {
@@ -83,14 +85,15 @@
suppressedVisualEffects.putInt(key, getSuppressedVisualEffects(i));
importance[i] = getImportance(i);
explanation.putString(key, getExplanation(key));
- overrideChannels.putParcelable(key, getChannel(key, i));
+ channels.putParcelable(key, getChannel(key, i));
overridePeople.putStringArrayList(key, getPeople(key, i));
snoozeCriteria.putParcelableArrayList(key, getSnoozeCriteria(key, i));
+ showBadge.putBoolean(key, getShowBadge(i));
}
NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
interceptedKeys.toArray(new String[0]), visibilityOverrides,
suppressedVisualEffects, importance, explanation, overrideGroupKeys,
- overrideChannels, overridePeople, snoozeCriteria);
+ channels, overridePeople, snoozeCriteria, showBadge);
return update;
}
@@ -122,6 +125,10 @@
return new NotificationChannel(key, key, getImportance(index));
}
+ private boolean getShowBadge(int index) {
+ return index % 3 == 0;
+ }
+
private ArrayList<String> getPeople(String key, int index) {
ArrayList<String> people = new ArrayList<>();
for (int i = 0; i < index; i++) {
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 40938fd..250aab8 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -24,7 +24,9 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,18 +35,25 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.MessageQueue;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.annotation.UiThreadTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -58,30 +67,76 @@
@RunWith(AndroidJUnit4.class)
public class NotificationManagerServiceTest {
private final String pkg = "com.android.server.notification";
- private final int uid = 0;
+ private final int uid = Binder.getCallingUid();
private NotificationManagerService mNotificationManagerService;
private INotificationManager mBinderService;
private IPackageManager mPackageManager = mock(IPackageManager.class);
+ private Context mContext;
+ private HandlerThread mThread;
@Before
@UiThreadTest
public void setUp() throws Exception {
- final Context context = InstrumentationRegistry.getTargetContext();
- mNotificationManagerService = new NotificationManagerService(context);
+ mContext = InstrumentationRegistry.getTargetContext();
+ mNotificationManagerService = new NotificationManagerService(mContext);
// MockPackageManager - default returns ApplicationInfo with matching calling UID
final ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.uid = Binder.getCallingUid();
+ applicationInfo.uid = uid;
when(mPackageManager.getApplicationInfo(any(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
+ final PackageManager mockPackageManagerClient = mock(PackageManager.class);
+ when(mockPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
final LightsManager mockLightsManager = mock(LightsManager.class);
when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
- mNotificationManagerService.init(mPackageManager, mockLightsManager);
+ // Use a separate thread for service looper.
+ mThread = new HandlerThread("TestThread");
+ mThread.start();
+ // Mock NotificationListeners to bypass security checks.
+ final NotificationManagerService.NotificationListeners mockNotificationListeners =
+ mock(NotificationManagerService.NotificationListeners.class);
+ when(mockNotificationListeners.checkServiceTokenLocked(any())).thenReturn(
+ mockNotificationListeners.new ManagedServiceInfo(null,
+ new ComponentName(pkg, "test_class"), uid, true, null, 0));
+
+ mNotificationManagerService.init(mThread.getLooper(), mPackageManager,
+ mockPackageManagerClient, mockLightsManager, mockNotificationListeners);
// Tests call directly into the Binder.
mBinderService = mNotificationManagerService.getBinderService();
}
+ public void waitForIdle() throws Exception {
+ MessageQueue queue = mThread.getLooper().getQueue();
+ CountDownLatch latch = new CountDownLatch(1);
+ queue.addIdleHandler(new MessageQueue.IdleHandler() {
+ @Override public boolean queueIdle() {
+ latch.countDown();
+ return false;
+ }
+ });
+ latch.await();
+ if (!queue.isIdle()) {
+ waitForIdle();
+ }
+ }
+
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel) {
+ if (channel == null) {
+ channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
+ }
+ Notification n = new Notification.Builder(mContext)
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
+ mContext.getPackageName(), 1, "tag", uid, 0,
+ n, new UserHandle(uid), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
@Test
@UiThreadTest
public void testCreateNotificationChannels_SingleChannel() throws Exception {
@@ -203,15 +258,107 @@
verify(usageStats, times(1)).registerBlocked(eq(r));
}
- private NotificationRecord generateNotificationRecord(NotificationChannel channel) {
- final Context context = InstrumentationRegistry.getTargetContext();
- Notification n = new Notification.Builder(context)
- .setContentTitle("foo")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setPriority(Notification.PRIORITY_HIGH)
- .build();
- StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, channel, 1, "tag", uid, uid,
- n, UserHandle.SYSTEM, null, uid);
- return new NotificationRecord(context, sbn);
+ @Test
+ @UiThreadTest
+ public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
+ mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ generateNotificationRecord(null).getNotification(), new int[1], 0);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(mContext.getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
+ 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",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelNotificationsFromListener(null, null);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelAllNotifications(null, sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], UserHandle.USER_ALL);
+ // Null pkg is how we signal a user switch.
+ mBinderService.cancelAllNotifications(null, sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(1, notifs.length);
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index fc94271f..15dcc26 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -136,10 +136,10 @@
Notification n = builder.build();
if (preO) {
- return new StatusBarNotification(pkg, pkg, defaultChannel, id1, tag1, uid, uid, n,
+ return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n,
mUser, null, uid);
} else {
- return new StatusBarNotification(pkg2, pkg2, channel, id2, tag2, uid2, uid2, n,
+ return new StatusBarNotification(pkg2, pkg2, id2, tag2, uid2, uid2, n,
mUser, null, uid2);
}
}
@@ -155,7 +155,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
}
@@ -166,7 +166,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_SOUND, record.getSound());
}
@@ -178,7 +178,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_SOUND, record.getSound());
}
@@ -189,7 +189,7 @@
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(CUSTOM_SOUND, record.getSound());
}
@@ -200,7 +200,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, true /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertNotNull(record.getVibration());
}
@@ -211,7 +211,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_VIBRATION, record.getVibration());
}
@@ -223,7 +223,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertTrue(!Objects.equals(CUSTOM_VIBRATION, record.getVibration()));
}
@@ -234,7 +234,7 @@
StatusBarNotification sbn = getNotification(false /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(CUSTOM_CHANNEL_VIBRATION, record.getVibration());
}
@@ -245,7 +245,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
}
@@ -256,7 +256,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
}
@@ -264,7 +264,7 @@
public void testImportance_preUpgrade() throws Exception {
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(NotificationManager.IMPORTANCE_HIGH, record.getImportance());
}
@@ -275,7 +275,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(NotificationManager.IMPORTANCE_LOW, record.getImportance());
}
@@ -283,7 +283,7 @@
public void testImportance_upgrade() throws Exception {
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(NotificationManager.IMPORTANCE_DEFAULT, record.getImportance());
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 59ac427..0320d8a 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -105,8 +105,8 @@
.setWhen(1205)
.build();
mRecordGroupGSortA = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiGroupGSortA, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiGroupGSortA, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
mNotiGroupGSortB = new Notification.Builder(getContext())
.setContentTitle("B")
@@ -115,24 +115,24 @@
.setWhen(1200)
.build();
mRecordGroupGSortB = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiGroupGSortB, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiGroupGSortB, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
mNotiNoGroup = new Notification.Builder(getContext())
.setContentTitle("C")
.setWhen(1201)
.build();
mRecordNoGroup = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroup, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiNoGroup, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
mNotiNoGroup2 = new Notification.Builder(getContext())
.setContentTitle("D")
.setWhen(1202)
.build();
mRecordNoGroup2 = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroup2, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiNoGroup2, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
mNotiNoGroupSortA = new Notification.Builder(getContext())
.setContentTitle("E")
@@ -140,8 +140,8 @@
.setSortKey("A")
.build();
mRecordNoGroupSortA = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroupSortA, user,
- null, System.currentTimeMillis()));
+ "package", "package", 1, null, 0, 0, mNotiNoGroupSortA, user,
+ null, System.currentTimeMillis()), getDefaultChannel());
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
@@ -254,8 +254,12 @@
mHelper.createNotificationChannel(pkg, uid, channel1, true);
mHelper.createNotificationChannel(pkg, uid, channel2, false);
+ mHelper.setShowBadge(pkg, uid, true);
+ mHelper.setShowBadge(pkg2, uid2, false);
+
ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(), channel2.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
@@ -263,6 +267,8 @@
parser.nextTag();
mHelper.readXml(parser, false);
+ assertFalse(mHelper.canShowBadge(pkg2, uid2));
+ assertTrue(mHelper.canShowBadge(pkg, uid));
assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
compareChannels(channel2,
mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
@@ -758,4 +764,11 @@
mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
}
+
+ @Test
+ public void testRecordDefaults() throws Exception {
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
+ assertEquals(true, mHelper.canShowBadge(pkg, uid));
+ assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+ }
}
diff --git a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
index 460fcdf..69724f4 100644
--- a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
@@ -33,6 +33,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any;
@@ -42,6 +43,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
@SmallTest
@@ -190,6 +192,39 @@
verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
}
+ @Test
+ public void testGetSnoozedByUser() throws Exception {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
+ NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT);
+ mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(r2, 1000);
+ mSnoozeHelper.snooze(r3, 1000);
+ mSnoozeHelper.snooze(r4, 1000);
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(
+ new int[] {UserHandle.USER_SYSTEM});
+ assertEquals(3, mSnoozeHelper.getSnoozed().size());
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(
+ new int[] {UserHandle.USER_CURRENT});
+ assertEquals(1, mSnoozeHelper.getSnoozed().size());
+ }
+
+ @Test
+ public void testGetSnoozedByUser_managedProfiles() throws Exception {
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(
+ new int[] {UserHandle.USER_SYSTEM, UserHandle.USER_CURRENT});
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
+ NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT);
+ mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(r2, 1000);
+ mSnoozeHelper.snooze(r3, 1000);
+ mSnoozeHelper.snooze(r4, 1000);
+ assertEquals(4, mSnoozeHelper.getSnoozed().size());
+ }
+
private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
UserHandle user) {
Notification n = new Notification.Builder(getContext())
@@ -199,8 +234,8 @@
.setWhen(1205)
.build();
return new NotificationRecord(getContext(), new StatusBarNotification(
- pkg, pkg, getDefaultChannel(), id, tag, 0, 0, n, user, null,
- System.currentTimeMillis()));
+ pkg, pkg, id, tag, 0, 0, n, user, null,
+ System.currentTimeMillis()), getDefaultChannel());
}
private NotificationChannel getDefaultChannel() {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 43c8957..4ca29cd 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -63,7 +63,10 @@
import android.net.ScoredNetwork;
import android.net.Uri;
import android.net.WifiKey;
+import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -82,6 +85,8 @@
import com.android.server.devicepolicy.MockUtils;
+import com.google.android.collect.Lists;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -96,9 +101,12 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
/**
* Tests for {@link NetworkScoreService}.
@@ -106,19 +114,27 @@
@RunWith(AndroidJUnit4.class)
@MediumTest
public class NetworkScoreServiceTest {
+ 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 ScoredNetwork SCORED_NETWORK =
- new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
+ new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID), "00:00:00:00:00:00")),
+ null /* rssiCurve*/);
+ private static final ScoredNetwork SCORED_NETWORK_2 =
+ new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID_2), "00:00:00:00:00:00")),
null /* rssiCurve*/);
private static final NetworkScorerAppData NEW_SCORER =
new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass");
- @Mock private PackageManager mPackageManager;
@Mock private NetworkScorerAppManager mNetworkScorerAppManager;
@Mock private Context mContext;
@Mock private Resources mResources;
@Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
@Mock private IBinder mIBinder, mIBinder2;
@Mock private INetworkRecommendationProvider mRecommendationProvider;
+ @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
+ @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;
+ @Mock private WifiInfo mWifiInfo;
@Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
private ContentResolver mContentResolver;
@@ -127,6 +143,11 @@
private RemoteCallback mRemoteCallback;
private OnResultListener mOnResultListener;
private HandlerThread mHandlerThread;
+ private List<ScanResult> mScanResults;
+
+ private static String quote(String str) {
+ return String.format("\"%s\"", str);
+ }
@Before
public void setUp() throws Exception {
@@ -136,6 +157,8 @@
mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getResources()).thenReturn(mResources);
+ when(mWifiInfo.getSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.ssid);
+ when(mWifiInfo.getBSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.bssid);
mHandlerThread = new HandlerThread("NetworkScoreServiceTest");
mHandlerThread.start();
mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager,
@@ -150,6 +173,21 @@
Settings.Global.putLong(mContentResolver,
Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L);
mNetworkScoreService.refreshRecommendationRequestTimeoutMs();
+ populateScanResults();
+ }
+
+ private void populateScanResults() {
+ mScanResults = new ArrayList<>();
+ mScanResults.add(createScanResult(SSID, SCORED_NETWORK.networkKey.wifiKey.bssid));
+ mScanResults.add(createScanResult(SSID_2, SCORED_NETWORK_2.networkKey.wifiKey.bssid));
+ mScanResults.add(createScanResult(SSID_3, "10:10:00:00:10:10"));
+ }
+
+ private ScanResult createScanResult(String ssid, String bssid) {
+ ScanResult result = new ScanResult();
+ result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
+ result.BSSID = bssid;
+ return result;
}
@After
@@ -622,6 +660,173 @@
assertEquals(NEW_SCORER.packageName, mNetworkScoreService.getActiveScorerPackage());
}
+ @Test
+ public void testCacheUpdatingConsumer_nullFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+ NetworkScoreService.FilteringCacheUpdatingConsumer consumer =
+ new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList), NetworkKey.TYPE_WIFI,
+ mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, null /*cookie*/);
+
+ verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+ verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_noneFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+
+ verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+ verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_unknownFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, -1 /*cookie*/);
+
+ verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+ verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_nonIntFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, "not an int" /*cookie*/);
+
+ verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+ verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_emptyScoreList() throws Exception {
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ Collections.emptyList(),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+
+ verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_currentNetworkFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList =
+ Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
+ filteredList.remove(SCORED_NETWORK);
+ when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
+
+ verify(mNetworkScoreCache).updateScores(filteredList);
+ verifyZeroInteractions(mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_scanResultsFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList =
+ Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
+ filteredList.remove(SCORED_NETWORK);
+ when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+
+ verify(mNetworkScoreCache).updateScores(filteredList);
+ verifyZeroInteractions(mCurrentNetworkFilter);
+ }
+
+ @Test
+ public void testCurrentNetworkScoreCacheFilter_nullWifiInfo() throws Exception {
+ NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+ new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> null /*WifiInfo*/);
+
+ 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);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ List<ScoredNetwork> expectedList = Collections.singletonList(SCORED_NETWORK);
+ assertEquals(expectedList, actualList);
+ }
+
+ @Test
+ public void testCurrentNetworkScoreCacheFilter_currentNetworkNotInList() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn("\"notInList\"");
+ 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 testScanResultsScoreCacheFilter_emptyScanResults() throws Exception {
+ NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter =
+ new NetworkScoreService.ScanResultsScoreCacheFilter(Collections::emptyList);
+
+ 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);
+
+ ScoredNetwork unmatchedScore =
+ new ScoredNetwork(new NetworkKey(new WifiKey(quote("newSsid"),
+ "00:00:00:00:00:00")), null /* rssiCurve*/);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2,
+ unmatchedScore));
+
+ List<ScoredNetwork> expectedList = Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2);
+ assertEquals(expectedList, actualList);
+ }
+
// "injects" the mock INetworkRecommendationProvider into the NetworkScoreService.
private void injectProvider() {
final ComponentName componentName = new ComponentName(NEW_SCORER.packageName,
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index ee49a00..c8c8c0e 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -45,9 +45,11 @@
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCacheListener;
import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.content.pm.RegisteredServicesCache.ServiceInfo;
import android.database.Cursor;
@@ -76,15 +78,18 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class AccountManagerServiceTest extends AndroidTestCase {
private static final String TAG = AccountManagerServiceTest.class.getSimpleName();
+ private static final long ONE_DAY_IN_MILLISECOND = 86400000;
@Mock private Context mMockContext;
@Mock private AppOpsManager mMockAppOpsManager;
@@ -94,6 +99,7 @@
@Mock private DevicePolicyManager mMockDevicePolicyManager;
@Mock private IAccountManagerResponse mMockAccountManagerResponse;
@Mock private IBinder mMockBinder;
+ @Mock private INotificationManager mMockNotificationManager;
@Captor private ArgumentCaptor<Intent> mIntentCaptor;
@Captor private ArgumentCaptor<Bundle> mBundleCaptor;
@@ -102,6 +108,7 @@
private static final String PREN_DB = "pren.db";
private static final String DE_DB = "de.db";
private static final String CE_DB = "ce.db";
+ private PackageInfo mPackageInfo;
private AccountManagerService mAms;
private TestInjector mTestInjector;
@@ -113,7 +120,16 @@
.thenReturn(PackageManager.SIGNATURE_MATCH);
final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0);
when(mMockUserManager.getUserInfo(eq(ui.id))).thenReturn(ui);
+ when(mMockContext.createPackageContextAsUser(
+ anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+
+ mPackageInfo = new PackageInfo();
+ mPackageInfo.signatures = new Signature[1];
+ mPackageInfo.signatures[0] = new Signature(new byte[] {'a', 'b', 'c', 'd'});
+ mPackageInfo.applicationInfo = new ApplicationInfo();
+ mPackageInfo.applicationInfo.privateFlags = ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
when(mMockContext.getSystemServiceName(AppOpsManager.class)).thenReturn(
@@ -129,7 +145,7 @@
Context realTestContext = getContext();
MyMockContext mockContext = new MyMockContext(realTestContext, mMockContext);
setContext(mockContext);
- mTestInjector = new TestInjector(realTestContext, mockContext);
+ mTestInjector = new TestInjector(realTestContext, mockContext, mMockNotificationManager);
mAms = new AccountManagerService(mTestInjector);
}
@@ -500,7 +516,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserSuccessWithoutPasswordForwarding() throws Exception {
+ public void testStartAddAccountSessionSuccessWithoutPasswordForwarding() throws Exception {
unlockSystemUser();
when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
PackageManager.PERMISSION_DENIED);
@@ -531,7 +547,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserSuccessWithPasswordForwarding() throws Exception {
+ public void testStartAddAccountSessionSuccessWithPasswordForwarding() throws Exception {
unlockSystemUser();
when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
PackageManager.PERMISSION_GRANTED);
@@ -564,7 +580,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserReturnWithInvalidIntent() throws Exception {
+ public void testStartAddAccountSessionReturnWithInvalidIntent() throws Exception {
unlockSystemUser();
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = new ActivityInfo();
@@ -593,7 +609,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserReturnWithValidIntent() throws Exception {
+ public void testStartAddAccountSessionReturnWithValidIntent() throws Exception {
unlockSystemUser();
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = new ActivityInfo();
@@ -626,7 +642,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserError() throws Exception {
+ public void testStartAddAccountSessionError() throws Exception {
unlockSystemUser();
Bundle options = createOptionsWithAccountName(
AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR);
@@ -650,14 +666,1782 @@
verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
}
+ @SmallTest
+ public void testStartUpdateCredentialsSessionWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.startUpdateCredentialsSession(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType",
+ true, // expectActivityLaunch
+ null); // optionsIn
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionWithNullAccount() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.startUpdateCredentialsSession(
+ mMockAccountManagerResponse, // response
+ null,
+ "authTokenType",
+ true, // expectActivityLaunch
+ null); // optionsIn
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionSuccessWithoutPasswordForwarding()
+ throws Exception {
+ unlockSystemUser();
+ when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType",
+ false, // expectActivityLaunch
+ options); // optionsIn
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+ assertNotNull(sessionBundle);
+ // Assert that session bundle is encrypted and hence data not visible.
+ assertNull(sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1));
+ // Assert password is not returned
+ assertNull(result.getString(AccountManager.KEY_PASSWORD));
+ assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN,
+ result.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionSuccessWithPasswordForwarding() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType",
+ false, // expectActivityLaunch
+ options); // optionsIn
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+ assertNotNull(sessionBundle);
+ // Assert that session bundle is encrypted and hence data not visible.
+ assertNull(sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1));
+ // Assert password is returned
+ assertEquals(result.getString(AccountManager.KEY_PASSWORD),
+ AccountManagerServiceTestFixtures.ACCOUNT_PASSWORD);
+ assertNull(result.getString(AccountManager.KEY_AUTHTOKEN));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN,
+ result.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionReturnWithInvalidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE);
+
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ "authTokenType",
+ true, // expectActivityLaunch
+ options); // optionsIn
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionReturnWithValidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE);
+
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ "authTokenType",
+ true, // expectActivityLaunch
+ options); // optionsIn
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ assertNotNull(intent);
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionError() throws Exception {
+ unlockSystemUser();
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR);
+ options.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_INVALID_RESPONSE);
+ options.putString(AccountManager.KEY_ERROR_MESSAGE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+ "authTokenType",
+ true, // expectActivityLaunch
+ options); // optionsIn
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.finishSessionAsUser(
+ null, // response
+ createEncryptedSessionBundle(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserWithNullSessionBundle() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ null, // sessionBundle
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserUserCannotModifyAccountNoDPM() throws Exception {
+ unlockSystemUser();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+ verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.of(2)));
+
+ // verify the intent for default CantAddAccountActivity is sent.
+ Intent intent = mIntentCaptor.getValue();
+ assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName());
+ assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0),
+ AccountManager.ERROR_CODE_USER_RESTRICTED);
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserUserCannotModifyAccountWithDPM() throws Exception {
+ unlockSystemUser();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(
+ DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal);
+ when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent(
+ anyInt(), anyString())).thenReturn(new Intent());
+ when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent(
+ anyInt(), anyBoolean())).thenReturn(new Intent());
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+ verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.of(2)));
+ verify(mMockDevicePolicyManagerInternal).createUserRestrictionSupportIntent(
+ anyInt(), anyString());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserWithBadSessionBundle() throws Exception {
+ unlockSystemUser();
+
+ Bundle badSessionBundle = new Bundle();
+ badSessionBundle.putString("any", "any");
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ badSessionBundle, // sessionBundle
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_BAD_REQUEST), anyString());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserWithBadAccountType() throws Exception {
+ unlockSystemUser();
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundleWithNoAccountType(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_BAD_ARGUMENTS), anyString());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserUserCannotModifyAccountForTypeNoDPM() throws Exception {
+ unlockSystemUser();
+ when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+ .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+ verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.of(2)));
+
+ // verify the intent for default CantAddAccountActivity is sent.
+ Intent intent = mIntentCaptor.getValue();
+ assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName());
+ assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0),
+ AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE);
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserUserCannotModifyAccountForTypeWithDPM() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+ mMockDevicePolicyManager);
+ when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+ .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(
+ DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal);
+ when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent(
+ anyInt(), anyString())).thenReturn(new Intent());
+ when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent(
+ anyInt(), anyBoolean())).thenReturn(new Intent());
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+ verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.of(2)));
+ verify(mMockDevicePolicyManagerInternal).createShowAdminSupportIntent(
+ anyInt(), anyBoolean());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserSuccess() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.finishSessionAsUser(
+ response, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+ // Verify notification is cancelled
+ verify(mMockNotificationManager).cancelNotificationWithTag(
+ anyString(), anyString(), anyInt(), anyInt());
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+ assertNotNull(sessionBundle);
+ // Assert that session bundle is decrypted and hence data is visible.
+ assertEquals(AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1,
+ sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1));
+ // Assert finishSessionAsUser added calling uid and pid into the sessionBundle
+ assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+ assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_PID));
+ // Assert App bundle data overrides sessionBundle data
+ assertEquals(sessionBundle.getString(
+ AccountManager.KEY_ANDROID_PACKAGE_NAME), "APCT.package");
+
+ // Verify response data
+ assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME,
+ result.getString(AccountManager.KEY_ACCOUNT_NAME));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserReturnWithInvalidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.finishSessionAsUser(
+ response, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE),
+ true, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserReturnWithValidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.finishSessionAsUser(
+ response, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE),
+ true, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ assertNotNull(intent);
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserError() throws Exception {
+ unlockSystemUser();
+ Bundle sessionBundle = createEncryptedSessionBundleWithError(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.finishSessionAsUser(
+ response, // response
+ sessionBundle,
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.isCredentialsUpdateSuggested(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedWithNullAccount() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.isCredentialsUpdateSuggested(
+ mMockAccountManagerResponse,
+ null, // account
+ AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedWithEmptyStatusToken() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.isCredentialsUpdateSuggested(
+ mMockAccountManagerResponse,
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ null);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedError() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.isCredentialsUpdateSuggested(
+ response,
+ AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+ AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedSuccess() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.isCredentialsUpdateSuggested(
+ response,
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ boolean needUpdate = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ assertTrue(needUpdate);
+ }
+
+ @SmallTest
+ public void testHasFeaturesWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.hasFeatures(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ new String[] {"feature1", "feature2"}, // features
+ "testPackage"); // opPackageName
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testHasFeaturesWithNullAccount() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.hasFeatures(
+ mMockAccountManagerResponse, // response
+ null, // account
+ new String[] {"feature1", "feature2"}, // features
+ "testPackage"); // opPackageName
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testHasFeaturesWithNullFeature() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.hasFeatures(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
+ null, // features
+ "testPackage"); // opPackageName
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+
+ }
+
+ @SmallTest
+ public void testHasFeaturesReadAccountsNotPermitted() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+ .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+ try {
+ mAms.hasFeatures(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
+ new String[] {"feature1", "feature2"}, // features
+ "testPackage"); // opPackageName
+ fail("SecurityException expected. But no exception was thrown.");
+ } catch (SecurityException e) {
+ } catch(Exception e){
+ fail(String.format("Expect SecurityException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testHasFeaturesReturnNullResult() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.hasFeatures(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_ERROR, // account
+ AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+ "testPackage"); // opPackageName
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), anyString());
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testHasFeaturesSuccess() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.hasFeatures(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
+ AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+ "testPackage"); // opPackageName
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ boolean hasFeatures = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ assertTrue(hasFeatures);
+ }
+
+ @SmallTest
+ public void testRemoveAccountAsUserWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.removeAccountAsUser(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testRemoveAccountAsUserWithNullAccount() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.removeAccountAsUser(
+ mMockAccountManagerResponse, // response
+ null, // account
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testRemoveAccountAsUserAccountNotManagedByCaller() throws Exception {
+ unlockSystemUser();
+ when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+ .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+ try {
+ mAms.removeAccountAsUser(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ fail("SecurityException expected. But no exception was thrown.");
+ } catch (SecurityException e) {
+ } catch(Exception e){
+ fail(String.format("Expect SecurityException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testRemoveAccountAsUserUserCannotModifyAccount() throws Exception {
+ unlockSystemUser();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.removeAccountAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testRemoveAccountAsUserUserCannotModifyAccountType() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+ mMockDevicePolicyManager);
+ when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+ .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.removeAccountAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testRemoveAccountAsUserRemovalAllowed() throws Exception {
+ unlockSystemUser();
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p1", null);
+ Account[] addedAccounts =
+ mAms.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName());
+ assertEquals(1, addedAccounts.length);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.removeAccountAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ boolean allowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ assertTrue(allowed);
+ Account[] accounts = mAms.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName());
+ assertEquals(0, accounts.length);
+ }
+
+ @SmallTest
+ public void testRemoveAccountAsUserRemovalNotAllowed() throws Exception {
+ unlockSystemUser();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.removeAccountAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ boolean allowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ assertFalse(allowed);
+ }
+
+ @SmallTest
+ public void testRemoveAccountAsUserReturnWithValidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.removeAccountAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ assertNotNull(intent);
+ }
+
+ @SmallTest
+ public void testGetAuthTokenLabelWithNullAccountType() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.getAuthTokenLabel(
+ mMockAccountManagerResponse, // response
+ null, // accountType
+ "authTokenType");
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testGetAuthTokenLabelWithNullAuthTokenType() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.getAuthTokenLabel(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ null); // authTokenType
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testGetAuthTokenWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.getAuthToken(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType", // authTokenType
+ true, // notifyOnAuthFailure
+ true, // expectActivityLaunch
+ createGetAuthTokenOptions());
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testGetAuthTokenWithNullAccount() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAuthToken(
+ response, // response
+ null, // account
+ "authTokenType", // authTokenType
+ true, // notifyOnAuthFailure
+ true, // expectActivityLaunch
+ createGetAuthTokenOptions());
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_BAD_ARGUMENTS), anyString());
+ }
+
+ @SmallTest
+ public void testGetAuthTokenWithNullAuthTokenType() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAuthToken(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ null, // authTokenType
+ true, // notifyOnAuthFailure
+ true, // expectActivityLaunch
+ createGetAuthTokenOptions());
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_BAD_ARGUMENTS), anyString());
+ }
+
+ @SmallTest
+ public void testGetAuthTokenWithInvalidPackage() throws Exception {
+ unlockSystemUser();
+ String[] list = new String[]{"test"};
+ when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+ try {
+ mAms.getAuthToken(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType", // authTokenType
+ true, // notifyOnAuthFailure
+ true, // expectActivityLaunch
+ createGetAuthTokenOptions());
+ fail("SecurityException expected. But no exception was thrown.");
+ } catch (SecurityException e) {
+ } catch(Exception e){
+ fail(String.format("Expect SecurityException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testGetAuthTokenFromInternal() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.createPackageContextAsUser(
+ anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+ when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+
+ mAms.setAuthToken(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType", AccountManagerServiceTestFixtures.AUTH_TOKEN);
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAuthToken(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType", // authTokenType
+ true, // notifyOnAuthFailure
+ true, // expectActivityLaunch
+ createGetAuthTokenOptions());
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ assertEquals(result.getString(AccountManager.KEY_AUTHTOKEN),
+ AccountManagerServiceTestFixtures.AUTH_TOKEN);
+ assertEquals(result.getString(AccountManager.KEY_ACCOUNT_NAME),
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+ assertEquals(result.getString(AccountManager.KEY_ACCOUNT_TYPE),
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+ }
+
+ @SmallTest
+ public void testGetAuthTokenSuccess() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.createPackageContextAsUser(
+ anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+ when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAuthToken(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType", // authTokenType
+ true, // notifyOnAuthFailure
+ false, // expectActivityLaunch
+ createGetAuthTokenOptions());
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ assertEquals(result.getString(AccountManager.KEY_AUTHTOKEN),
+ AccountManagerServiceTestFixtures.AUTH_TOKEN);
+ assertEquals(result.getString(AccountManager.KEY_ACCOUNT_NAME),
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+ assertEquals(result.getString(AccountManager.KEY_ACCOUNT_TYPE),
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+ }
+
+ @SmallTest
+ public void testGetAuthTokenReturnWithInvalidIntent() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.createPackageContextAsUser(
+ anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+ when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAuthToken(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ "authTokenType", // authTokenType
+ true, // notifyOnAuthFailure
+ false, // expectActivityLaunch
+ createGetAuthTokenOptions());
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+ }
+
+ @SmallTest
+ public void testGetAuthTokenReturnWithValidIntent() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.createPackageContextAsUser(
+ anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+ when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAuthToken(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ "authTokenType", // authTokenType
+ false, // notifyOnAuthFailure
+ true, // expectActivityLaunch
+ createGetAuthTokenOptions());
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ assertNotNull(intent);
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+ }
+
+ @SmallTest
+ public void testGetAuthTokenError() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.createPackageContextAsUser(
+ anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+ when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAuthToken(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+ "authTokenType", // authTokenType
+ true, // notifyOnAuthFailure
+ false, // expectActivityLaunch
+ createGetAuthTokenOptions());
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.addAccountAsUser(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ null, // optionsIn
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserWithNullAccountType() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.addAccountAsUser(
+ mMockAccountManagerResponse, // response
+ null, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ null, // optionsIn
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserUserCannotModifyAccountNoDPM() throws Exception {
+ unlockSystemUser();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+ mAms.addAccountAsUser(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ null, // optionsIn
+ UserHandle.USER_SYSTEM);
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+ verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.SYSTEM));
+
+ // verify the intent for default CantAddAccountActivity is sent.
+ Intent intent = mIntentCaptor.getValue();
+ assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName());
+ assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0),
+ AccountManager.ERROR_CODE_USER_RESTRICTED);
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserUserCannotModifyAccountWithDPM() throws Exception {
+ unlockSystemUser();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(
+ DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal);
+ when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent(
+ anyInt(), anyString())).thenReturn(new Intent());
+ when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent(
+ anyInt(), anyBoolean())).thenReturn(new Intent());
+
+ mAms.addAccountAsUser(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ null, // optionsIn
+ UserHandle.USER_SYSTEM);
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+ verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.SYSTEM));
+ verify(mMockDevicePolicyManagerInternal).createUserRestrictionSupportIntent(
+ anyInt(), anyString());
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserUserCannotModifyAccountForTypeNoDPM() throws Exception {
+ unlockSystemUser();
+ when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+ .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+ mAms.addAccountAsUser(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ null, // optionsIn
+ UserHandle.USER_SYSTEM);
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+ verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.SYSTEM));
+
+ // verify the intent for default CantAddAccountActivity is sent.
+ Intent intent = mIntentCaptor.getValue();
+ assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName());
+ assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0),
+ AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE);
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserUserCannotModifyAccountForTypeWithDPM() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+ mMockDevicePolicyManager);
+ when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+ .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(
+ DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal);
+ when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent(
+ anyInt(), anyString())).thenReturn(new Intent());
+ when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent(
+ anyInt(), anyBoolean())).thenReturn(new Intent());
+
+ mAms.addAccountAsUser(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ null, // optionsIn
+ UserHandle.USER_SYSTEM);
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+ verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.SYSTEM));
+ verify(mMockDevicePolicyManagerInternal).createShowAdminSupportIntent(
+ anyInt(), anyBoolean());
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserSuccess() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.addAccountAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ UserHandle.USER_SYSTEM);
+ waitForLatch(latch);
+ // Verify notification is cancelled
+ verify(mMockNotificationManager).cancelNotificationWithTag(
+ anyString(), anyString(), anyInt(), anyInt());
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ // Verify response data
+ assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS,
+ result.getString(AccountManager.KEY_ACCOUNT_NAME));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+
+ Bundle optionBundle = result.getParcelable(
+ AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE);
+ // Assert addAccountAsUser added calling uid and pid into the option bundle
+ assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+ assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_PID));
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserReturnWithInvalidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.addAccountAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE),
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserReturnWithValidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.addAccountAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE),
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ assertNotNull(intent);
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+ }
+
+ @SmallTest
+ public void testAddAccountAsUserError() throws Exception {
+ unlockSystemUser();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.addAccountAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR),
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testConfirmCredentialsAsUserWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.confirmCredentialsAsUser(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ new Bundle(), // options
+ false, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testConfirmCredentialsAsUserWithNullAccount() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.confirmCredentialsAsUser(
+ mMockAccountManagerResponse, // response
+ null, // account
+ new Bundle(), // options
+ false, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testConfirmCredentialsAsUserSuccess() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.confirmCredentialsAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ new Bundle(), // options
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ // Verify response data
+ assertTrue(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS,
+ result.getString(AccountManager.KEY_ACCOUNT_NAME));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+ }
+
+ @SmallTest
+ public void testConfirmCredentialsAsUserReturnWithInvalidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.confirmCredentialsAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ new Bundle(), // options
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+ }
+
+ @SmallTest
+ public void testConfirmCredentialsAsUserReturnWithValidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.confirmCredentialsAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ new Bundle(), // options
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ assertNotNull(intent);
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+ }
+
+ @SmallTest
+ public void testConfirmCredentialsAsUserError() throws Exception {
+ unlockSystemUser();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.confirmCredentialsAsUser(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+ new Bundle(), // options
+ true, // expectActivityLaunch
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testUpdateCredentialsWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.updateCredentials(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType",
+ false, // expectActivityLaunch
+ new Bundle()); // options
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testUpdateCredentialsWithNullAccount() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.updateCredentials(
+ mMockAccountManagerResponse, // response
+ null, // account
+ "authTokenType",
+ false, // expectActivityLaunch
+ new Bundle()); // options
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testUpdateCredentialsSuccess() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.updateCredentials(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType",
+ false, // expectActivityLaunch
+ new Bundle()); // options
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ // Verify response data
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS,
+ result.getString(AccountManager.KEY_ACCOUNT_NAME));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+ }
+
+ @SmallTest
+ public void testUpdateCredentialsReturnWithInvalidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.updateCredentials(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ "authTokenType",
+ true, // expectActivityLaunch
+ new Bundle()); // options
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+ }
+
+ @SmallTest
+ public void testUpdateCredentialsReturnWithValidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.updateCredentials(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ "authTokenType",
+ true, // expectActivityLaunch
+ new Bundle()); // options
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ assertNotNull(intent);
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+ }
+
+ @SmallTest
+ public void testUpdateCredentialsError() throws Exception {
+ unlockSystemUser();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.updateCredentials(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+ "authTokenType",
+ false, // expectActivityLaunch
+ new Bundle()); // options
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testEditPropertiesWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.editProperties(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ false); // expectActivityLaunch
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testEditPropertiesWithNullAccountType() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.editProperties(
+ mMockAccountManagerResponse, // response
+ null, // accountType
+ false); // expectActivityLaunch
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testEditPropertiesAccountNotManagedByCaller() throws Exception {
+ unlockSystemUser();
+ when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+ .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+ try {
+ mAms.editProperties(
+ mMockAccountManagerResponse, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ false); // expectActivityLaunch
+ fail("SecurityException expected. But no exception was thrown.");
+ } catch (SecurityException e) {
+ } catch(Exception e){
+ fail(String.format("Expect SecurityException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testEditPropertiesSuccess() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.editProperties(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ false); // expectActivityLaunch
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ // Verify response data
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS,
+ result.getString(AccountManager.KEY_ACCOUNT_NAME));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+ }
+
private void waitForLatch(CountDownLatch latch) {
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
- fail("should not throw an InterruptedException");
+ throw new IllegalStateException("Should not throw an InterruptedException", e);
}
}
+ private Bundle createAddAccountOptions(String accountName) {
+ Bundle options = new Bundle();
+ options.putString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME, accountName);
+ return options;
+ }
+
+ private Bundle createGetAuthTokenOptions() {
+ Bundle options = new Bundle();
+ options.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME,
+ AccountManagerServiceTestFixtures.CALLER_PACKAGE);
+ options.putLong(AccountManagerServiceTestFixtures.KEY_TOKEN_EXPIRY,
+ System.currentTimeMillis() + ONE_DAY_IN_MILLISECOND);
+ return options;
+ }
+
+ private Bundle encryptBundleWithCryptoHelper(Bundle sessionBundle) {
+ Bundle encryptedBundle = null;
+ try {
+ CryptoHelper cryptoHelper = CryptoHelper.getInstance();
+ encryptedBundle = cryptoHelper.encryptBundle(sessionBundle);
+ } catch (GeneralSecurityException e) {
+ throw new IllegalStateException("Failed to encrypt session bundle.", e);
+ }
+ return encryptedBundle;
+ }
+
+ private Bundle createEncryptedSessionBundle(final String accountName) {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME, accountName);
+ sessionBundle.putString(
+ AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1,
+ AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1);
+ sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE,
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+ sessionBundle.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, "APCT.session.package");
+ return encryptBundleWithCryptoHelper(sessionBundle);
+ }
+
+ private Bundle createEncryptedSessionBundleWithError(final String accountName) {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME, accountName);
+ sessionBundle.putString(
+ AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1,
+ AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1);
+ sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE,
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+ sessionBundle.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, "APCT.session.package");
+ sessionBundle.putInt(
+ AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_INVALID_RESPONSE);
+ sessionBundle.putString(AccountManager.KEY_ERROR_MESSAGE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ return encryptBundleWithCryptoHelper(sessionBundle);
+ }
+
+ private Bundle createEncryptedSessionBundleWithNoAccountType(final String accountName) {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME, accountName);
+ sessionBundle.putString(
+ AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1,
+ AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1);
+ sessionBundle.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, "APCT.session.package");
+ return encryptBundleWithCryptoHelper(sessionBundle);
+ }
+
+ private Bundle createAppBundle() {
+ Bundle appBundle = new Bundle();
+ appBundle.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, "APCT.package");
+ return appBundle;
+ }
+
private Bundle createOptionsWithAccountName(final String accountName) {
Bundle sessionBundle = new Bundle();
sessionBundle.putString(
@@ -769,6 +2553,12 @@
public String getOpPackageName() {
return mMockContext.getOpPackageName();
}
+
+ @Override
+ public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ return mMockContext.createPackageContextAsUser(packageName, flags, user);
+ }
}
static class TestAccountAuthenticatorCache extends AccountAuthenticatorCache {
@@ -784,9 +2574,13 @@
static class TestInjector extends AccountManagerService.Injector {
private Context mRealContext;
- TestInjector(Context realContext, Context mockContext) {
+ private INotificationManager mMockNotificationManager;
+ TestInjector(Context realContext,
+ Context mockContext,
+ INotificationManager mockNotificationManager) {
super(mockContext);
mRealContext = realContext;
+ mMockNotificationManager = mockNotificationManager;
}
@Override
@@ -820,7 +2614,7 @@
@Override
INotificationManager getNotificationManager() {
- return mock(INotificationManager.class);
+ return mMockNotificationManager;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
index 9a2c190..614680e 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
@@ -31,7 +31,8 @@
"account_manager_service_test:account_status_token_key";
public static final String KEY_ACCOUNT_PASSWORD =
"account_manager_service_test:account_password_key";
-
+ public static final String KEY_OPTIONS_BUNDLE =
+ "account_manager_service_test:option_bundle_key";
public static final String ACCOUNT_NAME_SUCCESS = "success_on_return@fixture.com";
public static final String ACCOUNT_NAME_INTERVENE = "intervene@fixture.com";
public static final String ACCOUNT_NAME_ERROR = "error@fixture.com";
@@ -47,7 +48,20 @@
public static final String ACCOUNT_STATUS_TOKEN =
"com.android.server.accounts.account_manager_service_test.account.status.token";
-
+ public static final String AUTH_TOKEN_LABEL =
+ "com.android.server.accounts.account_manager_service_test.auth.token.label";
+ public static final String AUTH_TOKEN =
+ "com.android.server.accounts.account_manager_service_test.auth.token";
+ public static final String KEY_TOKEN_EXPIRY =
+ "com.android.server.accounts.account_manager_service_test.auth.token.expiry";
+ public static final String ACCOUNT_FEATURE1 =
+ "com.android.server.accounts.account_manager_service_test.feature1";
+ public static final String ACCOUNT_FEATURE2 =
+ "com.android.server.accounts.account_manager_service_test.feature2";
+ public static final String[] ACCOUNT_FEATURES =
+ new String[]{ACCOUNT_FEATURE1, ACCOUNT_FEATURE2};
+ public static final String CALLER_PACKAGE =
+ "com.android.server.accounts.account_manager_service_test.caller.package";
public static final String ACCOUNT_PASSWORD =
"com.android.server.accounts.account_manager_service_test.account.password";
public static final String KEY_RESULT = "account_manager_service_test:result";
diff --git a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
index 0db11e0c..02b34ab 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
@@ -45,8 +45,15 @@
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
- throw new UnsupportedOperationException(
- "editProperties is not yet supported by the TestAccountAuthenticator");
+ Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_ACCOUNT_NAME,
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE,
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+ result.putString(
+ AccountManager.KEY_AUTHTOKEN,
+ Integer.toString(mTokenCounter.incrementAndGet()));
+ return result;
}
@Override
@@ -59,10 +66,38 @@
if (!mAccountType.equals(accountType)) {
throw new IllegalArgumentException("Request to the wrong authenticator!");
}
+ String accountName = null;
+
+ if (options != null) {
+ accountName = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME);
+ }
Bundle result = new Bundle();
- result.putString(AccountManager.KEY_ACCOUNT_NAME, "test_account@test.com");
- result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType);
+ if (accountName.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+ // fill bundle with a success result.
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType);
+ result.putString(AccountManager.KEY_AUTHTOKEN,
+ Integer.toString(mTokenCounter.incrementAndGet()));
+ result.putParcelable(AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE, options);
+ } else if (accountName.equals(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+ // Specify data to be returned by the eventual activity.
+ Intent eventualActivityResultData = new Intent();
+ eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName);
+ eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType);
+ // Fill result with Intent.
+ Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+ intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT, eventualActivityResultData);
+ intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+
+ result.putParcelable(AccountManager.KEY_INTENT, intent);
+ } else {
+ fillResultWithError(
+ result,
+ AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ }
return result;
}
@@ -71,8 +106,38 @@
AccountAuthenticatorResponse response,
Account account,
Bundle options) throws NetworkErrorException {
- throw new UnsupportedOperationException(
- "confirmCredentials is not yet supported by the TestAccountAuthenticator");
+ if (!mAccountType.equals(account.type)) {
+ throw new IllegalArgumentException("Request to the wrong authenticator!");
+ }
+ Bundle result = new Bundle();
+
+ if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+ // fill bundle with a success result.
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+ // Specify data to be returned by the eventual activity.
+ Intent eventualActivityResultData = new Intent();
+ eventualActivityResultData.putExtra(AccountManager.KEY_BOOLEAN_RESULT, true);
+ eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+
+ // Fill result with Intent.
+ Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+ intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT,
+ eventualActivityResultData);
+ intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+
+ result.putParcelable(AccountManager.KEY_INTENT, intent);
+ } else {
+ // fill with error
+ fillResultWithError(
+ result,
+ AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ }
+ return result;
}
@Override
@@ -81,14 +146,53 @@
Account account,
String authTokenType,
Bundle options) throws NetworkErrorException {
- throw new UnsupportedOperationException(
- "getAuthToken is not yet supported by the TestAccountAuthenticator");
+ if (!mAccountType.equals(account.type)) {
+ throw new IllegalArgumentException("Request to the wrong authenticator!");
+ }
+ Bundle result = new Bundle();
+
+ long expiryMillis = (options == null)
+ ? 0 : options.getLong(AccountManagerServiceTestFixtures.KEY_TOKEN_EXPIRY);
+ if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+ // fill bundle with a success result.
+ result.putString(
+ AccountManager.KEY_AUTHTOKEN, AccountManagerServiceTestFixtures.AUTH_TOKEN);
+ result.putLong(
+ AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY,
+ expiryMillis);
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+ // Specify data to be returned by the eventual activity.
+ Intent eventualActivityResultData = new Intent();
+ eventualActivityResultData.putExtra(
+ AccountManager.KEY_AUTHTOKEN, AccountManagerServiceTestFixtures.AUTH_TOKEN);
+ eventualActivityResultData.putExtra(
+ AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY,
+ expiryMillis);
+ eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+
+ // Fill result with Intent.
+ Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+ intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT,
+ eventualActivityResultData);
+ intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+
+ result.putParcelable(AccountManager.KEY_INTENT, intent);
+
+ } else {
+ fillResultWithError(
+ result,
+ AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ }
+ return result;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
- throw new UnsupportedOperationException(
- "getAuthTokenLabel is not yet supported by the TestAccountAuthenticator");
+ return AccountManagerServiceTestFixtures.AUTH_TOKEN_LABEL;
}
@Override
@@ -101,8 +205,31 @@
throw new IllegalArgumentException("Request to the wrong authenticator!");
}
Bundle result = new Bundle();
- result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
- result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+
+ if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+ // fill bundle with a success result.
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+ // Specify data to be returned by the eventual activity.
+ Intent eventualActivityResultData = new Intent();
+ eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+
+ // Fill result with Intent.
+ Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+ intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT,
+ eventualActivityResultData);
+ intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+
+ result.putParcelable(AccountManager.KEY_INTENT, intent);
+ } else {
+ // fill with error
+ fillResultWithError(
+ result,
+ AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ }
return result;
}
@@ -111,8 +238,17 @@
AccountAuthenticatorResponse response,
Account account,
String[] features) throws NetworkErrorException {
- throw new UnsupportedOperationException(
- "hasFeatures is not yet supported by the TestAccountAuthenticator");
+ Bundle result = new Bundle();
+ if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+ // fill bundle with a success result.
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+ } else {
+ // return null for error
+ result = null;
+ }
+
+ response.onResult(result);
+ return null;
}
@Override
@@ -197,7 +333,8 @@
result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
result.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN,
AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
- result.putString(AccountManager.KEY_PASSWORD, "doesn't matter");
+ result.putString(AccountManager.KEY_PASSWORD,
+ AccountManagerServiceTestFixtures.ACCOUNT_PASSWORD);
result.putString(AccountManager.KEY_AUTHTOKEN,
Integer.toString(mTokenCounter.incrementAndGet()));
} else if (accountName.equals(
@@ -243,6 +380,8 @@
Bundle result = new Bundle();
if (accountName.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+ // add sessionBundle into result for verification purpose
+ result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
// fill bundle with a success result.
result.putString(AccountManager.KEY_ACCOUNT_NAME,
AccountManagerServiceTestFixtures.ACCOUNT_NAME);
@@ -288,13 +427,31 @@
} else {
// fill with error
fillResultWithError(
- result, AccountManager.ERROR_CODE_INVALID_RESPONSE, "Default Error Message");
+ result,
+ AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
}
response.onResult(result);
return null;
}
+ @Override
+ public Bundle getAccountRemovalAllowed(
+ AccountAuthenticatorResponse response, Account account) throws NetworkErrorException {
+ Bundle result = new Bundle();
+ if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+ } else if (account.name.equals(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+ Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+ result.putParcelable(AccountManager.KEY_INTENT, intent);
+ } else {
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ }
+ return result;
+ }
+
private void fillResultWithError(Bundle result, Bundle options) {
int errorCode = AccountManager.ERROR_CODE_INVALID_RESPONSE;
String errorMsg = "Default Error Message";
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 f86a6b6..5d4c3cf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -68,6 +68,9 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
@@ -1129,6 +1132,7 @@
public void testSetGetApplicationRestriction() {
setAsProfileOwner(admin1);
+ mContext.packageName = admin1.getPackageName();
{
Bundle rest = new Bundle();
@@ -1160,29 +1164,131 @@
assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg2").size());
}
+ /**
+ * Setup a package in the package manager mock. Useful for faking installed applications.
+ *
+ * @param packageName the name of the package to be setup
+ * @param appId the application ID to be given to the package
+ * @return the UID of the package as known by the mock package manager
+ */
+ private int setupPackageInPackageManager(final String packageName, final int appId)
+ throws Exception {
+ // Make the PackageManager return the package instead of throwing a NameNotFoundException
+ final PackageInfo pi = new PackageInfo();
+ pi.applicationInfo = new ApplicationInfo();
+ pi.applicationInfo.flags = ApplicationInfo.FLAG_HAS_CODE;
+ doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
+ eq(packageName),
+ anyInt(),
+ eq(DpmMockContext.CALLER_USER_HANDLE));
+ // Setup application UID with the PackageManager
+ final int uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, appId);
+ doReturn(uid).when(mContext.packageManager).getPackageUidAsUser(
+ eq(packageName),
+ eq(DpmMockContext.CALLER_USER_HANDLE));
+ // Associate packageName to uid
+ doReturn(packageName).when(mContext.ipackageManager).getNameForUid(eq(uid));
+ doReturn(new String[]{packageName})
+ .when(mContext.ipackageManager).getPackagesForUid(eq(uid));
+ return uid;
+ }
+
+ /**
+ * 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.
+ */
+ public void testDelegation() throws Exception {
+ setAsProfileOwner(admin1);
+
+ final int userHandle = DpmMockContext.CALLER_USER_HANDLE;
+
+ // Given two packages
+ final String CERT_DELEGATE = "com.delegate.certs";
+ final String RESTRICTIONS_DELEGATE = "com.delegate.apprestrictions";
+ final int CERT_DELEGATE_UID = setupPackageInPackageManager(CERT_DELEGATE, 20988);
+ final int RESTRICTIONS_DELEGATE_UID = setupPackageInPackageManager(RESTRICTIONS_DELEGATE,
+ 20989);
+
+ // On delegation
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
+ dpm.setCertInstallerPackage(admin1, CERT_DELEGATE);
+ dpm.setApplicationRestrictionsManagingPackage(admin1, RESTRICTIONS_DELEGATE);
+
+ // DPMS correctly stores and retrieves the delegates
+ DevicePolicyManagerService.DevicePolicyData policy = dpms.mUserData.get(userHandle);
+ assertEquals(2, policy.mDelegationMap.size());
+ MoreAsserts.assertContentsInAnyOrder(policy.mDelegationMap.get(CERT_DELEGATE),
+ DELEGATION_CERT_INSTALL);
+ MoreAsserts.assertContentsInAnyOrder(dpm.getDelegatedScopes(admin1, CERT_DELEGATE),
+ DELEGATION_CERT_INSTALL);
+ assertEquals(CERT_DELEGATE, dpm.getCertInstallerPackage(admin1));
+ MoreAsserts.assertContentsInAnyOrder(policy.mDelegationMap.get(RESTRICTIONS_DELEGATE),
+ DELEGATION_APP_RESTRICTIONS);
+ MoreAsserts.assertContentsInAnyOrder(dpm.getDelegatedScopes(admin1, RESTRICTIONS_DELEGATE),
+ DELEGATION_APP_RESTRICTIONS);
+ assertEquals(RESTRICTIONS_DELEGATE, dpm.getApplicationRestrictionsManagingPackage(admin1));
+
+ // On calling install certificate APIs from an unauthorized process
+ mContext.binder.callingUid = RESTRICTIONS_DELEGATE_UID;
+ mContext.packageName = RESTRICTIONS_DELEGATE;
+
+ // DPMS throws a SecurityException
+ try {
+ dpm.installCaCert(null, null);
+ fail("Didn't throw SecurityException on unauthorized access");
+ } catch (SecurityException expected) {
+ }
+
+ // On calling install certificate APIs from an authorized process
+ mContext.binder.callingUid = CERT_DELEGATE_UID;
+ mContext.packageName = CERT_DELEGATE;
+
+ // DPMS executes without a SecurityException
+ try {
+ dpm.installCaCert(null, null);
+ } catch (SecurityException unexpected) {
+ fail("Threw SecurityException on authorized access");
+ } catch (NullPointerException expected) {
+ }
+
+ // On removing a delegate
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
+ dpm.setCertInstallerPackage(admin1, null);
+
+ // DPMS does not allow access to ex-delegate
+ mContext.binder.callingUid = CERT_DELEGATE_UID;
+ mContext.packageName = CERT_DELEGATE;
+ try {
+ dpm.installCaCert(null, null);
+ fail("Didn't throw SecurityException on unauthorized access");
+ } catch (SecurityException expected) {
+ }
+
+ // But still allows access to other existing delegates
+ mContext.binder.callingUid = RESTRICTIONS_DELEGATE_UID;
+ mContext.packageName = RESTRICTIONS_DELEGATE;
+ try {
+ dpm.getApplicationRestrictions(null, "pkg");
+ } catch (SecurityException expected) {
+ fail("Threw SecurityException on authorized access");
+ }
+ }
+
public void testApplicationRestrictionsManagingApp() throws Exception {
setAsProfileOwner(admin1);
final String nonExistAppRestrictionsManagerPackage = "com.google.app.restrictions.manager2";
final String appRestrictionsManagerPackage = "com.google.app.restrictions.manager";
final int appRestrictionsManagerAppId = 20987;
- final int appRestrictionsManagerUid = UserHandle.getUid(
- DpmMockContext.CALLER_USER_HANDLE, appRestrictionsManagerAppId);
- doReturn(appRestrictionsManagerUid).when(mContext.packageManager).getPackageUidAsUser(
- eq(appRestrictionsManagerPackage),
- eq(DpmMockContext.CALLER_USER_HANDLE));
- mContext.binder.callingUid = appRestrictionsManagerUid;
-
- final PackageInfo pi = new PackageInfo();
- pi.applicationInfo = new ApplicationInfo();
- pi.applicationInfo.flags = ApplicationInfo.FLAG_HAS_CODE;
- doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
- eq(appRestrictionsManagerPackage),
- anyInt(),
- eq(DpmMockContext.CALLER_USER_HANDLE));
+ final int appRestrictionsManagerUid = setupPackageInPackageManager(
+ appRestrictionsManagerPackage, appRestrictionsManagerAppId);
// appRestrictionsManager package shouldn't be able to manage restrictions as the PO hasn't
// delegated that permission yet.
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage());
Bundle rest = new Bundle();
rest.putString("KEY_STRING", "Foo1");
@@ -1191,18 +1297,21 @@
fail("Didn't throw expected SecurityException");
} catch (SecurityException expected) {
MoreAsserts.assertContainsRegex(
- "caller cannot manage application restrictions", expected.getMessage());
+ "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+ expected.getMessage());
}
try {
dpm.getApplicationRestrictions(null, "pkg1");
fail("Didn't throw expected SecurityException");
} catch (SecurityException expected) {
MoreAsserts.assertContainsRegex(
- "caller cannot manage application restrictions", expected.getMessage());
+ "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+ expected.getMessage());
}
// Check via the profile owner that no restrictions were set.
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size());
// Check the API does not allow setting a non-existent package
@@ -1222,6 +1331,7 @@
// Now that package should be able to set and retrieve app restrictions.
mContext.binder.callingUid = appRestrictionsManagerUid;
+ mContext.packageName = appRestrictionsManagerPackage;
assertTrue(dpm.isCallerApplicationRestrictionsManagingPackage());
dpm.setApplicationRestrictions(null, "pkg1", rest);
Bundle returned = dpm.getApplicationRestrictions(null, "pkg1");
@@ -1237,12 +1347,14 @@
fail("Didn't throw expected SecurityException");
} catch (SecurityException expected) {
MoreAsserts.assertContainsRegex(
- "caller cannot manage application restrictions", expected.getMessage());
+ "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+ expected.getMessage());
}
// The DPM is still able to manage app restrictions, even if it allowed another app to do it
// too.
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
assertEquals(returned, dpm.getApplicationRestrictions(admin1, "pkg1"));
dpm.setApplicationRestrictions(admin1, "pkg1", null);
assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size());
@@ -1251,13 +1363,15 @@
dpm.setApplicationRestrictionsManagingPackage(admin1, null);
assertNull(dpm.getApplicationRestrictionsManagingPackage(admin1));
mContext.binder.callingUid = appRestrictionsManagerUid;
+ mContext.packageName = appRestrictionsManagerPackage;
assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage());
try {
dpm.setApplicationRestrictions(null, "pkg1", null);
fail("Didn't throw expected SecurityException");
} catch (SecurityException expected) {
MoreAsserts.assertContainsRegex(
- "caller cannot manage application restrictions", expected.getMessage());
+ "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+ expected.getMessage());
}
}
@@ -2359,6 +2473,23 @@
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
}
+ private void setup_nonSplitUser_withDo_primaryUser() throws Exception {
+ setDeviceOwner();
+ setup_nonSplitUser_afterDeviceSetup_primaryUser();
+ setUpPackageManagerForFakeAdmin(adminAnotherPackage, DpmMockContext.ANOTHER_UID, admin2);
+ }
+
+ private void setup_nonSplitUser_withDo_primaryUser_ManagedProfile() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
+ final int MANAGED_PROFILE_USER_ID = 18;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+ when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
+ false /* we can't remove a managed profile */)).thenReturn(false);
+ when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
+ true)).thenReturn(true);
+ }
+
public void testIsProvisioningAllowed_nonSplitUser_afterDeviceSetup_primaryUser()
throws Exception {
setup_nonSplitUser_afterDeviceSetup_primaryUser();
@@ -2388,144 +2519,124 @@
DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT);
}
- public void testIsProvisioningAllowed_nonSplitUser_withDo_primaryUser() throws Exception {
- setDeviceOwner();
- setup_nonSplitUser_afterDeviceSetup_primaryUser();
- setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
+ public void testProvisioning_nonSplitUser_withDo_primaryUser() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
mContext.packageName = admin1.getPackageName();
-
- final ComponentName adminDifferentPackage =
- new ComponentName("another.package", "whatever.random.class");
- final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 948);
- setUpPackageManagerForFakeAdmin(adminDifferentPackage, ANOTHER_UID, admin2);
-
- // COMP mode is allowed.
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
- when(mContext.userManager.hasUserRestriction(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(true);
-
- // The DO should be allowed to initiate provisioning if it set the restriction itself.
- when(mContext.userManager.getUserRestrictionSource(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
- // But another app should not
- mContext.binder.callingUid = ANOTHER_UID;
- mContext.packageName = adminDifferentPackage.getPackageName();
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-
- // The DO should not be allowed to initiate provisioning if the restriction is set by
- // another entity.
- when(mContext.userManager.getUserRestrictionSource(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
- mContext.packageName = admin1.getPackageName();
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-
- mContext.binder.callingUid = ANOTHER_UID;
- mContext.packageName = adminDifferentPackage.getPackageName();
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
- }
-
- public void testIsProvisioningAllowed_nonSplitUser_comp() throws Exception {
- setDeviceOwner();
- setup_nonSplitUser_afterDeviceSetup_primaryUser();
- setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
-
- final ComponentName adminDifferentPackage =
- new ComponentName("another.package", "whatever.class");
- final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 948);
- setUpPackageManagerForFakeAdmin(adminDifferentPackage, ANOTHER_UID, admin2);
-
- final int MANAGED_PROFILE_USER_ID = 18;
- final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
- addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
-
- when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
- false /* we can't remove a managed profile */)).thenReturn(false);
- when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
- true)).thenReturn(true);
-
- // We can delete the managed profile to create a new one, so provisioning is allowed.
- mContext.packageName = admin1.getPackageName();
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
- mContext.packageName = adminDifferentPackage.getPackageName();
- mContext.binder.callingUid = ANOTHER_UID;
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
- when(mContext.userManager.hasUserRestriction(
- eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
- eq(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE))))
- .thenReturn(true);
-
- // Now, we can't remove the profile any more to create a new one.
- mContext.packageName = admin1.getPackageName();
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-
- mContext.packageName = adminDifferentPackage.getPackageName();
- mContext.binder.callingUid = ANOTHER_UID;
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
- }
-
- public void
- testCheckProvisioningPreCondition_nonSplitUser_withDo_primaryUser() throws Exception {
- setDeviceOwner();
- setup_nonSplitUser_afterDeviceSetup_primaryUser();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
// COMP mode is allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
- // And other DPCs can also provisioning a managed profile (DO + BYOD case).
+ // And other DPCs can also provision a managed profile (DO + BYOD case).
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- "some.other.dpc.package.name",
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+ }
+ public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedByDo() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
+ mContext.packageName = admin1.getPackageName();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+ // The DO should be allowed to initiate provisioning if it set the restriction itself, but
+ // other packages should be forbidden.
when(mContext.userManager.hasUserRestriction(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(true);
-
- // The DO should be allowed to initiate provisioning if it set the restriction itself, but
- // other packages should be forbidden.
when(mContext.userManager.getUserRestrictionSource(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- "some.other.dpc.package.name",
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+ }
+ public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedBySystem()
+ throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
+ mContext.packageName = admin1.getPackageName();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
// The DO should not be allowed to initiate provisioning if the restriction is set by
// another entity.
+ when(mContext.userManager.hasUserRestriction(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(true);
when(mContext.userManager.getUserRestrictionSource(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
- assertCheckProvisioningPreCondition(
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
+
+ assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- "some.other.dpc.package.name",
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+ }
+
+ public void testCheckProvisioningPreCondition_nonSplitUser_comp() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
+ mContext.packageName = admin1.getPackageName();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+
+ // We can delete the managed profile to create a new one, so provisioning is allowed.
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ assertCheckProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
+ DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+ }
+
+ public void testCheckProvisioningPreCondition_nonSplitUser_comp_cannot_remove_profile()
+ throws Exception {
+ setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
+ mContext.packageName = admin1.getPackageName();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+ when(mContext.userManager.hasUserRestriction(
+ eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(true);
+ when(mContext.userManager.getUserRestrictionSource(
+ eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+ // We can't remove the profile to create a new one.
+ assertCheckProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+
+ // But the device owner can still do it because it has set the restriction itself.
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
}
private void setup_splitUser_firstBoot_systemUser() throws Exception {
@@ -3422,6 +3533,21 @@
dpm.isProvisioningAllowed(action));
}
+ private void assertProvisioningAllowed(String action, boolean expected, String packageName,
+ int uid) {
+ String previousPackageName = mContext.packageName;
+ int previousUid = mMockContext.binder.callingUid;
+
+ // Call assertProvisioningAllowed with the packageName / uid passed as arguments.
+ mContext.packageName = packageName;
+ mMockContext.binder.callingUid = uid;
+ assertProvisioningAllowed(action, expected);
+
+ // Set the previous package name / calling uid to go back to the initial state.
+ mContext.packageName = previousPackageName;
+ mMockContext.binder.callingUid = previousUid;
+ }
+
private void assertCheckProvisioningPreCondition(String action, int provisioningCondition) {
assertCheckProvisioningPreCondition(action, admin1.getPackageName(), provisioningCondition);
}
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 2a82a52..22cd135 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -100,6 +100,10 @@
*/
public static final int SYSTEM_PID = 11111;
+ public static final String ANOTHER_PACKAGE_NAME = "com.another.package.name";
+
+ public static final int ANOTHER_UID = UserHandle.getUid(UserHandle.USER_SYSTEM, 18434);
+
public static class MockBinder {
public int callingUid = CALLER_UID;
public int callingPid = CALLER_PID;
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 8a11976..ed6779c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -45,6 +45,7 @@
public ComponentName admin1;
public ComponentName admin2;
public ComponentName admin3;
+ public ComponentName adminAnotherPackage;
public ComponentName adminNoPerm;
@Override
@@ -59,6 +60,8 @@
admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
+ adminAnotherPackage = new ComponentName(DpmMockContext.ANOTHER_PACKAGE_NAME,
+ "whatever.random.class");
adminNoPerm = new ComponentName(mRealTestContext, DummyDeviceAdmins.AdminNoPerm.class);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 167b33a..9835c88 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -566,6 +566,7 @@
protected Map<String, PackageInfo> mInjectedPackages;
protected Set<PackageWithUser> mUninstalledPackages;
+ protected Set<PackageWithUser> mEphemeralPackages;
protected Set<String> mSystemPackages;
protected PackageManager mMockPackageManager;
@@ -731,6 +732,7 @@
mUninstalledPackages = new HashSet<>();
mSystemPackages = new HashSet<>();
+ mEphemeralPackages = new HashSet<>();
mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
@@ -1034,6 +1036,9 @@
if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
}
+ if (mEphemeralPackages.contains(PackageWithUser.of(userId, packageName))) {
+ ret.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_EPHEMERAL;
+ }
if (mSystemPackages.contains(packageName)) {
ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerPresubmitTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerPresubmitTest.java
index 5c552a2..e6b4540f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerPresubmitTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerPresubmitTest.java
@@ -20,7 +20,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
-import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.GlobalPresubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -63,7 +63,7 @@
*/
@Test
@SmallTest
- @Presubmit
+ @GlobalPresubmit
public void testPrivAppPermissions() throws PackageManager.NameNotFoundException {
List<PackageInfo> installedPackages = mPackageManager
.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES | GET_PERMISSIONS);
@@ -103,12 +103,10 @@
// if privapp permissions are enforced, platform permissions must be whitelisted
// in SystemConfig
if (platformPermission && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
- assertTrue("Permission " + pName
- + " should be declared in "
- + "/etc/permissions/privapp-permissions-platform.xml "
- + "or privapp-permissions-<device>.xml file for package "
+ assertTrue("Permission " + pName + " should be declared in "
+ + "privapp-permissions-<category>.xml file for package "
+ packageName,
- privAppPermissions.contains(pName));
+ privAppPermissions != null && privAppPermissions.contains(pName));
}
assertTrue("Permission " + pName + " should be granted to " + packageName, granted);
}
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 d25923c..562de414 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -46,6 +46,7 @@
import com.android.frameworks.servicestests.R;
import com.android.server.pm.ShortcutService.ConfigConstants;
+import com.android.server.pm.ShortcutUser.PackageWithUser;
import java.io.File;
import java.io.FileWriter;
@@ -2037,4 +2038,32 @@
assertFalse(mService.isUserUnlockedL(USER_0));
assertFalse(mService.isUserUnlockedL(USER_10));
}
+
+ public void testEphemeralApp() {
+ mRunningUsers.put(USER_10, true); // this test needs user 10.
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ // Make package 1 ephemeral.
+ mEphemeralPackages.add(PackageWithUser.of(USER_0, CALLING_PACKAGE_1));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertExpectException(IllegalStateException.class, "Ephemeral apps", () -> {
+ mManager.getDynamicShortcuts();
+ });
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
index 2aca702..13a3c2f 100644
--- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
@@ -155,7 +155,7 @@
}
@Test
- public void testDuplicatePackageNameIsMergedAcrossMultipleUsers() throws Exception {
+ public void testDuplicatePackageNameIsNotMergedAcrossMultipleUsers() throws Exception {
PackageStats app = new PackageStats("com.test.app");
app.dataSize = 1000;
app.externalDataSize = 1000;
@@ -175,19 +175,19 @@
logger.dumpToFile(mOutputFile);
JSONObject output = getOutputFileAsJson();
- assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(2200);
- assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(22);
+ assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(2000);
+ assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(20);
JSONArray packageNames = output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
assertThat(packageNames.length()).isEqualTo(1);
assertThat(packageNames.getString(0)).isEqualTo("com.test.app");
JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
assertThat(appSizes.length()).isEqualTo(1);
- assertThat(appSizes.getLong(0)).isEqualTo(2200);
+ assertThat(appSizes.getLong(0)).isEqualTo(2000);
JSONArray cacheSizes = output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
assertThat(cacheSizes.length()).isEqualTo(1);
- assertThat(cacheSizes.getLong(0)).isEqualTo(22);
+ assertThat(cacheSizes.getLong(0)).isEqualTo(20);
}
private JSONObject getOutputFileAsJson() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 83a61ca..67e78d4 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -19,25 +19,39 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.webkit.UserPackage;
import android.webkit.WebViewProviderInfo;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
public class TestSystemImpl implements SystemInterface {
private String mUserProvider = null;
private final WebViewProviderInfo[] mPackageConfigs;
- HashMap<String, PackageInfo> mPackages = new HashMap();
+ List<Integer> mUsers = new ArrayList<>();
+ // Package -> [user, package]
+ Map<String, Map<Integer, PackageInfo>> mPackages = new HashMap();
private boolean mFallbackLogicEnabled;
private final int mNumRelros;
private final boolean mIsDebuggable;
private int mMultiProcessSetting;
+ public static final int PRIMARY_USER_ID = 0;
+
public TestSystemImpl(WebViewProviderInfo[] packageConfigs, boolean fallbackLogicEnabled,
int numRelros, boolean isDebuggable) {
mPackageConfigs = packageConfigs;
mFallbackLogicEnabled = fallbackLogicEnabled;
mNumRelros = numRelros;
mIsDebuggable = isDebuggable;
+ mUsers.add(PRIMARY_USER_ID);
+ }
+
+ public void addUser(int userId) {
+ mUsers.add(userId);
}
@Override
@@ -78,17 +92,20 @@
@Override
public void enablePackageForAllUsers(Context context, String packageName, boolean enable) {
- enablePackageForUser(packageName, enable, 0);
+ for(int userId : mUsers) {
+ enablePackageForUser(packageName, enable, userId);
+ }
}
@Override
public void enablePackageForUser(String packageName, boolean enable, int userId) {
- PackageInfo packageInfo = mPackages.get(packageName);
- if (packageInfo == null) {
+ Map<Integer, PackageInfo> userPackages = mPackages.get(packageName);
+ if (userPackages == null) {
throw new IllegalArgumentException("There is no package called " + packageName);
}
+ PackageInfo packageInfo = userPackages.get(userId);
packageInfo.applicationInfo.enabled = enable;
- setPackageInfo(packageInfo);
+ setPackageInfoForUser(userId, packageInfo);
}
@Override
@@ -97,23 +114,61 @@
@Override
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo info) throws
NameNotFoundException {
- PackageInfo ret = mPackages.get(info.packageName);
+ Map<Integer, PackageInfo> userPackages = mPackages.get(info.packageName);
+ if (userPackages == null) throw new NameNotFoundException(info.packageName);
+ PackageInfo ret = userPackages.get(PRIMARY_USER_ID);
if (ret == null) throw new NameNotFoundException(info.packageName);
return ret;
}
- public void setPackageInfo(PackageInfo pi) {
- mPackages.put(pi.packageName, pi);
+ @Override
+ public List<UserPackage> getPackageInfoForProviderAllUsers(
+ Context context, WebViewProviderInfo info) {
+ Map<Integer, PackageInfo> userPackages = mPackages.get(info.packageName);
+ List<UserPackage> ret = new ArrayList();
+ // Loop over defined users, and find the corresponding package for each user.
+ for (int userId : mUsers) {
+ ret.add(new UserPackage(createUserInfo(userId),
+ userPackages == null ? null : userPackages.get(userId)));
+ }
+ return ret;
}
+ private static UserInfo createUserInfo(int userId) {
+ return new UserInfo(userId, "User nr. " + userId, 0 /* flags */);
+ }
+
+ /**
+ * Set package for primary user.
+ */
+ public void setPackageInfo(PackageInfo pi) {
+ setPackageInfoForUser(PRIMARY_USER_ID, pi);
+ }
+
+ public void setPackageInfoForUser(int userId, PackageInfo pi) {
+ if (!mUsers.contains(userId)) {
+ throw new IllegalArgumentException("User nr. " + userId + " doesn't exist");
+ }
+ if (!mPackages.containsKey(pi.packageName)) {
+ mPackages.put(pi.packageName, new HashMap<Integer, PackageInfo>());
+ }
+ mPackages.get(pi.packageName).put(userId, pi);
+ }
+
+ /**
+ * Removes the package {@param packageName} for the primary user.
+ */
public void removePackageInfo(String packageName) {
- mPackages.remove(packageName);
+ mPackages.get(packageName).remove(PRIMARY_USER_ID);
}
@Override
public int getFactoryPackageVersion(String packageName) throws NameNotFoundException {
PackageInfo pi = null;
- pi = mPackages.get(packageName);
+ Map<Integer, PackageInfo> userPackages = mPackages.get(packageName);
+ if (userPackages == null) throw new NameNotFoundException();
+
+ pi = userPackages.get(PRIMARY_USER_ID);
if (pi != null && pi.applicationInfo.isSystemApp()) {
return pi.applicationInfo.versionCode;
}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 0519448..33cedfa 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -92,9 +92,15 @@
}
private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) {
+ // Set package infos for the primary user (user 0).
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, providers);
+ }
+
+ private void setEnabledAndValidPackageInfosForUser(int userId,
+ WebViewProviderInfo[] providers) {
for(WebViewProviderInfo wpi : providers) {
- mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, true /* enabled */,
- true /* valid */, true /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(userId, createPackageInfo(wpi.packageName,
+ true /* enabled */, true /* valid */, true /* installed */));
}
}
@@ -335,7 +341,7 @@
setEnabledAndValidPackageInfos(packages);
mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
- WebViewUpdateService.PACKAGE_ADDED, 0);
+ WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(singlePackage, 1 /* number of finished preparations */);
assertEquals(singlePackage,
@@ -344,7 +350,7 @@
// Remove the package again
mTestSystemImpl.removePackageInfo(singlePackage);
mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
- WebViewUpdateService.PACKAGE_ADDED, 0);
+ WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
// Package removed - ensure our interface states that there is no package
response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
@@ -374,7 +380,7 @@
createPackageInfo(wpi.packageName, true /* enabled */, true /* valid */,
true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(wpi.packageName,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(wpi.packageName, 1);
}
@@ -429,16 +435,8 @@
new WebViewProviderInfo(firstPackage, "", true, false, null),
new WebViewProviderInfo(secondPackage, "", true, false, null)};
setupWithPackages(packages);
- if (settingsChange) {
- // Have all packages be enabled, so that we can change provider however we want to
- setEnabledAndValidPackageInfos(packages);
- } else {
- // Have all packages be disabled so that we can change one to enabled later
- for(WebViewProviderInfo wpi : packages) {
- mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName,
- false /* enabled */, true /* valid */, true /* installed */));
- }
- }
+ // Have all packages be enabled, so that we can change provider however we want to
+ setEnabledAndValidPackageInfos(packages);
CountDownLatch countdown = new CountDownLatch(1);
@@ -457,8 +455,12 @@
mWebViewUpdateServiceImpl.waitForAndGetProvider();
assertEquals(WebViewFactory.LIBLOAD_SUCCESS, threadResponse.status);
assertEquals(secondPackage, threadResponse.packageInfo.packageName);
- // Verify that we killed the first package
- Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage));
+ // Verify that we killed the first package if we performed a settings change -
+ // otherwise we had to disable the first package, in which case its dependents
+ // should have been killed by the framework.
+ if (settingsChange) {
+ Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage));
+ }
countdown.countDown();
}
}).start();
@@ -470,11 +472,21 @@
if (settingsChange) {
mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage);
} else {
- // Switch provider by enabling the second one
+ // Enable the second provider
mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(
- secondPackage, WebViewUpdateService.PACKAGE_CHANGED, 0);
+ secondPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
+
+ // Ensure we haven't changed package yet.
+ assertEquals(firstPackage,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
+
+ // Switch provider by disabling the first one
+ mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, false /* enabled */,
+ true /* valid */, true /* installed */));
+ mWebViewUpdateServiceImpl.packageStateChanged(
+ firstPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
}
mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
// first package done, should start on second
@@ -528,7 +540,7 @@
mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */,
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(
- fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED, 0);
+ fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
if (fallbackLogicEnabled) {
// Check that we have now disabled the fallback package twice
@@ -573,7 +585,7 @@
createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */,
true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
// Verify fallback disabled, primary package used as provider, and fallback package killed
Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers(
@@ -583,7 +595,31 @@
}
@Test
- public void testFallbackChangesEnabledState() {
+ public void testFallbackChangesEnabledStateSingleUser() {
+ for (PackageRemovalType removalType : REMOVAL_TYPES) {
+ checkFallbackChangesEnabledState(false /* multiUser */, removalType);
+ }
+ }
+
+ @Test
+ public void testFallbackChangesEnabledStateMultiUser() {
+ for (PackageRemovalType removalType : REMOVAL_TYPES) {
+ checkFallbackChangesEnabledState(true /* multiUser */, removalType);
+ }
+ }
+
+ /**
+ * Represents how to remove a package during a tests (disabling it / uninstalling it / hiding
+ * it).
+ */
+ private enum PackageRemovalType {
+ UNINSTALL, DISABLE, HIDE
+ }
+
+ private PackageRemovalType[] REMOVAL_TYPES = PackageRemovalType.class.getEnumConstants();
+
+ public void checkFallbackChangesEnabledState(boolean multiUser,
+ PackageRemovalType removalType) {
String primaryPackage = "primary";
String fallbackPackage = "fallback";
WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
@@ -592,46 +628,68 @@
new WebViewProviderInfo(
fallbackPackage, "", true /* default available */, true /* fallback */, null)};
setupWithPackages(packages, true /* fallbackLogicEnabled */);
- setEnabledAndValidPackageInfos(packages);
+ int secondaryUserId = 10;
+ int userIdToChangePackageFor = multiUser ? secondaryUserId : TestSystemImpl.PRIMARY_USER_ID;
+ if (multiUser) {
+ mTestSystemImpl.addUser(secondaryUserId);
+ setEnabledAndValidPackageInfosForUser(secondaryUserId, packages);
+ }
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
runWebViewBootPreparationOnMainSync();
// Verify fallback disabled at boot when primary package enabled
- Mockito.verify(mTestSystemImpl).enablePackageForUser(
- Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */,
- Matchers.anyInt());
+ checkEnablePackageForUserCalled(fallbackPackage, false, multiUser
+ ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId}
+ : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 1 /* numUsages */);
checkPreparationPhasesForPackage(primaryPackage, 1);
+ boolean enabled = !(removalType == PackageRemovalType.DISABLE);
+ boolean installed = !(removalType == PackageRemovalType.UNINSTALL);
+ boolean hidden = (removalType == PackageRemovalType.HIDE);
// Disable primary package and ensure fallback becomes enabled and used
- mTestSystemImpl.setPackageInfo(
- createPackageInfo(primaryPackage, false /* enabled */, true /* valid */,
- true /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor,
+ createPackageInfo(primaryPackage, enabled /* enabled */, true /* valid */,
+ installed /* installed */, null /* signature */, 0 /* updateTime */,
+ hidden /* hidden */));
mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
- WebViewUpdateService.PACKAGE_CHANGED, 0);
+ removalType == PackageRemovalType.DISABLE
+ ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_REMOVED,
+ userIdToChangePackageFor); // USER ID
- Mockito.verify(mTestSystemImpl).enablePackageForUser(
- Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */,
- Matchers.anyInt());
+ checkEnablePackageForUserCalled(fallbackPackage, true, multiUser
+ ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId}
+ : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 1 /* numUsages */);
checkPreparationPhasesForPackage(fallbackPackage, 1);
// Again enable primary package and verify primary is used and fallback becomes disabled
- mTestSystemImpl.setPackageInfo(
+ mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor,
createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
- WebViewUpdateService.PACKAGE_CHANGED, 0);
+ removalType == PackageRemovalType.DISABLE
+ ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_ADDED,
+ userIdToChangePackageFor);
// Verify fallback is disabled a second time when primary package becomes enabled
- Mockito.verify(mTestSystemImpl, Mockito.times(2)).enablePackageForUser(
- Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */,
- Matchers.anyInt());
+ checkEnablePackageForUserCalled(fallbackPackage, false, multiUser
+ ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId}
+ : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 2 /* numUsages */);
checkPreparationPhasesForPackage(primaryPackage, 2);
}
+ private void checkEnablePackageForUserCalled(String packageName, boolean expectEnabled,
+ int[] userIds, int numUsages) {
+ for (int userId : userIds) {
+ Mockito.verify(mTestSystemImpl, Mockito.times(numUsages)).enablePackageForUser(
+ Mockito.eq(packageName), Mockito.eq(expectEnabled), Mockito.eq(userId));
+ }
+ }
+
@Test
public void testAddUserWhenFallbackLogicEnabled() {
checkAddingNewUser(true);
@@ -651,8 +709,10 @@
new WebViewProviderInfo(
fallbackPackage, "", true /* default available */, true /* fallback */, null)};
setupWithPackages(packages, fallbackLogicEnabled);
- setEnabledAndValidPackageInfos(packages);
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
int newUser = 100;
+ mTestSystemImpl.addUser(newUser);
+ setEnabledAndValidPackageInfosForUser(newUser, packages);
mWebViewUpdateServiceImpl.handleNewUser(newUser);
if (fallbackLogicEnabled) {
// Verify fallback package becomes disabled for new user
@@ -668,6 +728,42 @@
}
/**
+ * Ensures that adding a new user for which the current WebView package is uninstalled causes a
+ * change of WebView provider.
+ */
+ @Test
+ public void testAddingNewUserWithUninstalledPackage() {
+ String primaryPackage = "primary";
+ String fallbackPackage = "fallback";
+ WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
+ new WebViewProviderInfo(
+ primaryPackage, "", true /* default available */, false /* fallback */, null),
+ new WebViewProviderInfo(
+ fallbackPackage, "", true /* default available */, true /* fallback */, null)};
+ setupWithPackages(packages, true /* fallbackLogicEnabled */);
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
+ int newUser = 100;
+ mTestSystemImpl.addUser(newUser);
+ // Let the primary package be uninstalled for the new user
+ mTestSystemImpl.setPackageInfoForUser(newUser,
+ createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
+ false /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(newUser,
+ createPackageInfo(fallbackPackage, false /* enabled */, true /* valid */,
+ true /* installed */));
+ mWebViewUpdateServiceImpl.handleNewUser(newUser);
+ // Verify fallback package doesn't become disabled for the primary user.
+ Mockito.verify(mTestSystemImpl, Mockito.never()).enablePackageForUser(
+ Mockito.anyObject(), Mockito.eq(false) /* enable */,
+ Mockito.eq(TestSystemImpl.PRIMARY_USER_ID) /* user */);
+ // Verify that we enable the fallback package for the secondary user.
+ Mockito.verify(mTestSystemImpl, Mockito.times(1)).enablePackageForUser(
+ Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */,
+ Mockito.eq(newUser) /* user */);
+ checkPreparationPhasesForPackage(fallbackPackage, 1 /* numRelros */);
+ }
+
+ /**
* Timing dependent test where we verify that the list of valid webview packages becoming empty
* at a certain point doesn't crash us or break our state.
*/
@@ -713,7 +809,7 @@
1 /* updateTime */ ));
mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
// Ensure we use firstPackage
checkPreparationPhasesForPackage(firstPackage, 2 /* second preparation for this package */);
@@ -742,14 +838,14 @@
mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
false /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
- WebViewUpdateService.PACKAGE_ADDED, 0);
+ WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(firstPackage, 2 /* second time for this package */);
// Now make the second package valid again and verify that it is used again
mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
- WebViewUpdateService.PACKAGE_ADDED, 0);
+ WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(secondPackage, 2 /* second time for this package */);
}
@@ -820,7 +916,7 @@
mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage);
} else {
mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
}
WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
@@ -831,7 +927,7 @@
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(secondPackage, 1);
@@ -863,11 +959,11 @@
createPackageInfo(firstPackage, true /* enabled */, false /* valid */,
true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
} else {
mTestSystemImpl.removePackageInfo(firstPackage);
mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
- WebViewUpdateService.PACKAGE_REMOVED, 0);
+ WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID);
}
checkPreparationPhasesForPackage(secondPackage, 1);
@@ -1098,8 +1194,10 @@
}
}
- // Ensure that the update service uses an uninstalled package if that is the only package
- // available.
+ /**
+ * Ensure that the update service does use an uninstalled package when that is the only
+ * package available.
+ */
@Test
public void testWithSingleUninstalledPackage() {
String testPackageName = "test.package.name";
@@ -1113,21 +1211,32 @@
runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(testPackageName, 1 /* first preparation phase */);
+ // TODO(gsennton) change this logic to use the code below when we have created a functional
+ // stub.
+ //Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
+ // Matchers.anyObject());
+ //WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
+ //assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
+ //assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
}
@Test
public void testNonhiddenPackageUserOverHidden() {
- checkVisiblePackageUserOverNonVisible(false /* true == uninstalled, false == hidden */);
+ checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.HIDE);
+ checkVisiblePackageUserOverNonVisible(true /* multiUser*/, PackageRemovalType.HIDE);
}
@Test
public void testInstalledPackageUsedOverUninstalled() {
- checkVisiblePackageUserOverNonVisible(true /* true == uninstalled, false == hidden */);
+ checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.UNINSTALL);
+ checkVisiblePackageUserOverNonVisible(true /* multiUser*/, PackageRemovalType.UNINSTALL);
}
- private void checkVisiblePackageUserOverNonVisible(boolean uninstalledNotHidden) {
- boolean testUninstalled = uninstalledNotHidden;
- boolean testHidden = !uninstalledNotHidden;
+ private void checkVisiblePackageUserOverNonVisible(boolean multiUser,
+ PackageRemovalType removalType) {
+ assert removalType != PackageRemovalType.DISABLE;
+ boolean testUninstalled = removalType == PackageRemovalType.UNINSTALL;
+ boolean testHidden = removalType == PackageRemovalType.HIDE;
String installedPackage = "installedPackage";
String uninstalledPackage = "uninstalledPackage";
WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
@@ -1137,11 +1246,25 @@
false /* fallback */, null)};
setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */);
- mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
- true /* valid */, true /* installed */));
- mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
+ int secondaryUserId = 5;
+ if (multiUser) {
+ mTestSystemImpl.addUser(secondaryUserId);
+ // Install all packages for the primary user.
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages);
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(
+ installedPackage, true /* enabled */, true /* valid */, true /* installed */));
+ // Hide or uninstall the primary package for the second user
+ mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
true /* valid */, (testUninstalled ? false : true) /* installed */,
null /* signatures */, 0 /* updateTime */, (testHidden ? true : false)));
+ } else {
+ mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
+ true /* valid */, true /* installed */));
+ // Hide or uninstall the primary package
+ mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
+ true /* valid */, (testUninstalled ? false : true) /* installed */,
+ null /* signatures */, 0 /* updateTime */, (testHidden ? true : false)));
+ }
runWebViewBootPreparationOnMainSync();
@@ -1160,9 +1283,7 @@
}
/**
- * Ensure that we won't prioritize an uninstalled (or hidden) package even if it is user-chosen,
- * and that an uninstalled (or hidden) package is not considered valid (in the
- * getValidWebViewPackages() API).
+ * Ensure that we won't prioritize an uninstalled (or hidden) package even if it is user-chosen.
*/
private void checkCantSwitchToNonVisiblePackage(boolean uninstalledNotHidden) {
boolean testUninstalled = uninstalledNotHidden;
@@ -1176,27 +1297,31 @@
false /* fallback */, null)};
setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */);
- mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
- true /* valid */, true /* installed */));
- mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
- true /* valid */, (testUninstalled ? false : true) /* installed */,
- null /* signatures */, 0 /* updateTime */,
- (testHidden ? true : false) /* hidden */));
+ int secondaryUserId = 412;
+ mTestSystemImpl.addUser(secondaryUserId);
+
+ // Let all packages be installed and enabled for the primary user.
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages);
+ // Only uninstall the 'uninstalled package' for the secondary user.
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(installedPackage,
+ true /* enabled */, true /* valid */, true /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(uninstalledPackage,
+ true /* enabled */, true /* valid */, !testUninstalled /* installed */,
+ null /* signatures */, 0 /* updateTime */, testHidden /* hidden */));
runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
- // Ensure that only the installed package is considered valid
- WebViewProviderInfo[] validPackages = mWebViewUpdateServiceImpl.getValidWebViewPackages();
- assertEquals(1, validPackages.length);
- assertEquals(installedPackage, validPackages[0].packageName);
-
// ensure that we don't switch to the uninstalled package (it will be used if it becomes
// installed later)
assertEquals(installedPackage,
mWebViewUpdateServiceImpl.changeProviderAndSetting(uninstalledPackage));
+ // Ensure both packages are considered valid.
+ assertEquals(2, mWebViewUpdateServiceImpl.getValidWebViewPackages().length);
+
+
// We should only have called onWebViewProviderChanged once (before calling
// changeProviderAndSetting
Mockito.verify(mTestSystemImpl, Mockito.times(1)).onWebViewProviderChanged(
@@ -1227,12 +1352,16 @@
false /* fallback */, null)};
setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */);
- mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
- true /* valid */, true /* installed */));
- mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
- true /* valid */, (testUninstalled ? false : true) /* installed */,
- null /* signatures */, 0 /* updateTime */,
- (testHidden ? true : false) /* hidden */));
+ int secondaryUserId = 4;
+ mTestSystemImpl.addUser(secondaryUserId);
+
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages);
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(installedPackage,
+ true /* enabled */, true /* valid */, true /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(uninstalledPackage,
+ true /* enabled */, true /* valid */,
+ (testUninstalled ? false : true) /* installed */, null /* signatures */,
+ 0 /* updateTime */, (testHidden ? true : false) /* hidden */));
// Start with the setting pointing to the uninstalled package
mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
@@ -1242,12 +1371,21 @@
checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
}
+ @Test
+ public void testFallbackEnabledIfPrimaryUninstalledSingleUser() {
+ checkFallbackEnabledIfPrimaryUninstalled(false /* multiUser */);
+ }
+
+ @Test
+ public void testFallbackEnabledIfPrimaryUninstalledMultiUser() {
+ checkFallbackEnabledIfPrimaryUninstalled(true /* multiUser */);
+ }
+
/**
- * Ensures that fallback becomes enabled if the primary package is uninstalled for the current
+ * Ensures that fallback becomes enabled at boot if the primary package is uninstalled for some
* user.
*/
- @Test
- public void testFallbackEnabledIfPrimaryUninstalled() {
+ private void checkFallbackEnabledIfPrimaryUninstalled(boolean multiUser) {
String primaryPackage = "primary";
String fallbackPackage = "fallback";
WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
@@ -1256,10 +1394,24 @@
new WebViewProviderInfo(
fallbackPackage, "", true /* default available */, true /* fallback */, null)};
setupWithPackages(packages, true /* fallback logic enabled */);
- mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+ int secondaryUserId = 5;
+ if (multiUser) {
+ mTestSystemImpl.addUser(secondaryUserId);
+ // Install all packages for the primary user.
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
+ // Only install fallback package for secondary user.
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId,
+ createPackageInfo(primaryPackage, true /* enabled */,
+ true /* valid */, false /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId,
+ createPackageInfo(fallbackPackage, false /* enabled */,
+ true /* valid */, true /* installed */));
+ } else {
+ mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
true /* valid */, false /* installed */));
- mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */,
+ mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, false /* enabled */,
true /* valid */, true /* installed */));
+ }
runWebViewBootPreparationOnMainSync();
// Verify that we enable the fallback package
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 26accc3..2af4163 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -18,12 +18,10 @@
import org.junit.Test;
-import android.os.Binder;
-import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.view.IApplicationToken;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -74,6 +72,67 @@
controller.removeContainer(sDisplayContent.getDisplayId());
// Assert orientation is unspecified to after container is removed.
assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation());
+
+ // Reset display frozen state
+ sWm.mDisplayFrozen = false;
+ }
+
+ private void assertHasStartingWindow(AppWindowToken atoken) {
+ assertNotNull(atoken.startingSurface);
+ assertNotNull(atoken.startingData);
+ assertNotNull(atoken.startingWindow);
+ }
+
+ private void assertNoStartingWindow(AppWindowToken atoken) {
+ assertNull(atoken.startingSurface);
+ assertNull(atoken.startingWindow);
+ assertNull(atoken.startingData);
+ }
+
+ @Test
+ public void testCreateRemoveStartingWindow() throws Exception {
+ final TestAppWindowContainerController controller = createAppWindowController();
+ controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false);
+ waitUntilHandlerIdle();
+ final AppWindowToken atoken = controller.getAppWindowToken();
+ assertHasStartingWindow(atoken);
+ controller.removeStartingWindow();
+ waitUntilHandlerIdle();
+ assertNoStartingWindow(atoken);
+ }
+
+ @Test
+ public void testTransferStartingWindow() throws Exception {
+ final TestAppWindowContainerController controller1 = createAppWindowController();
+ final TestAppWindowContainerController controller2 = createAppWindowController();
+ controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false);
+ waitUntilHandlerIdle();
+ controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
+ true, true, false);
+ waitUntilHandlerIdle();
+ assertNoStartingWindow(controller1.getAppWindowToken());
+ assertHasStartingWindow(controller2.getAppWindowToken());
+ }
+
+ @Test
+ public void testTransferStartingWindowWhileCreating() throws Exception {
+ final TestAppWindowContainerController controller1 = createAppWindowController();
+ final TestAppWindowContainerController controller2 = createAppWindowController();
+ sPolicy.setRunnableWhenAddingSplashScreen(() -> {
+
+ // Surprise, ...! Transfer window in the middle of the creation flow.
+ controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
+ true, true, false);
+ });
+ controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false);
+ waitUntilHandlerIdle();
+ assertNoStartingWindow(controller1.getAppWindowToken());
+ assertHasStartingWindow(controller2.getAppWindowToken());
}
private TestAppWindowContainerController createAppWindowController() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 1d9875f..154fa91 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -119,6 +119,7 @@
sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, sWm.mLastOrientation);
assertTrue(appWindow.resizeReported);
+ appWindow.removeImmediately();
}
@Test
@@ -148,5 +149,6 @@
sWm.updateRotation(false, false);
sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
assertTrue(appWindow.resizeReported);
+ appWindow.removeImmediately();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 85931e8..e54e319 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -236,7 +236,7 @@
assertEquals(dc, token.getDisplayContent());
// Move stack to first display.
- sWm.moveStackToDisplay(stack.mStackId, sDisplayContent.getDisplayId());
+ sDisplayContent.moveStackToDisplay(stack);
assertEquals(sDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId());
assertEquals(sDisplayContent, stack.getParent().getParent());
assertEquals(sDisplayContent, stack.getDisplayContent());
diff --git a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
new file mode 100644
index 0000000..7a789d4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
@@ -0,0 +1,123 @@
+/*
+ * 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.wm;
+
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import android.graphics.Rect;
+import android.hardware.display.DisplayManagerGlobal;
+import android.view.Display;
+import android.view.DisplayInfo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class for {@link StackWindowController}.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.wm.StackWindowControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class StackWindowControllerTests extends WindowTestsBase {
+ @Test
+ public void testRemoveContainer() throws Exception {
+ final StackWindowController stackController =
+ createStackControllerOnDisplay(sDisplayContent);
+ final TestTaskWindowContainerController taskController =
+ new TestTaskWindowContainerController(stackController);
+
+ final TaskStack stack = stackController.mContainer;
+ final Task task = taskController.mContainer;
+ assertNotNull(stack);
+ assertNotNull(task);
+ stackController.removeContainer();
+ // Assert that the container was removed.
+ assertNull(stackController.mContainer);
+ assertNull(taskController.mContainer);
+ assertNull(stack.getDisplayContent());
+ assertNull(task.getDisplayContent());
+ assertNull(task.mStack);
+ }
+
+ @Test
+ public void testRemoveContainer_deferRemoval() throws Exception {
+ final StackWindowController stackController =
+ createStackControllerOnDisplay(sDisplayContent);
+ final TestTaskWindowContainerController taskController =
+ new TestTaskWindowContainerController(stackController);
+
+ final TaskStack stack = stackController.mContainer;
+ final TestTask task = (TestTask) taskController.mContainer;
+ // Stack removal is deferred if one of its child is animating.
+ task.setLocalIsAnimating(true);
+
+ stackController.removeContainer();
+ // For the case of deferred removal the stack controller will no longer be connected to the
+ // container, but the task controller will still be connected to the its container until
+ // the stack window container is removed.
+ assertNull(stackController.mContainer);
+ assertNull(stack.getController());
+ assertNotNull(taskController.mContainer);
+ assertNotNull(task.getController());
+
+ stack.removeImmediately();
+ assertNull(taskController.mContainer);
+ assertNull(task.getController());
+ }
+
+ @Test
+ public void testReparent() throws Exception {
+ // Create first stack on primary display.
+ final StackWindowController stack1Controller =
+ createStackControllerOnDisplay(sDisplayContent);
+ final TaskStack stack1 = stack1Controller.mContainer;
+ final TestTaskWindowContainerController taskController =
+ new TestTaskWindowContainerController(stack1Controller);
+ final TestTask task1 = (TestTask) taskController.mContainer;
+ task1.mOnDisplayChangedCalled = false;
+
+ // Create second display and put second stack on it.
+ final Display display = new Display(DisplayManagerGlobal.getInstance(),
+ sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
+ final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
+ new WallpaperController(sWm));
+ sWm.mRoot.addChild(dc, 1);
+
+ final StackWindowController stack2Controller =
+ createStackControllerOnDisplay(dc);
+ final TaskStack stack2 = stack2Controller.mContainer;
+
+ // Reparent
+ stack1Controller.reparent(dc.getDisplayId(), new Rect());
+ assertEquals(dc, stack1.getDisplayContent());
+ final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
+ final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
+ assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
+ assertTrue(task1.mOnDisplayChangedCalled);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
new file mode 100644
index 0000000..aab75ee
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskSnapshotSurface}.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskSnapshotSurfaceTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotSurfaceTest extends WindowTestsBase {
+
+ private TaskSnapshotSurface mSurface;
+
+ @Before
+ public void setUp() {
+ mSurface = new TaskSnapshotSurface(null, null, null, Color.WHITE);
+ }
+
+ @Test
+ public void fillEmptyBackground_fillHorizontally() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(200);
+ when(mockCanvas.getHeight()).thenReturn(100);
+ final Bitmap b = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_fillVertically() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(100);
+ when(mockCanvas.getHeight()).thenReturn(200);
+ final Bitmap b = Bitmap.createBitmap(200, 100, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_fillBoth() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(200);
+ when(mockCanvas.getHeight()).thenReturn(200);
+ final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+ verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_dontFill_sameSize() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(100);
+ when(mockCanvas.getHeight()).thenReturn(100);
+ final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_dontFill_bitmapLarger() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(100);
+ when(mockCanvas.getHeight()).thenReturn(100);
+ final Bitmap b = Bitmap.createBitmap(200, 200, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
index bb9bc9e..462bd68 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -17,17 +17,16 @@
package com.android.server.wm;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.After;
-import android.hardware.display.DisplayManagerGlobal;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.view.Display;
-import android.view.DisplayInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -46,95 +45,69 @@
@RunWith(AndroidJUnit4.class)
public class TaskStackContainersTests extends WindowTestsBase {
+ private TaskStack mPinnedStack;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mPinnedStack = new StackWindowController(PINNED_STACK_ID, null,
+ sDisplayContent.getDisplayId(), true /* onTop */, new Rect(), sWm).mContainer;
+
+ // Stack should contain visible app window to be considered visible.
+ final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
+ assertFalse(mPinnedStack.isVisible());
+ final TestAppWindowToken pinnedApp = new TestAppWindowToken(sDisplayContent);
+ pinnedTask.addChild(pinnedApp, 0 /* addPos */);
+ assertTrue(mPinnedStack.isVisible());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mPinnedStack.removeImmediately();
+ }
+
@Test
public void testStackPositionChildAt() throws Exception {
// Test that always-on-top stack can't be moved to position other than top.
final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent);
- final TaskStack pinnedStack = addPinnedStack();
final WindowContainer taskStackContainer = stack1.getParent();
final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
- final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack);
+ final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
assertGreaterThan(pinnedStackPos, stack2Pos);
assertGreaterThan(stack2Pos, stack1Pos);
- taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, pinnedStack, false);
+ taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false);
assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
- taskStackContainer.positionChildAt(1, pinnedStack, false);
+ taskStackContainer.positionChildAt(1, mPinnedStack, false);
assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
}
+
@Test
public void testStackPositionBelowPinnedStack() throws Exception {
// Test that no stack can be above pinned stack.
- final TaskStack pinnedStack = addPinnedStack();
final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
final WindowContainer taskStackContainer = stack1.getParent();
final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
- final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack);
+ final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
assertGreaterThan(pinnedStackPos, stackPos);
taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
- }
-
- @Test
- public void testReparentBetweenDisplays() throws Exception {
- // Create first stack on primary display.
- final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
- final TestTaskWindowContainerController taskController =
- new TestTaskWindowContainerController(stack1.mStackId);
- final TestTask task1 = (TestTask) taskController.mContainer;
- task1.mOnDisplayChangedCalled = false;
-
- // Create second display and put second stack on it.
- final Display display = new Display(DisplayManagerGlobal.getInstance(),
- sDisplayContent.getDisplayId() + 1, new DisplayInfo(),
- DEFAULT_DISPLAY_ADJUSTMENTS);
- final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
- new WallpaperController(sWm));
- sWm.mRoot.addChild(dc, 1);
- final TaskStack stack2 = createTaskStackOnDisplay(dc);
-
- // Reparent and check state.DisplayContent.java:2572
- sWm.moveStackToDisplay(stack1.mStackId, dc.getDisplayId());
- assertEquals(dc, stack1.getDisplayContent());
- final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
- final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
- assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
- assertTrue(task1.mOnDisplayChangedCalled);
- }
-
- private TaskStack addPinnedStack() {
- TaskStack pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
- if (pinnedStack == null) {
- sDisplayContent.addStackToDisplay(PINNED_STACK_ID, true);
- pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
- }
-
- if (!pinnedStack.isVisible()) {
- // Stack should contain visible app window to be considered visible.
- final Task pinnedTask = createTaskInStack(pinnedStack, 0 /* userId */);
- assertFalse(pinnedStack.isVisible());
- final TestAppWindowToken pinnedApp = new TestAppWindowToken(sDisplayContent);
- pinnedTask.addChild(pinnedApp, 0 /* addPos */);
- assertTrue(pinnedStack.isVisible());
- }
-
- return pinnedStack;
+ assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
index 7cd3f64..3ee1da43 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
@@ -18,14 +18,14 @@
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-import org.junit.Test;
-
import android.hardware.display.DisplayManagerGlobal;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.Display;
import android.view.DisplayInfo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -40,7 +40,7 @@
*/
@SmallTest
@Presubmit
-@org.junit.runner.RunWith(AndroidJUnit4.class)
+@RunWith(AndroidJUnit4.class)
public class TaskWindowContainerControllerTests extends WindowTestsBase {
@Test
@@ -57,7 +57,7 @@
}
@Test
- public void testRemoveContainer_DeferRemoval() throws Exception {
+ public void testRemoveContainer_deferRemoval() throws Exception {
final TestTaskWindowContainerController taskController =
new TestTaskWindowContainerController();
final TestAppWindowContainerController appController =
@@ -83,58 +83,66 @@
@Test
public void testReparent() throws Exception {
- final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
+ final StackWindowController stackController1 =
+ createStackControllerOnDisplay(sDisplayContent);
final TestTaskWindowContainerController taskController =
- new TestTaskWindowContainerController(stack1.mStackId);
- final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent);
+ new TestTaskWindowContainerController(stackController1);
+ final StackWindowController stackController2 =
+ createStackControllerOnDisplay(sDisplayContent);
final TestTaskWindowContainerController taskController2 =
- new TestTaskWindowContainerController(stack2.mStackId);
+ new TestTaskWindowContainerController(stackController2);
boolean gotException = false;
try {
- taskController.reparent(stack1.mStackId, 0);
+ taskController.reparent(stackController1, 0);
} catch (IllegalArgumentException e) {
gotException = true;
}
assertTrue("Should not be able to reparent to the same parent", gotException);
+ final StackWindowController stackController3 =
+ createStackControllerOnDisplay(sDisplayContent);
+ stackController3.setContainer(null);
gotException = false;
try {
- taskController.reparent(sNextStackId + 1, 0);
+ taskController.reparent(stackController3, 0);
} catch (IllegalArgumentException e) {
gotException = true;
}
- assertTrue("Should not be able to reparent to a stackId that doesn't exist", gotException);
+ assertTrue("Should not be able to reparent to a stack that doesn't have a container",
+ gotException);
- taskController.reparent(stack2.mStackId, 0);
- assertEquals(stack2, taskController.mContainer.getParent());
+ taskController.reparent(stackController2, 0);
+ assertEquals(stackController2.mContainer, taskController.mContainer.getParent());
assertEquals(0, ((TestTask) taskController.mContainer).positionInParent());
assertEquals(1, ((TestTask) taskController2.mContainer).positionInParent());
}
@Test
- public void testReparentBetweenDisplays() throws Exception {
+ public void testReparent_BetweenDisplays() throws Exception {
// Create first stack on primary display.
- final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
+ final StackWindowController stack1Controller =
+ createStackControllerOnDisplay(sDisplayContent);
+ final TaskStack stack1 = stack1Controller.mContainer;
final TestTaskWindowContainerController taskController =
- new TestTaskWindowContainerController(stack1.mStackId);
+ new TestTaskWindowContainerController(stack1Controller);
final TestTask task1 = (TestTask) taskController.mContainer;
task1.mOnDisplayChangedCalled = false;
// Create second display and put second stack on it.
final Display display = new Display(DisplayManagerGlobal.getInstance(),
- sDisplayContent.getDisplayId() + 1, new DisplayInfo(),
- DEFAULT_DISPLAY_ADJUSTMENTS);
+ sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
new WallpaperController(sWm));
sWm.mRoot.addChild(dc, 1);
- final TaskStack stack2 = createTaskStackOnDisplay(dc);
+ final StackWindowController stack2Controller = createStackControllerOnDisplay(dc);
+ final TaskStack stack2 = stack2Controller.mContainer;
final TestTaskWindowContainerController taskController2 =
- new TestTaskWindowContainerController(stack2.mStackId);
+ new TestTaskWindowContainerController(stack2Controller);
final TestTask task2 = (TestTask) taskController2.mContainer;
// Reparent and check state
- taskController.reparent(stack2.mStackId, 0);
+ taskController.reparent(stack2Controller, 0);
assertEquals(stack2, task1.getParent());
assertEquals(0, task1.positionInParent());
assertEquals(1, task2.positionInParent());
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 269b719..ec429a0 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -24,6 +24,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
@@ -74,6 +75,7 @@
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
import android.os.PowerManagerInternal;
@@ -92,6 +94,8 @@
int rotationToReport = 0;
+ private Runnable mRunnableWhenAddingSplashScreen;
+
static synchronized WindowManagerService getWindowManagerService(Context context) {
if (sWm == null) {
// We only want to do this once for the test process as we don't want WM to try to
@@ -318,11 +322,36 @@
return false;
}
+ /**
+ * Sets a runnable to run when adding a splash screen which gets executed after the window has
+ * been added but before returning the surface.
+ */
+ void setRunnableWhenAddingSplashScreen(Runnable r) {
+ mRunnableWhenAddingSplashScreen = r;
+ }
+
@Override
public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
int logo, int windowFlags, Configuration overrideConfig, int displayId) {
- return null;
+ final com.android.server.wm.WindowState window;
+ final AppWindowToken atoken;
+ synchronized (sWm.mWindowMap) {
+ atoken = WindowTestsBase.sDisplayContent.getAppWindowToken(appToken);
+ window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken,
+ "Starting window");
+ atoken.startingWindow = window;
+ }
+ if (mRunnableWhenAddingSplashScreen != null) {
+ mRunnableWhenAddingSplashScreen.run();
+ mRunnableWhenAddingSplashScreen = null;
+ }
+ return () -> {
+ synchronized (sWm.mWindowMap) {
+ atoken.removeChild(window);
+ atoken.startingWindow = null;
+ }
+ };
}
@Override
@@ -482,7 +511,7 @@
@Override
public boolean isScreenOn() {
- return false;
+ return true;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 6129198..772bfb4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -45,20 +45,18 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class UnknownAppVisibilityControllerTest {
+public class UnknownAppVisibilityControllerTest extends WindowTestsBase {
private WindowManagerService mWm;
- private @Mock ActivityManagerInternal mAm;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ super.setUp();
final Context context = InstrumentationRegistry.getTargetContext();
- LocalServices.addService(ActivityManagerInternal.class, mAm);
doAnswer((InvocationOnMock invocationOnMock) -> {
invocationOnMock.getArgumentAt(0, Runnable.class).run();
return null;
- }).when(mAm).notifyKeyguardFlagsChanged(any());
+ }).when(sMockAm).notifyKeyguardFlagsChanged(any());
mWm = TestWindowManagerPolicy.getWindowManagerService(context);
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 466da94..186884b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -20,6 +20,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.graphics.Rect;
import android.os.Binder;
@@ -76,7 +77,8 @@
final Rect mInsetBounds = new Rect();
boolean mFullscreenForTest = true;
TaskWithBounds(Rect bounds) {
- super(0, mStubStack, 0, sWm, null, null, false, 0, false, null);
+ super(0, mStubStack, 0, sWm, null, null, false, 0, false, false, new TaskDescription(),
+ null);
mBounds = bounds;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 813d263..72157b6 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -16,14 +16,18 @@
package com.android.server.wm;
+import android.app.ActivityManager.TaskDescription;
+import android.app.ActivityManagerInternal;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.view.IApplicationToken;
import org.junit.Assert;
import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
-import android.app.ActivityManager;
import android.app.ActivityManager.TaskSnapshot;
import android.content.Context;
import android.os.IBinder;
@@ -50,13 +54,17 @@
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.mockito.Mockito.mock;
+import com.android.server.AttributeCache;
+import com.android.server.LocalServices;
+
/**
* Common base class for window manager unit test classes.
*/
class WindowTestsBase {
static WindowManagerService sWm = null;
- private final IWindow mIWindow = new TestIWindow();
- private final Session mMockSession = mock(Session.class);
+ static TestWindowManagerPolicy sPolicy = null;
+ private final static IWindow sIWindow = new TestIWindow();
+ private final static Session sMockSession = mock(Session.class);
static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
private static int sNextTaskId = 0;
@@ -72,19 +80,31 @@
static WindowState sAppWindow;
static WindowState sChildAppWindowAbove;
static WindowState sChildAppWindowBelow;
+ static @Mock ActivityManagerInternal sMockAm;
@Before
public void setUp() throws Exception {
if (sOneTimeSetupDone) {
+ Mockito.reset(sMockAm);
return;
}
sOneTimeSetupDone = true;
+ MockitoAnnotations.initMocks(this);
final Context context = InstrumentationRegistry.getTargetContext();
+ LocalServices.addService(ActivityManagerInternal.class, sMockAm);
+ AttributeCache.init(context);
sWm = TestWindowManagerPolicy.getWindowManagerService(context);
+ sPolicy = (TestWindowManagerPolicy) sWm.mPolicy;
sLayersController = new WindowLayersController(sWm);
+ sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId());
+ if (sDisplayContent != null) {
+ sDisplayContent.removeImmediately();
+ }
sDisplayContent = new DisplayContent(context.getDisplay(), sWm, sLayersController,
new WallpaperController(sWm));
sWm.mRoot.addChild(sDisplayContent, 0);
+ sWm.mDisplayEnabled = true;
+ sWm.mDisplayReady = true;
// Set-up some common windows.
sWallpaperWindow = createWindow(null, TYPE_WALLPAPER, sDisplayContent, "wallpaperWindow");
@@ -108,7 +128,14 @@
Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second);
}
- private WindowToken createWindowToken(DisplayContent dc, int type) {
+ /**
+ * Waits until the main handler for WM has processed all messages.
+ */
+ void waitUntilHandlerIdle() {
+ sWm.mH.runWithScissors(() -> { }, 0);
+ }
+
+ private static WindowToken createWindowToken(DisplayContent dc, int type) {
if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
return new TestWindowToken(type, dc);
}
@@ -120,7 +147,7 @@
return token;
}
- WindowState createWindow(WindowState parent, int type, String name) {
+ static WindowState createWindow(WindowState parent, int type, String name) {
return (parent == null)
? createWindow(parent, type, sDisplayContent, name)
: createWindow(parent, type, parent.mToken, name);
@@ -132,16 +159,16 @@
return createWindow(null, type, token, name);
}
- WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
+ static WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
final WindowToken token = createWindowToken(dc, type);
return createWindow(parent, type, token, name);
}
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
+ static WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
attrs.setTitle(name);
- final WindowState w = new WindowState(sWm, mMockSession, mIWindow, token, parent, OP_NONE,
+ final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE,
0, attrs, 0, 0);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
// adding it to the token...
@@ -150,22 +177,26 @@
}
/** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */
- TaskStack createTaskStackOnDisplay(DisplayContent dc) {
- final int stackId = sNextStackId++;
- dc.addStackToDisplay(stackId, true);
- return sWm.mStackIdToStack.get(stackId);
+ static TaskStack createTaskStackOnDisplay(DisplayContent dc) {
+ return createStackControllerOnDisplay(dc).mContainer;
+ }
+
+ static StackWindowController createStackControllerOnDisplay(DisplayContent dc) {
+ final int stackId = ++sNextStackId;
+ return new StackWindowController(stackId, null, dc.getDisplayId(),
+ true /* onTop */, new Rect(), sWm);
}
/**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
- Task createTaskInStack(TaskStack stack, int userId) {
+ static Task createTaskInStack(TaskStack stack, int userId) {
final Task newTask = new Task(sNextTaskId++, stack, userId, sWm, null, EMPTY, false, 0,
- false, null);
+ false, false, new TaskDescription(), null);
stack.addTask(newTask, POSITION_TOP);
return newTask;
}
/* Used so we can gain access to some protected members of the {@link WindowToken} class */
- class TestWindowToken extends WindowToken {
+ static class TestWindowToken extends WindowToken {
TestWindowToken(int type, DisplayContent dc) {
this(type, dc, false /* persistOnEmpty */);
@@ -185,7 +216,7 @@
}
/** Used so we can gain access to some protected members of the {@link AppWindowToken} class. */
- class TestAppWindowToken extends AppWindowToken {
+ static class TestAppWindowToken extends AppWindowToken {
TestAppWindowToken(DisplayContent dc) {
super(sWm, null, false, dc);
@@ -213,12 +244,16 @@
boolean mShouldDeferRemoval = false;
boolean mOnDisplayChangedCalled = false;
+ private boolean mUseLocalIsAnimating = false;
+ private boolean mIsAnimating = false;
TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode,
- boolean homeTask, TaskWindowContainerController controller) {
+ boolean supportsPictureInPicture, boolean homeTask,
+ TaskWindowContainerController controller) {
super(taskId, stack, userId, service, bounds, overrideConfig, isOnTopLauncher,
- resizeMode, homeTask, controller);
+ resizeMode, supportsPictureInPicture, homeTask, new TaskDescription(),
+ controller);
}
boolean shouldDeferRemoval() {
@@ -234,6 +269,16 @@
super.onDisplayChanged(dc);
mOnDisplayChangedCalled = true;
}
+
+ @Override
+ boolean isAnimating() {
+ return mUseLocalIsAnimating ? mIsAnimating : super.isAnimating();
+ }
+
+ void setLocalIsAnimating(boolean isAnimating) {
+ mUseLocalIsAnimating = true;
+ mIsAnimating = isAnimating;
+ }
}
/**
@@ -243,21 +288,33 @@
class TestTaskWindowContainerController extends TaskWindowContainerController {
TestTaskWindowContainerController() {
- this(createTaskStackOnDisplay(sDisplayContent).mStackId);
+ this(createStackControllerOnDisplay(sDisplayContent));
}
- TestTaskWindowContainerController(int stackId) {
- super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */,
- EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, false /* homeTask*/,
- false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */);
+ TestTaskWindowContainerController(StackWindowController stackController) {
+ super(sNextTaskId++, new TaskWindowContainerListener() {
+ @Override
+ public void onSnapshotChanged(TaskSnapshot snapshot) {
+
+ }
+
+ @Override
+ public void requestResize(Rect bounds, int resizeMode) {
+
+ }
+ }, stackController, 0 /* userId */, null /* bounds */,
+ EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE,
+ false /* supportsPictureInPicture */, false /* homeTask*/,
+ false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */,
+ new TaskDescription(), sWm);
}
@Override
TestTask createTask(int taskId, TaskStack stack, int userId, Rect bounds,
- Configuration overrideConfig, int resizeMode, boolean homeTask,
- boolean isOnTopLauncher) {
+ Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+ boolean homeTask, boolean isOnTopLauncher, TaskDescription taskDescription) {
return new TestTask(taskId, stack, userId, mService, bounds, overrideConfig,
- isOnTopLauncher, resizeMode, homeTask, this);
+ isOnTopLauncher, resizeMode, supportsPictureInPicture, homeTask, this);
}
}
@@ -279,6 +336,10 @@
0 /* inputDispatchingTimeoutNanos */, sWm);
mToken = token;
}
+
+ AppWindowToken getAppWindowToken() {
+ return (AppWindowToken) sDisplayContent.getWindowToken(mToken.asBinder());
+ }
}
class TestIApplicationToken implements IApplicationToken {
@@ -295,7 +356,7 @@
boolean resizeReported;
TestWindowState(WindowManager.LayoutParams attrs, WindowToken token) {
- super(sWm, mMockSession, mIWindow, token, null, OP_NONE, 0, attrs, 0, 0);
+ super(sWm, sMockSession, sIWindow, token, null, OP_NONE, 0, attrs, 0, 0);
}
@Override
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 68765b6..6826975 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -37,6 +37,7 @@
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
@@ -46,8 +47,6 @@
private static final String PROP_VERIFY_STORAGE = "fw.verify_storage";
- // TODO: pivot all methods to manual mode when quota isn't supported
-
public static class Lifecycle extends SystemService {
private StorageStatsService mService;
@@ -71,11 +70,11 @@
private final Installer mInstaller;
public StorageStatsService(Context context) {
- mContext = context;
- mAppOps = context.getSystemService(AppOpsManager.class);
- mUser = context.getSystemService(UserManager.class);
- mPackage = context.getSystemService(PackageManager.class);
- mStorage = context.getSystemService(StorageManager.class);
+ mContext = Preconditions.checkNotNull(context);
+ mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class));
+ mUser = Preconditions.checkNotNull(context.getSystemService(UserManager.class));
+ mPackage = Preconditions.checkNotNull(context.getPackageManager());
+ mStorage = Preconditions.checkNotNull(context.getSystemService(StorageManager.class));
mInstaller = new Installer(context);
mInstaller.onStart();
@@ -107,7 +106,7 @@
case AppOpsManager.MODE_ALLOWED:
return;
case AppOpsManager.MODE_DEFAULT:
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.PACKAGE_USAGE_STATS, TAG);
return;
default:
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 6dfb48b..7a69803 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -106,7 +106,7 @@
private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
- private static final boolean ENABLE_KERNEL_UPDATES = false;
+ private static final boolean ENABLE_KERNEL_UPDATES = true;
private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
long mAppIdleScreenThresholdMillis;
diff --git a/services/usb/Android.mk b/services/usb/Android.mk
index feabf0a..f560e71 100644
--- a/services/usb/Android.mk
+++ b/services/usb/Android.mk
@@ -8,5 +8,7 @@
$(call all-java-files-under,java)
LOCAL_JAVA_LIBRARIES := services.core
+LOCAL_STATIC_JAVA_LIBRARIES := android.hardware.usb@1.0-java-static \
+android.hidl.manager@1.0-java-static
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index db7a31a..fbbe636 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -656,6 +656,7 @@
// send a sticky broadcast containing current USB state
Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(UsbManager.USB_CONNECTED, mConnected);
intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected);
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 4aff3d54..4b8e4c8 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -16,36 +16,40 @@
package com.android.server.usb;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.FgThread;
-
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.V1_0.IUsb;
+import android.hardware.usb.V1_0.IUsbCallback;
+import android.hardware.usb.V1_0.PortRole;
+import android.hardware.usb.V1_0.PortRoleType;
+import android.hardware.usb.V1_0.PortStatus;
+import android.hardware.usb.V1_0.Status;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.HwBinder;
import android.os.Message;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UEventObserver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
import android.os.UserHandle;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.FgThread;
-import libcore.io.IoUtils;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
/**
* Allows trusted components to control the properties of physical USB ports
- * via the "/sys/class/dual_role_usb" kernel interface.
+ * via the IUsb.hal.
* <p>
* Note: This interface may not be supported on all chipsets since the USB drivers
* must be changed to publish this information through the module. At the moment
@@ -60,43 +64,6 @@
private static final int MSG_UPDATE_PORTS = 1;
- // UEvent path to watch.
- private static final String UEVENT_FILTER = "SUBSYSTEM=dual_role_usb";
-
- // SysFS directory that contains USB ports as subdirectories.
- private static final String SYSFS_CLASS = "/sys/class/dual_role_usb";
-
- // SysFS file that contains a USB port's supported modes. (read-only)
- // Contents: "", "ufp", "dfp", or "ufp dfp".
- private static final String SYSFS_PORT_SUPPORTED_MODES = "supported_modes";
-
- // SysFS file that contains a USB port's current mode. (read-write if configurable)
- // Contents: "", "ufp", or "dfp".
- private static final String SYSFS_PORT_MODE = "mode";
-
- // SysFS file that contains a USB port's current power role. (read-write if configurable)
- // Contents: "", "source", or "sink".
- private static final String SYSFS_PORT_POWER_ROLE = "power_role";
-
- // SysFS file that contains a USB port's current data role. (read-write if configurable)
- // Contents: "", "host", or "device".
- private static final String SYSFS_PORT_DATA_ROLE = "data_role";
-
- // Port modes: upstream facing port or downstream facing port.
- private static final String PORT_MODE_DFP = "dfp";
- private static final String PORT_MODE_UFP = "ufp";
-
- // Port power roles: source or sink.
- private static final String PORT_POWER_ROLE_SOURCE = "source";
- private static final String PORT_POWER_ROLE_SINK = "sink";
-
- // Port data roles: host or device.
- private static final String PORT_DATA_ROLE_HOST = "host";
- private static final String PORT_DATA_ROLE_DEVICE = "device";
-
- private static final String USB_TYPEC_PROP_PREFIX = "sys.usb.typec.";
- private static final String USB_TYPEC_STATE = "sys.usb.typec.state";
-
// All non-trivial role combinations.
private static final int COMBO_SOURCE_HOST =
UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST);
@@ -110,8 +77,29 @@
// The system context.
private final Context mContext;
- // True if we have kernel support.
- private final boolean mHaveKernelSupport;
+ // Proxy object for the usb hal daemon.
+ // @GuardedBy("mLock")
+ private IUsb mProxy = null;
+
+ // Callback when the UsbPort status is changed by the kernel.
+ // Mostly due a command sent by the remote Usb device.
+ private HALCallback mHALCallback = new HALCallback(null, this);
+
+ // Notification object used to listen to the start of the usb daemon.
+ private final ServiceNotification mServiceNotification = new ServiceNotification();
+
+ // Cookie sent for usb hal death notification.
+ private static final int USB_HAL_DEATH_COOKIE = 1000;
+
+ // Usb hal service name.
+ private static String sSERVICENAME = "usb_hal";
+
+ // Used as the key while sending the bundle to Main thread.
+ private static final String PORT_INFO = "port_info";
+
+ // This is monitored to prevent updating the protInfo before the system
+ // is ready.
+ private boolean mSystemReady;
// Mutex for all mutable shared state.
private final Object mLock = new Object();
@@ -123,17 +111,37 @@
private final ArrayMap<String, PortInfo> mPorts = new ArrayMap<String, PortInfo>();
// List of all simulated ports, indexed by id.
- private final ArrayMap<String, SimulatedPortInfo> mSimulatedPorts =
- new ArrayMap<String, SimulatedPortInfo>();
+ private final ArrayMap<String, RawPortInfo> mSimulatedPorts =
+ new ArrayMap<String, RawPortInfo>();
public UsbPortManager(Context context) {
mContext = context;
- mHaveKernelSupport = new File(SYSFS_CLASS).exists();
+ try {
+ boolean ret = IServiceManager.getService("manager")
+ .registerForNotifications("android.hardware.usb@1.0::IUsb",
+ "", mServiceNotification);
+ if (!ret) {
+ logAndPrint(Log.ERROR, null, "Failed to register service start notification");
+ }
+ } catch (RemoteException e) {
+ logAndPrint(Log.ERROR, null, "Failed to register service start notification");
+ Thread.dumpStack();
+ return;
+ }
+ connectToProxy(null);
}
public void systemReady() {
- mUEventObserver.startObserving(UEVENT_FILTER);
- scheduleUpdatePorts();
+ if (mProxy != null) {
+ try {
+ mProxy.queryPortStatus();
+ } catch (RemoteException e) {
+ logAndPrint(Log.ERROR, null,
+ "ServiceStart: Failed to query port status");
+ Thread.dumpStack();
+ }
+ }
+ mSystemReady = true;
}
public UsbPort[] getPorts() {
@@ -223,20 +231,14 @@
+ ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
+ ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
- SimulatedPortInfo sim = mSimulatedPorts.get(portId);
+ RawPortInfo sim = mSimulatedPorts.get(portId);
if (sim != null) {
// Change simulated state.
sim.mCurrentMode = newMode;
sim.mCurrentPowerRole = newPowerRole;
sim.mCurrentDataRole = newDataRole;
- } else if (mHaveKernelSupport) {
- // Change actual state.
- final File portDir = new File(SYSFS_CLASS, portId);
- if (!portDir.exists()) {
- logAndPrint(Log.ERROR, pw, "USB port not found: portId=" + portId);
- return;
- }
-
+ updatePortsLocked(pw, null);
+ } else if (mProxy != null) {
if (currentMode != newMode) {
// Changing the mode will have the side-effect of also changing
// the power and data roles but it might take some time to apply
@@ -244,38 +246,54 @@
// hardware, we have no way of knowing whether it will work apriori
// which is why we would prefer to set the power and data roles
// directly instead.
- if (!writeFile(portDir, SYSFS_PORT_MODE,
- newMode == UsbPort.MODE_DFP ? PORT_MODE_DFP : PORT_MODE_UFP)) {
- logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: "
+
+ logAndPrint(Log.ERROR, pw, "Trying to set the USB port mode: "
+ "portId=" + portId
+ ", newMode=" + UsbPort.modeToString(newMode));
+ PortRole newRole = new PortRole();
+ newRole.type = PortRoleType.MODE;
+ newRole.role = newMode;
+ try {
+ mProxy.switchRole(portId, newRole);
+ } catch (RemoteException e) {
+ logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: "
+ + "portId=" + portId
+ + ", newMode=" + UsbPort.modeToString(newRole.role));
+ Thread.dumpStack();
return;
}
} else {
// Change power and data role independently as needed.
if (currentPowerRole != newPowerRole) {
- if (!writeFile(portDir, SYSFS_PORT_POWER_ROLE,
- newPowerRole == UsbPort.POWER_ROLE_SOURCE
- ? PORT_POWER_ROLE_SOURCE : PORT_POWER_ROLE_SINK)) {
+ PortRole newRole = new PortRole();
+ newRole.type = PortRoleType.POWER_ROLE;
+ newRole.role = newPowerRole;
+ try {
+ mProxy.switchRole(portId, newRole);
+ } catch (RemoteException e) {
logAndPrint(Log.ERROR, pw, "Failed to set the USB port power role: "
+ "portId=" + portId
- + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole));
+ + ", newPowerRole=" + UsbPort.powerRoleToString(newRole.role));
+ Thread.dumpStack();
return;
}
}
if (currentDataRole != newDataRole) {
- if (!writeFile(portDir, SYSFS_PORT_DATA_ROLE,
- newDataRole == UsbPort.DATA_ROLE_HOST
- ? PORT_DATA_ROLE_HOST : PORT_DATA_ROLE_DEVICE)) {
+ PortRole newRole = new PortRole();
+ newRole.type = PortRoleType.DATA_ROLE;
+ newRole.role = newDataRole;
+ try {
+ mProxy.switchRole(portId, newRole);
+ } catch (RemoteException e) {
logAndPrint(Log.ERROR, pw, "Failed to set the USB port data role: "
+ "portId=" + portId
- + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
+ + ", newDataRole=" + UsbPort.dataRoleToString(newRole.role));
+ Thread.dumpStack();
return;
}
}
}
}
- updatePortsLocked(pw);
}
}
@@ -289,8 +307,8 @@
pw.println("Adding simulated port: portId=" + portId
+ ", supportedModes=" + UsbPort.modeToString(supportedModes));
mSimulatedPorts.put(portId,
- new SimulatedPortInfo(portId, supportedModes));
- updatePortsLocked(pw);
+ new RawPortInfo(portId, supportedModes));
+ updatePortsLocked(pw, null);
}
}
@@ -298,7 +316,7 @@
int powerRole, boolean canChangePowerRole,
int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw) {
synchronized (mLock) {
- final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
+ final RawPortInfo portInfo = mSimulatedPorts.get(portId);
if (portInfo == null) {
pw.println("Cannot connect simulated port which does not exist.");
return;
@@ -328,13 +346,13 @@
portInfo.mCanChangePowerRole = canChangePowerRole;
portInfo.mCurrentDataRole = dataRole;
portInfo.mCanChangeDataRole = canChangeDataRole;
- updatePortsLocked(pw);
+ updatePortsLocked(pw, null);
}
}
public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
synchronized (mLock) {
- final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
+ final RawPortInfo portInfo = mSimulatedPorts.get(portId);
if (portInfo == null) {
pw.println("Cannot disconnect simulated port which does not exist.");
return;
@@ -347,7 +365,7 @@
portInfo.mCanChangePowerRole = false;
portInfo.mCurrentDataRole = 0;
portInfo.mCanChangeDataRole = false;
- updatePortsLocked(pw);
+ updatePortsLocked(pw, null);
}
}
@@ -361,7 +379,7 @@
pw.println("Disconnecting simulated port: portId=" + portId);
mSimulatedPorts.removeAt(index);
- updatePortsLocked(pw);
+ updatePortsLocked(pw, null);
}
}
@@ -370,7 +388,7 @@
pw.println("Removing all simulated ports and ending simulation.");
if (!mSimulatedPorts.isEmpty()) {
mSimulatedPorts.clear();
- updatePortsLocked(pw);
+ updatePortsLocked(pw, null);
}
}
}
@@ -393,9 +411,108 @@
}
}
- private void updatePortsLocked(IndentingPrintWriter pw) {
- // Assume all ports are gone unless informed otherwise.
- // Kind of pessimistic but simple.
+ private static class HALCallback extends IUsbCallback.Stub {
+ public IndentingPrintWriter pw;
+ public UsbPortManager portManager;
+
+ HALCallback() {
+ super();
+ }
+
+ HALCallback(IndentingPrintWriter pw, UsbPortManager portManager) {
+ this.pw = pw;
+ this.portManager = portManager;
+ }
+
+ public void notifyPortStatusChange(ArrayList<PortStatus> currentPortStatus, int retval) {
+ if (!portManager.mSystemReady) return;
+
+ if (retval != Status.SUCCESS) {
+ logAndPrint(Log.ERROR, pw, "port status enquiry failed");
+ return;
+ }
+
+ ArrayList<RawPortInfo> newPortInfo = new ArrayList<RawPortInfo>();
+
+ for (PortStatus current : currentPortStatus) {
+ RawPortInfo temp = new RawPortInfo(current.portName,
+ current.supportedModes, current.currentMode,
+ current.canChangeMode, current.currentPowerRole,
+ current.canChangePowerRole,
+ current.currentDataRole, current.canChangeDataRole);
+ newPortInfo.add(temp);
+ logAndPrint(Log.INFO, pw, "ClientCallback: " + current.portName);
+ }
+
+ Message message = portManager.mHandler.obtainMessage();
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
+ message.what = MSG_UPDATE_PORTS;
+ message.setData(bundle);
+ portManager.mHandler.sendMessage(message);
+ return;
+ }
+
+ public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) {
+ if (retval == Status.SUCCESS) {
+ logAndPrint(Log.INFO, pw, portName + "role switch successful");
+ } else {
+ logAndPrint(Log.ERROR, pw, portName + "role switch failed");
+ }
+ }
+ };
+
+ final class DeathRecipient implements HwBinder.DeathRecipient {
+ public IndentingPrintWriter pw;
+
+ DeathRecipient(IndentingPrintWriter pw) {
+ this.pw = pw;
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ if (cookie == USB_HAL_DEATH_COOKIE) {
+ logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie);
+ synchronized (mLock) {
+ mProxy = null;
+ }
+ }
+ }
+ }
+
+ final class ServiceNotification extends IServiceNotification.Stub {
+ @Override
+ public void onRegistration(String fqName, String name, boolean preexisting) {
+ logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name);
+ connectToProxy(null);
+ }
+ }
+
+ private void connectToProxy(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ if (mProxy != null) return;
+
+ try {
+ mProxy = IUsb.getService(sSERVICENAME);
+ mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
+ mProxy.setCallback(mHALCallback);
+ mProxy.queryPortStatus();
+ } catch (NoSuchElementException e) {
+ logAndPrint(Log.ERROR, pw, sSERVICENAME + "not found."
+ + " Did the service failed to start ?");
+ Thread.dumpStack();
+ } catch (RemoteException e) {
+ logAndPrint(Log.ERROR, pw, sSERVICENAME + "connectToProxy: Service not responding");
+ Thread.dumpStack();
+ }
+ }
+ }
+
+ /**
+ * Simulated ports directly add the new roles to mSimulatedPorts before calling.
+ * USB hal callback populates and sends the newPortInfo.
+ */
+ private void updatePortsLocked(IndentingPrintWriter pw, ArrayList<RawPortInfo> newPortInfo) {
for (int i = mPorts.size(); i-- > 0; ) {
mPorts.valueAt(i).mDisposition = PortInfo.DISPOSITION_REMOVED;
}
@@ -404,34 +521,18 @@
if (!mSimulatedPorts.isEmpty()) {
final int count = mSimulatedPorts.size();
for (int i = 0; i < count; i++) {
- final SimulatedPortInfo portInfo = mSimulatedPorts.valueAt(i);
+ final RawPortInfo portInfo = mSimulatedPorts.valueAt(i);
addOrUpdatePortLocked(portInfo.mPortId, portInfo.mSupportedModes,
portInfo.mCurrentMode, portInfo.mCanChangeMode,
portInfo.mCurrentPowerRole, portInfo.mCanChangePowerRole,
portInfo.mCurrentDataRole, portInfo.mCanChangeDataRole, pw);
}
- } else if (mHaveKernelSupport) {
- final File[] portDirs = new File(SYSFS_CLASS).listFiles();
- if (portDirs != null) {
- for (File portDir : portDirs) {
- if (!portDir.isDirectory()) {
- continue;
- }
-
- // Parse the sysfs file contents.
- final String portId = portDir.getName();
- final int supportedModes = readSupportedModes(portDir);
- final int currentMode = readCurrentMode(portDir);
- final boolean canChangeMode = canChangeMode(portDir);
- final int currentPowerRole = readCurrentPowerRole(portDir);
- final boolean canChangePowerRole = canChangePowerRole(portDir);
- final int currentDataRole = readCurrentDataRole(portDir);
- final boolean canChangeDataRole = canChangeDataRole(portDir);
- addOrUpdatePortLocked(portId, supportedModes,
- currentMode, canChangeMode,
- currentPowerRole, canChangePowerRole,
- currentDataRole, canChangeDataRole, pw);
- }
+ } else {
+ for (RawPortInfo currentPortInfo : newPortInfo) {
+ addOrUpdatePortLocked(currentPortInfo.mPortId, currentPortInfo.mSupportedModes,
+ currentPortInfo.mCurrentMode, currentPortInfo.mCanChangeMode,
+ currentPortInfo.mCurrentPowerRole, currentPortInfo.mCanChangePowerRole,
+ currentPortInfo.mCurrentDataRole, currentPortInfo.mCanChangeDataRole, pw);
}
}
@@ -457,20 +558,21 @@
}
}
+
// Must only be called by updatePortsLocked.
private void addOrUpdatePortLocked(String portId, int supportedModes,
- int currentMode, boolean canChangeMode,
- int currentPowerRole, boolean canChangePowerRole,
- int currentDataRole, boolean canChangeDataRole,
- IndentingPrintWriter pw) {
+ int currentMode, boolean canChangeMode,
+ int currentPowerRole, boolean canChangePowerRole,
+ int currentDataRole, boolean canChangeDataRole,
+ IndentingPrintWriter pw) {
// Only allow mode switch capability for dual role ports.
// Validate that the current mode matches the supported modes we expect.
if (supportedModes != UsbPort.MODE_DUAL) {
canChangeMode = false;
if (currentMode != 0 && currentMode != supportedModes) {
logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB "
- + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes)
- + ", currentMode=" + UsbPort.modeToString(currentMode));
+ + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes)
+ + ", currentMode=" + UsbPort.modeToString(currentMode));
currentMode = 0;
}
}
@@ -485,8 +587,8 @@
// Can change both power and data role independently.
// Assume all combinations are possible.
supportedRoleCombinations |=
- COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE
- | COMBO_SINK_HOST | COMBO_SINK_DEVICE;
+ COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE
+ | COMBO_SINK_HOST | COMBO_SINK_DEVICE;
} else if (canChangePowerRole) {
// Can only change power role.
// Assume data role must remain at its current value.
@@ -514,24 +616,24 @@
if (portInfo == null) {
portInfo = new PortInfo(portId, supportedModes);
portInfo.setStatus(currentMode, canChangeMode,
- currentPowerRole, canChangePowerRole,
- currentDataRole, canChangeDataRole,
- supportedRoleCombinations);
+ currentPowerRole, canChangePowerRole,
+ currentDataRole, canChangeDataRole,
+ supportedRoleCombinations);
mPorts.put(portId, portInfo);
} else {
// Sanity check that ports aren't changing definition out from under us.
if (supportedModes != portInfo.mUsbPort.getSupportedModes()) {
logAndPrint(Log.WARN, pw, "Ignoring inconsistent list of supported modes from "
- + "USB port driver (should be immutable): "
- + "previous=" + UsbPort.modeToString(
- portInfo.mUsbPort.getSupportedModes())
- + ", current=" + UsbPort.modeToString(supportedModes));
+ + "USB port driver (should be immutable): "
+ + "previous=" + UsbPort.modeToString(
+ portInfo.mUsbPort.getSupportedModes())
+ + ", current=" + UsbPort.modeToString(supportedModes));
}
if (portInfo.setStatus(currentMode, canChangeMode,
- currentPowerRole, canChangePowerRole,
- currentDataRole, canChangeDataRole,
- supportedRoleCombinations)) {
+ currentPowerRole, canChangePowerRole,
+ currentDataRole, canChangeDataRole,
+ supportedRoleCombinations)) {
portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
} else {
portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -572,120 +674,6 @@
});
}
- private void scheduleUpdatePorts() {
- if (!mHandler.hasMessages(MSG_UPDATE_PORTS)) {
- mHandler.sendEmptyMessage(MSG_UPDATE_PORTS);
- }
- }
-
- private static int readSupportedModes(File portDir) {
- int modes = 0;
- final String contents = readFile(portDir, SYSFS_PORT_SUPPORTED_MODES);
- if (contents != null) {
- if (contents.contains(PORT_MODE_DFP)) {
- modes |= UsbPort.MODE_DFP;
- }
- if (contents.contains(PORT_MODE_UFP)) {
- modes |= UsbPort.MODE_UFP;
- }
- }
- return modes;
- }
-
- private static int readCurrentMode(File portDir) {
- final String contents = readFile(portDir, SYSFS_PORT_MODE);
- if (contents != null) {
- if (contents.equals(PORT_MODE_DFP)) {
- return UsbPort.MODE_DFP;
- }
- if (contents.equals(PORT_MODE_UFP)) {
- return UsbPort.MODE_UFP;
- }
- }
- return 0;
- }
-
- private static int readCurrentPowerRole(File portDir) {
- final String contents = readFile(portDir, SYSFS_PORT_POWER_ROLE);
- if (contents != null) {
- if (contents.equals(PORT_POWER_ROLE_SOURCE)) {
- return UsbPort.POWER_ROLE_SOURCE;
- }
- if (contents.equals(PORT_POWER_ROLE_SINK)) {
- return UsbPort.POWER_ROLE_SINK;
- }
- }
- return 0;
- }
-
- private static int readCurrentDataRole(File portDir) {
- final String contents = readFile(portDir, SYSFS_PORT_DATA_ROLE);
- if (contents != null) {
- if (contents.equals(PORT_DATA_ROLE_HOST)) {
- return UsbPort.DATA_ROLE_HOST;
- }
- if (contents.equals(PORT_DATA_ROLE_DEVICE)) {
- return UsbPort.DATA_ROLE_DEVICE;
- }
- }
- return 0;
- }
-
- private static boolean fileIsRootWritable(String path) {
- try {
- // If the file is user writable, then it is root writable.
- return (Os.stat(path).st_mode & OsConstants.S_IWUSR) != 0;
- } catch (ErrnoException e) {
- return false;
- }
- }
-
- private static boolean canChangeMode(File portDir) {
- return fileIsRootWritable(new File(portDir, SYSFS_PORT_MODE).getPath());
- }
-
- private static boolean canChangePowerRole(File portDir) {
- return fileIsRootWritable(new File(portDir, SYSFS_PORT_POWER_ROLE).getPath());
- }
-
- private static boolean canChangeDataRole(File portDir) {
- return fileIsRootWritable(new File(portDir, SYSFS_PORT_DATA_ROLE).getPath());
- }
-
- private static String readFile(File dir, String filename) {
- final File file = new File(dir, filename);
- try {
- return IoUtils.readFileAsString(file.getAbsolutePath()).trim();
- } catch (IOException ex) {
- return null;
- }
- }
-
- private static boolean waitForState(String property, String state) {
- // wait for the transition to complete.
- // give up after 5 seconds.
- // 5 seconds is probably too long, but we have seen hardware that takes
- // over 3 seconds to change states.
- String value = null;
- for (int i = 0; i < 100; i++) {
- // State transition is done when property is set to the new configuration
- value = SystemProperties.get(property);
- if (state.equals(value)) return true;
- SystemClock.sleep(50);
- }
- Slog.e(TAG, "waitForState(" + state + ") for " + property + " FAILED: got " + value);
- return false;
- }
-
- private static String propertyFromFilename(String filename) {
- return USB_TYPEC_PROP_PREFIX + filename;
- }
-
- private static boolean writeFile(File dir, String filename, String contents) {
- SystemProperties.set(propertyFromFilename(filename), contents);
- return waitForState(USB_TYPEC_STATE, contents);
- }
-
private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
Slog.println(priority, TAG, msg);
if (pw != null) {
@@ -698,8 +686,10 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_PORTS: {
+ Bundle b = msg.getData();
+ ArrayList<RawPortInfo> PortInfo = b.getParcelableArrayList(PORT_INFO);
synchronized (mLock) {
- updatePortsLocked(null);
+ updatePortsLocked(null, PortInfo);
}
break;
}
@@ -707,13 +697,6 @@
}
};
- private final UEventObserver mUEventObserver = new UEventObserver() {
- @Override
- public void onUEvent(UEvent event) {
- scheduleUpdatePorts();
- }
- };
-
/**
* Describes a USB port.
*/
@@ -764,10 +747,10 @@
}
/**
- * Describes a simulated USB port.
- * Roughly mirrors the information we would ordinarily get from the kernel.
+ * Used for storing the raw data from the kernel
+ * Values of the member variables mocked directly incase of emulation.
*/
- private static final class SimulatedPortInfo {
+ private static final class RawPortInfo implements Parcelable {
public final String mPortId;
public final int mSupportedModes;
public int mCurrentMode;
@@ -777,9 +760,63 @@
public int mCurrentDataRole;
public boolean mCanChangeDataRole;
- public SimulatedPortInfo(String portId, int supportedModes) {
+ RawPortInfo(String portId, int supportedModes) {
mPortId = portId;
mSupportedModes = supportedModes;
}
+
+ RawPortInfo(String portId, int supportedModes,
+ int currentMode, boolean canChangeMode,
+ int currentPowerRole, boolean canChangePowerRole,
+ int currentDataRole, boolean canChangeDataRole) {
+ mPortId = portId;
+ mSupportedModes = supportedModes;
+ mCurrentMode = currentMode;
+ mCanChangeMode = canChangeMode;
+ mCurrentPowerRole = currentPowerRole;
+ mCanChangePowerRole = canChangePowerRole;
+ mCurrentDataRole = currentDataRole;
+ mCanChangeDataRole = canChangeDataRole;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPortId);
+ dest.writeInt(mSupportedModes);
+ dest.writeInt(mCurrentMode);
+ dest.writeByte((byte) (mCanChangeMode ? 1 : 0));
+ dest.writeInt(mCurrentPowerRole);
+ dest.writeByte((byte) (mCanChangePowerRole ? 1 : 0));
+ dest.writeInt(mCurrentDataRole);
+ dest.writeByte((byte) (mCanChangeDataRole ? 1 : 0));
+ }
+
+ public static final Parcelable.Creator<RawPortInfo> CREATOR =
+ new Parcelable.Creator<RawPortInfo>() {
+ @Override
+ public RawPortInfo createFromParcel(Parcel in) {
+ String id = in.readString();
+ int supportedModes = in.readInt();
+ int currentMode = in.readInt();
+ boolean canChangeMode = in.readByte() != 0;
+ int currentPowerRole = in.readInt();
+ boolean canChangePowerRole = in.readByte() != 0;
+ int currentDataRole = in.readInt();
+ boolean canChangeDataRole = in.readByte() != 0;
+ return new RawPortInfo(id, supportedModes, currentMode, canChangeMode,
+ currentPowerRole, canChangePowerRole,
+ currentDataRole, canChangeDataRole);
+ }
+
+ @Override
+ public RawPortInfo[] newArray(int size) {
+ return new RawPortInfo[size];
+ }
+ };
}
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 00e8f9f..ba7b6a1 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -58,12 +58,15 @@
* Input: get*Extra field {@link #EXTRA_PHONE_ACCOUNT_HANDLE} contains the component name of the
* {@link android.telecom.ConnectionService} that Telecom should bind to. Telecom will then
* ask the connection service for more information about the call prior to showing any UI.
+ *
+ * @deprecated Use {@link #addNewIncomingCall} instead.
*/
public static final String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
/**
* Similar to {@link #ACTION_INCOMING_CALL}, but is used only by Telephony to add a new
* sim-initiated MO call for carrier testing.
+ * @deprecated Use {@link #addNewUnknownCall} instead.
* @hide
*/
public static final String ACTION_NEW_UNKNOWN_CALL = "android.telecom.action.NEW_UNKNOWN_CALL";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c0a86d6..a853d5c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -598,6 +598,38 @@
"vvm_cellular_data_required_bool";
/**
+ * The default OMTP visual voicemail client prefix to use. Defaulted to "//VVM"
+ */
+ public static final String KEY_VVM_CLIENT_PREFIX_STRING =
+ "vvm_client_prefix_string";
+
+ /**
+ * Whether to use SSL to connect to the visual voicemail IMAP server. Defaulted to false.
+ */
+ public static final String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
+
+ /**
+ * A set of capabilities that should not be used even if it is reported by the visual voicemail
+ * IMAP CAPABILITY command.
+ */
+ public static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY =
+ "vvm_disabled_capabilities_string_array";
+
+ /**
+ * Whether legacy mode should be used when the visual voicemail client is disabled.
+ *
+ * <p>Legacy mode is a mode that on the carrier side visual voicemail is still activated, but on
+ * the client side all network operations are disabled. SMSs are still monitored so a new
+ * message SYNC SMS will be translated to show a message waiting indicator, like traditional
+ * voicemails.
+ *
+ * <p>This is for carriers that does not support VVM deactivation so voicemail can continue to
+ * function without the data cost.
+ */
+ public static final String KEY_VVM_LEGACY_MODE_ENABLED_BOOL =
+ "vvm_legacy_mode_enabled_bool";
+
+ /**
* Whether to prefetch audio data on new voicemail arrival, defaulted to true.
*/
public static final String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
@@ -605,10 +637,20 @@
/**
* The package name of the carrier's visual voicemail app to ensure that dialer visual voicemail
* and carrier visual voicemail are not active at the same time.
+ *
+ * @deprecated use {@link #KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY}.
*/
+ @Deprecated
public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
/**
+ * A list of the carrier's visual voicemail app package names to ensure that dialer visual
+ * voicemail and carrier visual voicemail are not active at the same time.
+ */
+ public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY =
+ "carrier_vvm_package_name_string_array";
+
+ /**
* Flag specifying whether ICCID is showed in SIM Status screen, default to false.
*/
public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
@@ -1308,8 +1350,13 @@
sDefaults.putInt(KEY_VVM_PORT_NUMBER_INT, 0);
sDefaults.putString(KEY_VVM_TYPE_STRING, "");
sDefaults.putBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL, false);
+ sDefaults.putString(KEY_VVM_CLIENT_PREFIX_STRING,"//VVM");
+ sDefaults.putBoolean(KEY_VVM_SSL_ENABLED_BOOL,false);
+ sDefaults.putStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_VVM_LEGACY_MODE_ENABLED_BOOL,false);
sDefaults.putBoolean(KEY_VVM_PREFETCH_BOOL, true);
sDefaults.putString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING, "");
+ sDefaults.putStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL, false);
sDefaults.putBoolean(KEY_CI_ACTION_ON_SYS_UPDATE_BOOL, false);
sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING, "");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a3f7c18..b28627b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4791,6 +4791,20 @@
}
}
+ /*
+ * @return true, if the device is currently on a technology (e.g. UMTS or LTE) which can support
+ * voice and data simultaneously. This can change based on location or network condition.
+ */
+ public boolean isConcurrentVoiceAndDataAllowed() {
+ try {
+ ITelephony telephony = getITelephony();
+ return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed(mSubId));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isConcurrentVoiceAndDataAllowed", e);
+ }
+ return false;
+ }
+
/** @hide */
@SystemApi
public boolean handlePinMmi(String dialString) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index c0d6768ae..9a9a092 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -466,6 +466,12 @@
*/
int getVoiceMessageCountForSubscriber(int subId);
+ /**
+ * Returns true if current state supports both voice and data
+ * simultaneously. This can change based on location or network condition.
+ */
+ boolean isConcurrentVoiceAndDataAllowed(int subId);
+
oneway void setVisualVoicemailEnabled(String callingPackage,
in PhoneAccountHandle accountHandle, boolean enabled);
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index da27ea9..56aad23 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -424,6 +424,17 @@
mNM.notify("secret", 7012, n);
}
},
+ new Test("1 minute timeout") {
+ public void run()
+ {
+ Notification n = new Notification.Builder(NotificationTestList.this)
+ .setSmallIcon(R.drawable.icon2)
+ .setContentTitle("timeout in a minute")
+ .setTimeout(System.currentTimeMillis() + (1000 * 60))
+ .build();
+ mNM.notify("timeout_min", 7013, n);
+ }
+ },
new Test("Off") {
public void run() {
PowerManager pm = (PowerManager)NotificationTestList.this.getSystemService(Context.POWER_SERVICE);
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
index d0026a2..0aca45e 100644
--- a/tools/aapt/AaptConfig.cpp
+++ b/tools/aapt/AaptConfig.cpp
@@ -267,8 +267,8 @@
uint16_t minSdk = 0;
if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
== ResTable_config::UI_MODE_TYPE_VR_HEADSET
- || config->colorimetry & ResTable_config::MASK_WIDE_COLOR_GAMUT
- || config->colorimetry & ResTable_config::MASK_HDR) {
+ || config->colorMode & ResTable_config::MASK_WIDE_COLOR_GAMUT
+ || config->colorMode & ResTable_config::MASK_HDR) {
minSdk = SDK_O;
} else if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
minSdk = SDK_MNC;
@@ -451,18 +451,18 @@
bool parseWideColorGamut(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
- if (out) out->colorimetry =
- (out->colorimetry&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
+ if (out) out->colorMode =
+ (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
| ResTable_config::WIDE_COLOR_GAMUT_ANY;
return true;
} else if (strcmp(name, "widecg") == 0) {
- if (out) out->colorimetry =
- (out->colorimetry&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
+ if (out) out->colorMode =
+ (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
| ResTable_config::WIDE_COLOR_GAMUT_YES;
return true;
} else if (strcmp(name, "nowidecg") == 0) {
- if (out) out->colorimetry =
- (out->colorimetry&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
+ if (out) out->colorMode =
+ (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
| ResTable_config::WIDE_COLOR_GAMUT_NO;
return true;
}
@@ -471,18 +471,18 @@
bool parseHdr(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
- if (out) out->colorimetry =
- (out->colorimetry&~ResTable_config::MASK_HDR)
+ if (out) out->colorMode =
+ (out->colorMode&~ResTable_config::MASK_HDR)
| ResTable_config::HDR_ANY;
return true;
} else if (strcmp(name, "highdr") == 0) {
- if (out) out->colorimetry =
- (out->colorimetry&~ResTable_config::MASK_HDR)
+ if (out) out->colorMode =
+ (out->colorMode&~ResTable_config::MASK_HDR)
| ResTable_config::HDR_YES;
return true;
} else if (strcmp(name, "lowdr") == 0) {
- if (out) out->colorimetry =
- (out->colorimetry&~ResTable_config::MASK_HDR)
+ if (out) out->colorMode =
+ (out->colorMode&~ResTable_config::MASK_HDR)
| ResTable_config::HDR_NO;
return true;
}
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index 653c1b4..a93ee2e 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -55,6 +55,7 @@
mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL),
mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL),
mAutoAddOverlay(false), mGenDependencies(false), mNoVersionVectors(false),
+ mNoVersionTransitions(false),
mCrunchedOutputDir(NULL), mProguardFile(NULL), mMainDexProguardFile(NULL),
mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL),
@@ -219,6 +220,8 @@
void setBuildAppAsSharedLibrary(bool val) { mBuildAppAsSharedLibrary = val; }
void setNoVersionVectors(bool val) { mNoVersionVectors = val; }
bool getNoVersionVectors() const { return mNoVersionVectors; }
+ void setNoVersionTransitions(bool val) { mNoVersionTransitions = val; }
+ bool getNoVersionTransitions() const { return mNoVersionTransitions; }
/*
* Set and get the file specification.
@@ -299,6 +302,7 @@
bool mAutoAddOverlay;
bool mGenDependencies;
bool mNoVersionVectors;
+ bool mNoVersionTransitions;
const char* mCrunchedOutputDir;
const char* mProguardFile;
const char* mMainDexProguardFile;
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 984d98e..417b7ae 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -223,6 +223,8 @@
" localization\n"
" --no-version-vectors\n"
" Do not automatically generate versioned copies of vector XML resources.\n"
+ " --no-version-transitions\n"
+ " Do not automatically generate versioned copies of transition XML resources.\n"
" --private-symbols\n"
" Java package name to use when generating R.java for private resources.\n",
gDefaultIgnoreAssets);
@@ -704,6 +706,8 @@
bundle.setPseudolocalize(PSEUDO_ACCENTED | PSEUDO_BIDI);
} else if (strcmp(cp, "-no-version-vectors") == 0) {
bundle.setNoVersionVectors(true);
+ } else if (strcmp(cp, "-no-version-transitions") == 0) {
+ bundle.setNoVersionTransitions(true);
} else if (strcmp(cp, "-private-symbols") == 0) {
argc--;
argv++;
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 045d68c..cf5badc 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1223,6 +1223,7 @@
sp<ResourceTypeSet> colors;
sp<ResourceTypeSet> menus;
sp<ResourceTypeSet> mipmaps;
+ sp<ResourceTypeSet> fonts;
ASSIGN_IT(drawable);
ASSIGN_IT(layout);
@@ -1235,6 +1236,7 @@
ASSIGN_IT(color);
ASSIGN_IT(menu);
ASSIGN_IT(mipmap);
+ ASSIGN_IT(font);
assets->setResources(resources);
// now go through any resource overlays and collect their files
@@ -1257,6 +1259,7 @@
!applyFileOverlay(bundle, assets, &raws, "raw") ||
!applyFileOverlay(bundle, assets, &colors, "color") ||
!applyFileOverlay(bundle, assets, &menus, "menu") ||
+ !applyFileOverlay(bundle, assets, &fonts, "font") ||
!applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
return UNKNOWN_ERROR;
}
@@ -1291,6 +1294,13 @@
}
}
+ if (fonts != NULL) {
+ err = makeFileResources(bundle, assets, &table, fonts, "font");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
if (layouts != NULL) {
err = makeFileResources(bundle, assets, &table, layouts, "layout");
if (err != NO_ERROR) {
@@ -1549,6 +1559,26 @@
err = NO_ERROR;
}
+ if (fonts != NULL) {
+ ResourceDirIterator it(fonts, String8("font"));
+ while ((err=it.next()) == NO_ERROR) {
+ // fonts can be resources other than xml.
+ if (it.getFile()->getPath().getPathExtension() == ".xml") {
+ String8 src = it.getFile()->getPrintableSource();
+ err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+ it.getFile(), &table, xmlFlags);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
// Now compile any generated resources.
std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
while (!workQueue.empty()) {
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 661409e..63498f7 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -4730,6 +4730,32 @@
return NO_ERROR;
}
+const String16 kTransitionElements[] = {
+ String16("fade"),
+ String16("changeBounds"),
+ String16("slide"),
+ String16("explode"),
+ String16("changeImageTransform"),
+ String16("changeTransform"),
+ String16("changeClipBounds"),
+ String16("autoTransition"),
+ String16("recolor"),
+ String16("changeScroll"),
+ String16("transitionSet"),
+ String16("transition"),
+ String16("transitionManager"),
+};
+
+static bool IsTransitionElement(const String16& name) {
+ for (int i = 0, size = sizeof(kTransitionElements) / sizeof(kTransitionElements[0]);
+ i < size; ++i) {
+ if (name == kTransitionElements[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
status_t ResourceTable::modifyForCompat(const Bundle* bundle,
const String16& resourceName,
const sp<AaptFile>& target,
@@ -4766,6 +4792,11 @@
continue;
}
+ if (bundle->getNoVersionTransitions() && (IsTransitionElement(node->getElementName()))) {
+ // We were told not to version transition tags, so skip the children here.
+ continue;
+ }
+
const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes();
for (size_t i = 0; i < attrs.size(); i++) {
const XMLNode::attribute_entry& attr = attrs[i];
diff --git a/tools/aapt/tests/AaptConfig_test.cpp b/tools/aapt/tests/AaptConfig_test.cpp
index 23f61e9..4f22fa5 100644
--- a/tools/aapt/tests/AaptConfig_test.cpp
+++ b/tools/aapt/tests/AaptConfig_test.cpp
@@ -103,13 +103,13 @@
ConfigDescription config;
EXPECT_TRUE(TestParse("widecg", &config));
EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES,
- config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+ config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
EXPECT_EQ(SDK_O, config.sdkVersion);
EXPECT_EQ(String8("widecg-v26"), config.toString());
EXPECT_TRUE(TestParse("nowidecg", &config));
EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_NO,
- config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+ config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
EXPECT_EQ(SDK_O, config.sdkVersion);
EXPECT_EQ(String8("nowidecg-v26"), config.toString());
}
@@ -118,13 +118,13 @@
ConfigDescription config;
EXPECT_TRUE(TestParse("highdr", &config));
EXPECT_EQ(android::ResTable_config::HDR_YES,
- config.colorimetry & android::ResTable_config::MASK_HDR);
+ config.colorMode & android::ResTable_config::MASK_HDR);
EXPECT_EQ(SDK_O, config.sdkVersion);
EXPECT_EQ(String8("highdr-v26"), config.toString());
EXPECT_TRUE(TestParse("lowdr", &config));
EXPECT_EQ(android::ResTable_config::HDR_NO,
- config.colorimetry & android::ResTable_config::MASK_HDR);
+ config.colorMode & android::ResTable_config::MASK_HDR);
EXPECT_EQ(SDK_O, config.sdkVersion);
EXPECT_EQ(String8("lowdr-v26"), config.toString());
}
\ No newline at end of file
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 5bea3ad..46098cb 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -209,20 +209,20 @@
static bool parseWideColorGamut(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out)
- out->colorimetry =
- (out->colorimetry & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+ out->colorMode =
+ (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
ResTable_config::WIDE_COLOR_GAMUT_ANY;
return true;
} else if (strcmp(name, "widecg") == 0) {
if (out)
- out->colorimetry =
- (out->colorimetry & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+ out->colorMode =
+ (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
ResTable_config::WIDE_COLOR_GAMUT_YES;
return true;
} else if (strcmp(name, "nowidecg") == 0) {
if (out)
- out->colorimetry =
- (out->colorimetry & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+ out->colorMode =
+ (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
ResTable_config::WIDE_COLOR_GAMUT_NO;
return true;
}
@@ -232,20 +232,20 @@
static bool parseHdr(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out)
- out->colorimetry =
- (out->colorimetry & ~ResTable_config::MASK_HDR) |
+ out->colorMode =
+ (out->colorMode & ~ResTable_config::MASK_HDR) |
ResTable_config::HDR_ANY;
return true;
} else if (strcmp(name, "highdr") == 0) {
if (out)
- out->colorimetry =
- (out->colorimetry & ~ResTable_config::MASK_HDR) |
+ out->colorMode =
+ (out->colorMode & ~ResTable_config::MASK_HDR) |
ResTable_config::HDR_YES;
return true;
} else if (strcmp(name, "lowdr") == 0) {
if (out)
- out->colorimetry =
- (out->colorimetry & ~ResTable_config::MASK_HDR) |
+ out->colorMode =
+ (out->colorMode & ~ResTable_config::MASK_HDR) |
ResTable_config::HDR_NO;
return true;
}
@@ -840,8 +840,8 @@
uint16_t min_sdk = 0;
if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
== ResTable_config::UI_MODE_TYPE_VR_HEADSET ||
- config->colorimetry & ResTable_config::MASK_WIDE_COLOR_GAMUT ||
- config->colorimetry & ResTable_config::MASK_HDR) {
+ config->colorMode & ResTable_config::MASK_WIDE_COLOR_GAMUT ||
+ config->colorMode & ResTable_config::MASK_HDR) {
min_sdk = SDK_O;
} else if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
min_sdk = SDK_MARSHMALLOW;
@@ -912,11 +912,11 @@
if ((screenLayout2 | o.screenLayout2) & MASK_SCREENROUND) {
return !(o.screenLayout2 & MASK_SCREENROUND);
}
- if ((colorimetry | o.colorimetry) & MASK_HDR) {
- return !(o.colorimetry & MASK_HDR);
+ if ((colorMode | o.colorMode) & MASK_HDR) {
+ return !(o.colorMode & MASK_HDR);
}
- if ((colorimetry | o.colorimetry) & MASK_WIDE_COLOR_GAMUT) {
- return !(o.colorimetry & MASK_WIDE_COLOR_GAMUT);
+ if ((colorMode | o.colorMode) & MASK_WIDE_COLOR_GAMUT) {
+ return !(o.colorMode & MASK_WIDE_COLOR_GAMUT);
}
if (orientation || o.orientation) return (!o.orientation);
if ((uiMode | o.uiMode) & MASK_UI_MODE_TYPE) {
@@ -964,9 +964,9 @@
!pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) ||
!pred(screenLayout2 & MASK_SCREENROUND,
o.screenLayout2 & MASK_SCREENROUND) ||
- !pred(colorimetry & MASK_HDR, o.colorimetry & MASK_HDR) ||
- !pred(colorimetry & MASK_WIDE_COLOR_GAMUT,
- o.colorimetry & MASK_WIDE_COLOR_GAMUT) ||
+ !pred(colorMode & MASK_HDR, o.colorMode & MASK_HDR) ||
+ !pred(colorMode & MASK_WIDE_COLOR_GAMUT,
+ o.colorMode & MASK_WIDE_COLOR_GAMUT) ||
!pred(orientation, o.orientation) ||
!pred(touchscreen, o.touchscreen) ||
!pred(inputFlags & MASK_KEYSHIDDEN, o.inputFlags & MASK_KEYSHIDDEN) ||
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index b88838a..14a5656 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -106,13 +106,13 @@
ConfigDescription config;
EXPECT_TRUE(TestParse("widecg", &config));
EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES,
- config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+ config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
EXPECT_EQ(SDK_O, config.sdkVersion);
EXPECT_EQ(std::string("widecg-v26"), config.toString().string());
EXPECT_TRUE(TestParse("nowidecg", &config));
EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_NO,
- config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+ config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
EXPECT_EQ(SDK_O, config.sdkVersion);
EXPECT_EQ(std::string("nowidecg-v26"), config.toString().string());
}
@@ -121,13 +121,13 @@
ConfigDescription config;
EXPECT_TRUE(TestParse("highdr", &config));
EXPECT_EQ(android::ResTable_config::HDR_YES,
- config.colorimetry & android::ResTable_config::MASK_HDR);
+ config.colorMode & android::ResTable_config::MASK_HDR);
EXPECT_EQ(SDK_O, config.sdkVersion);
EXPECT_EQ(std::string("highdr-v26"), config.toString().string());
EXPECT_TRUE(TestParse("lowdr", &config));
EXPECT_EQ(android::ResTable_config::HDR_NO,
- config.colorimetry & android::ResTable_config::MASK_HDR);
+ config.colorMode & android::ResTable_config::MASK_HDR);
EXPECT_EQ(SDK_O, config.sdkVersion);
EXPECT_EQ(std::string("lowdr-v26"), config.toString().string());
}
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 74d4019..15d7e2e 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -25,7 +25,7 @@
static const char* sMajorVersion = "2";
// Update minor version whenever a feature or flag is added.
-static const char* sMinorVersion = "4";
+static const char* sMinorVersion = "5";
int PrintVersion() {
std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/AppOne/Android.mk
index bc40a62..a6f32d4 100644
--- a/tools/aapt2/integration-tests/AppOne/Android.mk
+++ b/tools/aapt2/integration-tests/AppOne/Android.mk
@@ -24,5 +24,5 @@
LOCAL_STATIC_ANDROID_LIBRARIES := \
AaptTestStaticLibOne \
AaptTestStaticLibTwo
-LOCAL_AAPT_FLAGS := --no-version-vectors
+LOCAL_AAPT_FLAGS := --no-version-vectors --no-version-transitions
include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml b/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml
new file mode 100644
index 0000000..e10e6c2
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:transitionOrdering="sequential">
+ <fade android:fadingMode="fade_out" />
+ <changeBounds />
+ <fade android:fadingMode="fade_in" />
+</transitionSet>
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index c3ce076..f7e0f8f 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -80,6 +80,7 @@
// Optimizations/features.
bool no_auto_version = false;
bool no_version_vectors = false;
+ bool no_version_transitions = false;
bool no_resource_deduping = false;
bool no_xml_namespaces = false;
bool do_not_compress_anything = false;
@@ -250,6 +251,7 @@
struct ResourceFileFlattenerOptions {
bool no_auto_version = false;
bool no_version_vectors = false;
+ bool no_version_transitions = false;
bool no_xml_namespaces = false;
bool keep_raw_values = false;
bool do_not_compress_anything = false;
@@ -306,6 +308,23 @@
return ArchiveEntry::kCompress;
}
+static bool IsTransitionElement(const std::string& name) {
+ return
+ name == "fade" ||
+ name == "changeBounds" ||
+ name == "slide" ||
+ name == "explode" ||
+ name == "changeImageTransform" ||
+ name == "changeTransform" ||
+ name == "changeClipBounds" ||
+ name == "autoTransition" ||
+ name == "recolor" ||
+ name == "changeScroll" ||
+ name == "transitionSet" ||
+ name == "transition" ||
+ name == "transitionManager";
+}
+
bool ResourceFileFlattener::LinkAndVersionXmlFile(
ResourceTable* table, FileOperation* file_op,
std::queue<FileOperation>* out_file_op_queue) {
@@ -345,6 +364,17 @@
}
}
}
+ if (options_.no_version_transitions) {
+ // Skip this if it is a transition resource.
+ xml::Element* el = xml::FindRootElement(doc);
+ if (el && el->namespace_uri.empty()) {
+ if (IsTransitionElement(el->name)) {
+ // We are NOT going to version this file.
+ file_op->skip_version = true;
+ return true;
+ }
+ }
+ }
const ConfigDescription& config = file_op->config;
@@ -1384,6 +1414,7 @@
options_.extensions_to_not_compress;
file_flattener_options.no_auto_version = options_.no_auto_version;
file_flattener_options.no_version_vectors = options_.no_version_vectors;
+ file_flattener_options.no_version_transitions = options_.no_version_transitions;
file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces;
file_flattener_options.update_proguard_spec =
static_cast<bool>(options_.generate_proguard_rules_path);
@@ -1863,6 +1894,11 @@
"Use this only\n"
"when building with vector drawable support library",
&options.no_version_vectors)
+ .OptionalSwitch("--no-version-transitions",
+ "Disables automatic versioning of transition resources. "
+ "Use this only\n"
+ "when building with transition support library",
+ &options.no_version_transitions)
.OptionalSwitch("--no-resource-deduping",
"Disables automatic deduping of resources with\n"
"identical values across compatible configurations.",
@@ -2104,6 +2140,7 @@
if (options.static_lib) {
options.no_auto_version = true;
options.no_version_vectors = true;
+ options.no_version_transitions = true;
}
LinkCommand cmd(&context, options);
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 8001033..e2a752e 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -1,5 +1,10 @@
# Android Asset Packaging Tool 2.0 (AAPT2) release notes
+## Version 2.5
+### `aapt2 link ...`
+- Transition XML versioning: Adds a new flag `--no-version-transitions` to disable automatic
+ versioning of Transition XML resources.
+
## Version 2.4
### `aapt2 link ...`
- Supports `<meta-data>` tags in `<manifest>`.
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index 50efc7f..147ed99 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.text.FontConfig;
import com.android.ide.common.rendering.api.AssetRepository;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
@@ -248,14 +249,17 @@
// ---- delegate methods ----
@LayoutlibDelegate
/*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex) {
- final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mNativePtr);
+ if (thisFontFamily.mBuilderPtr == 0) {
+ throw new IllegalStateException("Unable to call addFont after freezing.");
+ }
+ final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mBuilderPtr);
return delegate != null && delegate.addFont(path, ttcIndex);
}
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long nCreateFamily(String lang, int variant) {
+ /*package*/ static long nInitBuilder(String lang, int variant) {
// TODO: support lang. This is required for japanese locale.
FontFamily_Delegate delegate = new FontFamily_Delegate();
// variant can be 0, 1 or 2.
@@ -270,6 +274,11 @@
}
@LayoutlibDelegate
+ /*package*/ static long nCreateFamily(long builderPtr) {
+ return builderPtr;
+ }
+
+ @LayoutlibDelegate
/*package*/ static void nUnrefFamily(long nativePtr) {
// Removing the java reference for the object doesn't mean that it's freed for garbage
// collection. Typeface_Delegate may still hold a reference for it.
@@ -277,22 +286,22 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex) {
+ /*package*/ static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) {
assert false : "The only client of this method has been overriden.";
return false;
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
- int ttcIndex, List<FontListParser.Axis> listOfAxis,
+ /*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
+ int ttcIndex, List<FontConfig.Axis> listOfAxis,
int weight, boolean isItalic) {
assert false : "The only client of this method has been overriden.";
return false;
}
- static boolean addFont(long nativeFamily, final String path, final int weight,
+ static boolean addFont(long builderPtr, final String path, final int weight,
final boolean isItalic) {
- final FontFamily_Delegate delegate = getDelegate(nativeFamily);
+ final FontFamily_Delegate delegate = getDelegate(builderPtr);
if (delegate != null) {
if (sFontLocation == null) {
delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic));
@@ -304,8 +313,8 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
- FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily);
+ /*package*/ static boolean nAddFontFromAsset(long builderPtr, AssetManager mgr, String path) {
+ FontFamily_Delegate ffd = sManager.getDelegate(builderPtr);
if (ffd == null) {
return false;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 5cd34f6..f6c463f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.text.FontConfig;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
@@ -208,13 +209,14 @@
}
@LayoutlibDelegate
- /*package*/ static FontFamily makeFamilyFromParsed(FontListParser.Family family,
+ /*package*/ static FontFamily makeFamilyFromParsed(FontConfig.Family family,
Map<String, ByteBuffer> bufferForPath) {
- FontFamily fontFamily = new FontFamily(family.lang, family.variant);
- for (FontListParser.Font font : family.fonts) {
- FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.fontName, font.weight,
- font.isItalic);
+ FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
+ for (FontConfig.Font font : family.getFonts()) {
+ FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, font.getFontName(),
+ font.getWeight(), font.isItalic());
}
+ fontFamily.freeze();
return fontFamily;
}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 5ed5460..56898f1 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -340,12 +340,6 @@
}
@Override
- public Rect getBoundsForNewConfiguration(int stackId) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
public void setScreenCaptureDisabled(int userId, boolean disabled) {
// TODO Auto-generated method stub
}
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 7ba86fd..741eb27 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
@@ -339,7 +339,8 @@
*/
private final static String[] PROMOTED_FIELDS = new String[] {
"android.graphics.drawable.VectorDrawable#mVectorState",
- "android.view.Choreographer#mLastFrameTimeNanos"
+ "android.view.Choreographer#mLastFrameTimeNanos",
+ "android.graphics.FontFamily#mBuilderPtr"
};
/**
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
index 65a49ea..98fd0f3 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -21,11 +21,17 @@
import android.net.wifi.hotspot2.pps.HomeSP;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.xml.sax.SAXException;
@@ -131,16 +137,34 @@
private static final String NODE_FQDN = "FQDN";
private static final String NODE_FRIENDLY_NAME = "FriendlyName";
private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
+ private static final String NODE_NETWORK_ID = "NetworkID";
+ private static final String NODE_SSID = "SSID";
+ private static final String NODE_HESSID = "HESSID";
+ private static final String NODE_ICON_URL = "IconURL";
+ private static final String NODE_HOME_OI_LIST = "HomeOIList";
+ private static final String NODE_HOME_OI = "HomeOI";
+ private static final String NODE_HOME_OI_REQUIRED = "HomeOIRequired";
+ private static final String NODE_OTHER_HOME_PARTNERS = "OtherHomePartners";
/**
* Fields under Credential subtree.
*/
private static final String NODE_CREDENTIAL = "Credential";
+ private static final String NODE_CREATION_DATE = "CreationDate";
+ private static final String NODE_EXPIRATION_DATE = "ExpirationDate";
private static final String NODE_USERNAME_PASSWORD = "UsernamePassword";
private static final String NODE_USERNAME = "Username";
private static final String NODE_PASSWORD = "Password";
+ private static final String NODE_MACHINE_MANAGED = "MachineManaged";
+ private static final String NODE_SOFT_TOKEN_APP = "SoftTokenApp";
+ private static final String NODE_ABLE_TO_SHARE = "AbleToShare";
private static final String NODE_EAP_METHOD = "EAPMethod";
private static final String NODE_EAP_TYPE = "EAPType";
+ private static final String NODE_VENDOR_ID = "VendorId";
+ private static final String NODE_VENDOR_TYPE = "VendorType";
+ private static final String NODE_INNER_EAP_TYPE = "InnerEAPType";
+ private static final String NODE_INNER_VENDOR_ID = "InnerVendorID";
+ private static final String NODE_INNER_VENDOR_TYPE = "InnerVendorType";
private static final String NODE_INNER_METHOD = "InnerMethod";
private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
@@ -148,6 +172,7 @@
private static final String NODE_REALM = "Realm";
private static final String NODE_SIM = "SIM";
private static final String NODE_SIM_IMSI = "IMSI";
+ private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
/**
* URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
@@ -558,6 +583,20 @@
homeSp.roamingConsortiumOIs =
parseRoamingConsortiumOI(getPpsNodeValue(child));
break;
+ case NODE_ICON_URL:
+ homeSp.iconUrl = getPpsNodeValue(child);
+ break;
+ case NODE_NETWORK_ID:
+ homeSp.homeNetworkIds = parseNetworkIds(child);
+ break;
+ case NODE_HOME_OI_LIST:
+ Pair<List<Long>, List<Long>> homeOIs = parseHomeOIList(child);
+ homeSp.matchAllOIs = convertFromLongList(homeOIs.first);
+ homeSp.matchAnyOIs = convertFromLongList(homeOIs.second);
+ break;
+ case NODE_OTHER_HOME_PARTNERS:
+ homeSp.otherHomePartners = parseOtherHomePartners(child);
+ break;
default:
throw new ParsingException("Unknown node under HomeSP: " + child.getName());
}
@@ -587,6 +626,192 @@
}
/**
+ * Parse configurations under PerProviderSubscription/HomeSP/NetworkID subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/NetworkID
+ * subtree
+ * @return HashMap<String, Long> representing list of <SSID, HESSID> pair.
+ * @throws ParsingException
+ */
+ static private Map<String, Long> parseNetworkIds(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for NetworkID");
+ }
+
+ Map<String, Long> networkIds = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<String, Long> networkId = parseNetworkIdInstance(child);
+ networkIds.put(networkId.first, networkId.second);
+ }
+ return networkIds;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/NetworkID/<X+> subtree.
+ * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+ * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/NetworkID/<X+> subtree
+ * @return Pair<String, Long> representing <SSID, HESSID> pair.
+ * @throws ParsingException
+ */
+ static private Pair<String, Long> parseNetworkIdInstance(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for NetworkID instance");
+ }
+
+ String ssid = null;
+ Long hessid = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_SSID:
+ ssid = getPpsNodeValue(child);
+ break;
+ case NODE_HESSID:
+ try {
+ hessid = Long.parseLong(getPpsNodeValue(child), 16);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid HESSID: " + getPpsNodeValue(child));
+ }
+ break;
+ default:
+ throw new ParsingException("Unknown node under NetworkID instance: " +
+ child.getName());
+ }
+ }
+ if (ssid == null)
+ throw new ParsingException("NetworkID instance missing SSID");
+
+ return new Pair<String, Long>(ssid, hessid);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/HomeOIList
+ * subtree
+ * @return Pair<List<Long>, List<Long>> containing both MatchAllOIs and MatchAnyOIs list.
+ * @throws ParsingException
+ */
+ private static Pair<List<Long>, List<Long>> parseHomeOIList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeOIList");
+ }
+
+ List<Long> matchAllOIs = new ArrayList<Long>();
+ List<Long> matchAnyOIs = new ArrayList<Long>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<Long, Boolean> homeOI = parseHomeOIInstance(child);
+ if (homeOI.second.booleanValue()) {
+ matchAllOIs.add(homeOI.first);
+ } else {
+ matchAnyOIs.add(homeOI.first);
+ }
+ }
+ return new Pair<List<Long>, List<Long>>(matchAllOIs, matchAnyOIs);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree.
+ * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+ * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree
+ * @return Pair<Long, Boolean> containing a HomeOI and a HomeOIRequired flag
+ * @throws ParsingException
+ */
+ private static Pair<Long, Boolean> parseHomeOIInstance(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeOI instance");
+ }
+
+ Long oi = null;
+ Boolean required = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_HOME_OI:
+ try {
+ oi = Long.valueOf(getPpsNodeValue(child), 16);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid HomeOI: " + getPpsNodeValue(child));
+ }
+ break;
+ case NODE_HOME_OI_REQUIRED:
+ required = Boolean.valueOf(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under NetworkID instance: " +
+ child.getName());
+ }
+ }
+ if (oi == null) {
+ throw new ParsingException("HomeOI instance missing OI field");
+ }
+ if (required == null) {
+ throw new ParsingException("HomeOI instance missing required field");
+ }
+ return new Pair<Long, Boolean>(oi, required);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners subtree.
+ * This contains a list of FQDN (Fully Qualified Domain Name) that are considered
+ * home partners.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/OtherHomePartners subtree
+ * @return String[] list of partner's FQDN
+ * @throws ParsingException
+ */
+ private static String[] parseOtherHomePartners(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for OtherHomePartners");
+ }
+ List<String> otherHomePartners = new ArrayList<String>();
+ for (PPSNode child : node.getChildren()) {
+ String fqdn = parseOtherHomePartnerInstance(child);
+ otherHomePartners.add(fqdn);
+ }
+ return otherHomePartners.toArray(new String[otherHomePartners.size()]);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree.
+ * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+ * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree
+ * @return String FQDN of the partner
+ * @throws ParsingException
+ */
+ private static String parseOtherHomePartnerInstance(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for OtherHomePartner instance");
+ }
+ String fqdn = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_FQDN:
+ fqdn = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException(
+ "Unknown node under OtherHomePartner instance: " + child.getName());
+ }
+ }
+ if (fqdn == null) {
+ throw new ParsingException("OtherHomePartner instance missing FQDN field");
+ }
+ return fqdn;
+ }
+
+ /**
* Parse configurations under PerProviderSubscription/Credential subtree.
*
* @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree
@@ -601,6 +826,12 @@
Credential credential = new Credential();
for (PPSNode child: node.getChildren()) {
switch (child.getName()) {
+ case NODE_CREATION_DATE:
+ credential.creationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_EXPIRATION_DATE:
+ credential.expirationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
case NODE_USERNAME_PASSWORD:
credential.userCredential = parseUserCredential(child);
break;
@@ -610,6 +841,10 @@
case NODE_REALM:
credential.realm = getPpsNodeValue(child);
break;
+ case NODE_CHECK_AAA_SERVER_CERT_STATUS:
+ credential.checkAAAServerCertStatus =
+ Boolean.parseBoolean(getPpsNodeValue(child));
+ break;
case NODE_SIM:
credential.simCredential = parseSimCredential(child);
break;
@@ -644,6 +879,15 @@
case NODE_PASSWORD:
userCred.password = getPpsNodeValue(child);
break;
+ case NODE_MACHINE_MANAGED:
+ userCred.machineManaged = Boolean.parseBoolean(getPpsNodeValue(child));
+ break;
+ case NODE_SOFT_TOKEN_APP:
+ userCred.softTokenApp = getPpsNodeValue(child);
+ break;
+ case NODE_ABLE_TO_SHARE:
+ userCred.ableToShare = Boolean.parseBoolean(getPpsNodeValue(child));
+ break;
case NODE_EAP_METHOD:
parseEAPMethod(child, userCred);
break;
@@ -678,6 +922,15 @@
case NODE_INNER_METHOD:
userCred.nonEapInnerMethod = getPpsNodeValue(child);
break;
+ case NODE_VENDOR_ID:
+ case NODE_VENDOR_TYPE:
+ case NODE_INNER_EAP_TYPE:
+ case NODE_INNER_VENDOR_ID:
+ case NODE_INNER_VENDOR_TYPE:
+ // Only EAP-TTLS is currently supported for user credential, which doesn't
+ // use any of these parameters.
+ Log.d(TAG, "Ignore unsupported EAP method parameter: " + child.getName());
+ break;
default:
throw new ParsingException("Unknown node under EAPMethod: " + child.getName());
}
@@ -770,6 +1023,22 @@
}
/**
+ * Convert a date string to the number of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * @param dateStr String in the format of yyyy-MM-dd'T'HH:mm:ss'Z'
+ * @return number of milliseconds
+ * @throws ParsingException
+ */
+ private static long parseDate(String dateStr) throws ParsingException {
+ try {
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ return format.parse(dateStr).getTime();
+ } catch (ParseException pe) {
+ throw new ParsingException("Badly formatted time: " + dateStr);
+ }
+ }
+
+ /**
* Parse an integer string.
*
* @param value String of integer value
@@ -783,4 +1052,19 @@
throw new ParsingException("Invalid integer value: " + value);
}
}
+
+ /**
+ * Convert a List<Long> to a primitive long array long[].
+ *
+ * @param list List to be converted
+ * @return long[]
+ */
+ private static long[] convertFromLongList(List<Long> list) {
+ Long[] objectArray = list.toArray(new Long[list.size()]);
+ long[] primitiveArray = new long[objectArray.length];
+ for (int i = 0; i < objectArray.length; i++) {
+ primitiveArray[i] = objectArray[i].longValue();
+ }
+ return primitiveArray;
+ }
}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 790dfaf..3374f42d 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -23,6 +23,7 @@
import android.text.TextUtils;
import android.util.Log;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
@@ -41,8 +42,6 @@
* In addition to the fields in the Credential subtree, this will also maintain necessary
* information for the private key and certificates associated with this credential.
*
- * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
- *
* @hide
*/
public final class Credential implements Parcelable {
@@ -52,7 +51,21 @@
* Max string length for realm. Refer to Credential/Realm node in Hotspot 2.0 Release 2
* Technical Specification Section 9.1 for more info.
*/
- private static final int MAX_REALM_LENGTH = 253;
+ private static final int MAX_REALM_BYTES = 253;
+
+ /**
+ * The time this credential is created. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long creationTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The time this credential will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long expirationTimeInMs = Long.MIN_VALUE;
/**
* The realm associated with this credential. It will be used to determine
@@ -62,6 +75,13 @@
public String realm = null;
/**
+ * When set to true, the device should check AAA (Authentication, Authorization,
+ * and Accounting) server's certificate during EAP (Extensible Authentication
+ * Protocol) authentication.
+ */
+ public boolean checkAAAServerCertStatus = false;
+
+ /**
* Username-password based credential.
* Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree.
*/
@@ -70,13 +90,13 @@
* Maximum string length for username. Refer to Credential/UsernamePassword/Username
* node in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
*/
- private static final int MAX_USERNAME_LENGTH = 63;
+ private static final int MAX_USERNAME_BYTES = 63;
/**
* Maximum string length for password. Refer to Credential/UsernamePassword/Password
* in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
*/
- private static final int MAX_PASSWORD_LENGTH = 255;
+ private static final int MAX_PASSWORD_BYTES = 255;
/**
* Supported Non-EAP inner methods. Refer to
@@ -97,6 +117,21 @@
public String password = null;
/**
+ * Flag indicating if the password is machine managed.
+ */
+ public boolean machineManaged = false;
+
+ /**
+ * The name of the application used to generate the password.
+ */
+ public String softTokenApp = null;
+
+ /**
+ * Flag indicating if this credential is usable on other mobile devices as well.
+ */
+ public boolean ableToShare = false;
+
+ /**
* EAP (Extensible Authentication Protocol) method type.
* Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
* for valid values.
@@ -123,6 +158,9 @@
if (source != null) {
username = source.username;
password = source.password;
+ machineManaged = source.machineManaged;
+ softTokenApp = source.softTokenApp;
+ ableToShare = source.ableToShare;
eapType = source.eapType;
nonEapInnerMethod = source.nonEapInnerMethod;
}
@@ -137,6 +175,9 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(username);
dest.writeString(password);
+ dest.writeInt(machineManaged ? 1 : 0);
+ dest.writeString(softTokenApp);
+ dest.writeInt(ableToShare ? 1 : 0);
dest.writeInt(eapType);
dest.writeString(nonEapInnerMethod);
}
@@ -151,10 +192,13 @@
}
UserCredential that = (UserCredential) thatObject;
- return TextUtils.equals(username, that.username) &&
- TextUtils.equals(password, that.password) &&
- eapType == that.eapType &&
- TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
+ return TextUtils.equals(username, that.username)
+ && TextUtils.equals(password, that.password)
+ && machineManaged == that.machineManaged
+ && TextUtils.equals(softTokenApp, that.softTokenApp)
+ && ableToShare == that.ableToShare
+ && eapType == that.eapType
+ && TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
}
/**
@@ -167,8 +211,9 @@
Log.d(TAG, "Missing username");
return false;
}
- if (username.length() > MAX_USERNAME_LENGTH) {
- Log.d(TAG, "username exceeding maximum length: " + username.length());
+ if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+ Log.d(TAG, "username exceeding maximum length: "
+ + username.getBytes(StandardCharsets.UTF_8).length);
return false;
}
@@ -176,8 +221,9 @@
Log.d(TAG, "Missing password");
return false;
}
- if (password.length() > MAX_PASSWORD_LENGTH) {
- Log.d(TAG, "password exceeding maximum length: " + password.length());
+ if (password.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+ Log.d(TAG, "password exceeding maximum length: "
+ + password.getBytes(StandardCharsets.UTF_8).length);
return false;
}
@@ -202,6 +248,9 @@
UserCredential userCredential = new UserCredential();
userCredential.username = in.readString();
userCredential.password = in.readString();
+ userCredential.machineManaged = in.readInt() != 0;
+ userCredential.softTokenApp = in.readString();
+ userCredential.ableToShare = in.readInt() != 0;
userCredential.eapType = in.readInt();
userCredential.nonEapInnerMethod = in.readString();
return userCredential;
@@ -281,8 +330,8 @@
}
CertificateCredential that = (CertificateCredential) thatObject;
- return TextUtils.equals(certType, that.certType) &&
- Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
+ return TextUtils.equals(certType, that.certType)
+ && Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
}
/**
@@ -295,8 +344,8 @@
Log.d(TAG, "Unsupported certificate type: " + certType);
return false;
}
- if (certSha256FingerPrint == null ||
- certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
+ if (certSha256FingerPrint == null
+ || certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
Log.d(TAG, "Invalid SHA-256 fingerprint");
return false;
}
@@ -378,8 +427,8 @@
}
SimCredential that = (SimCredential) thatObject;
- return TextUtils.equals(imsi, that.imsi) &&
- eapType == that.eapType;
+ return TextUtils.equals(imsi, that.imsi)
+ && eapType == that.eapType;
}
@Override
@@ -400,8 +449,8 @@
if (!verifyImsi()) {
return false;
}
- if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA &&
- eapType != EAPConstants.EAP_AKA_PRIME) {
+ if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA
+ && eapType != EAPConstants.EAP_AKA_PRIME) {
Log.d(TAG, "Invalid EAP Type for SIM credential: " + eapType);
return false;
}
@@ -490,7 +539,10 @@
*/
public Credential(Credential source) {
if (source != null) {
+ creationTimeInMs = source.creationTimeInMs;
+ expirationTimeInMs = source.expirationTimeInMs;
realm = source.realm;
+ checkAAAServerCertStatus = source.checkAAAServerCertStatus;
if (source.userCredential != null) {
userCredential = new UserCredential(source.userCredential);
}
@@ -516,7 +568,10 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(creationTimeInMs);
+ dest.writeLong(expirationTimeInMs);
dest.writeString(realm);
+ dest.writeInt(checkAAAServerCertStatus ? 1 : 0);
dest.writeParcelable(userCredential, flags);
dest.writeParcelable(certCredential, flags);
dest.writeParcelable(simCredential, flags);
@@ -535,16 +590,19 @@
}
Credential that = (Credential) thatObject;
- return TextUtils.equals(realm, that.realm) &&
- (userCredential == null ? that.userCredential == null :
- userCredential.equals(that.userCredential)) &&
- (certCredential == null ? that.certCredential == null :
- certCredential.equals(that.certCredential)) &&
- (simCredential == null ? that.simCredential == null :
- simCredential.equals(that.simCredential)) &&
- isX509CertificateEquals(caCertificate, that.caCertificate) &&
- isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain) &&
- isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
+ return TextUtils.equals(realm, that.realm)
+ && creationTimeInMs == that.creationTimeInMs
+ && expirationTimeInMs == that.expirationTimeInMs
+ && checkAAAServerCertStatus == that.checkAAAServerCertStatus
+ && (userCredential == null ? that.userCredential == null
+ : userCredential.equals(that.userCredential))
+ && (certCredential == null ? that.certCredential == null
+ : certCredential.equals(that.certCredential))
+ && (simCredential == null ? that.simCredential == null
+ : simCredential.equals(that.simCredential))
+ && isX509CertificateEquals(caCertificate, that.caCertificate)
+ && isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain)
+ && isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
}
/**
@@ -557,8 +615,9 @@
Log.d(TAG, "Missing realm");
return false;
}
- if (realm.length() > MAX_REALM_LENGTH) {
- Log.d(TAG, "realm exceeding maximum length: " + realm.length());
+ if (realm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
+ Log.d(TAG, "realm exceeding maximum length: "
+ + realm.getBytes(StandardCharsets.UTF_8).length);
return false;
}
@@ -588,7 +647,10 @@
@Override
public Credential createFromParcel(Parcel in) {
Credential credential = new Credential();
+ credential.creationTimeInMs = in.readLong();
+ credential.expirationTimeInMs = in.readLong();
credential.realm = in.readString();
+ credential.checkAAAServerCertStatus = in.readInt() != 0;
credential.userCredential = in.readParcelable(null);
credential.certCredential = in.readParcelable(null);
credential.simCredential = in.readParcelable(null);
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
index d4a5792..4ddf210 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
@@ -21,7 +21,11 @@
import android.text.TextUtils;
import android.util.Log;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
/**
* Class representing HomeSP subtree in PerProviderSubscription (PPS)
@@ -30,14 +34,22 @@
* For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
* Release 2 Technical Specification.
*
- * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
- *
* @hide
*/
public final class HomeSP implements Parcelable {
private static final String TAG = "HomeSP";
/**
+ * Maximum number of bytes allowed for a SSID.
+ */
+ private static final int MAX_SSID_BYTES = 32;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
+ /**
* FQDN (Fully Qualified Domain Name) of this home service provider.
*/
public String fqdn = null;
@@ -48,6 +60,55 @@
public String friendlyName = null;
/**
+ * Icon URL of this home service provider.
+ */
+ public String iconUrl = null;
+
+ /**
+ * <SSID, HESSID> duple of the networks that are consider home networks.
+ *
+ * According to the Section 9.1.2 of the Hotspot 2.0 Release 2 Technical Specification,
+ * all nodes in the PSS MO are encoded using UTF-8 unless stated otherwise. Thus, the SSID
+ * string is assumed to be encoded using UTF-8.
+ */
+ public Map<String, Long> homeNetworkIds = null;
+
+ /**
+ * Used for determining if this provider is a member of a given Hotspot provider.
+ * Every Organization Identifiers (OIs) in this list are required to match an OI in the
+ * the Roaming Consortium advertised by a Hotspot, in order to consider this provider
+ * as a member of that Hotspot provider (e.g. successful authentication with such Hotspot
+ * is possible).
+ *
+ * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+ * (MO) tree for more detail.
+ */
+ public long[] matchAllOIs = null;
+
+ /**
+ * Used for determining if this provider is a member of a given Hotspot provider.
+ * Matching of any Organization Identifiers (OIs) in this list with an OI in the
+ * Roaming Consortium advertised by a Hotspot, will consider this provider as a member
+ * of that Hotspot provider (e.g. successful authentication with such Hotspot
+ * is possible).
+ *
+ * {@link #matchAllOIs} will have precedence over this one, meaning this list will
+ * only be used for matching if {@link #matchAllOIs} is null or empty.
+ *
+ * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+ * (MO) tree for more detail.
+ */
+ public long[] matchAnyOIs = null;
+
+ /**
+ * List of FQDN (Fully Qualified Domain Name) of partner providers.
+ * These providers should also be regarded as home Hotspot operators.
+ * This relationship is most likely achieved via a commercial agreement or
+ * operator merges between the providers.
+ */
+ public String[] otherHomePartners = null;
+
+ /**
* List of Organization Identifiers (OIs) identifying a roaming consortium of
* which this provider is a member.
*/
@@ -64,13 +125,28 @@
* @param source The source to copy from
*/
public HomeSP(HomeSP source) {
- if (source != null) {
- fqdn = source.fqdn;
- friendlyName = source.friendlyName;
- if (source.roamingConsortiumOIs != null) {
- roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
- source.roamingConsortiumOIs.length);
- }
+ if (source == null) {
+ return;
+ }
+ fqdn = source.fqdn;
+ friendlyName = source.friendlyName;
+ iconUrl = source.iconUrl;
+ if (source.homeNetworkIds != null) {
+ homeNetworkIds = Collections.unmodifiableMap(source.homeNetworkIds);
+ }
+ if (source.matchAllOIs != null) {
+ matchAllOIs = Arrays.copyOf(source.matchAllOIs, source.matchAllOIs.length);
+ }
+ if (source.matchAnyOIs != null) {
+ matchAnyOIs = Arrays.copyOf(source.matchAnyOIs, source.matchAnyOIs.length);
+ }
+ if (source.otherHomePartners != null) {
+ otherHomePartners = Arrays.copyOf(source.otherHomePartners,
+ source.otherHomePartners.length);
+ }
+ if (source.roamingConsortiumOIs != null) {
+ roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
+ source.roamingConsortiumOIs.length);
}
}
@@ -83,6 +159,11 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(fqdn);
dest.writeString(friendlyName);
+ dest.writeString(iconUrl);
+ writeHomeNetworkIds(dest, homeNetworkIds);
+ dest.writeLongArray(matchAllOIs);
+ dest.writeLongArray(matchAnyOIs);
+ dest.writeStringArray(otherHomePartners);
dest.writeLongArray(roamingConsortiumOIs);
}
@@ -96,9 +177,15 @@
}
HomeSP that = (HomeSP) thatObject;
- return TextUtils.equals(fqdn, that.fqdn) &&
- TextUtils.equals(friendlyName, that.friendlyName) &&
- Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs);
+ return TextUtils.equals(fqdn, that.fqdn)
+ && TextUtils.equals(friendlyName, that.friendlyName)
+ && TextUtils.equals(iconUrl, that.iconUrl)
+ && (homeNetworkIds == null ? that.homeNetworkIds == null
+ : homeNetworkIds.equals(that.homeNetworkIds))
+ && Arrays.equals(matchAllOIs, that.matchAllOIs)
+ && Arrays.equals(matchAnyOIs, that.matchAnyOIs)
+ && Arrays.equals(otherHomePartners, that.otherHomePartners)
+ && Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs);
}
/**
@@ -115,6 +202,16 @@
Log.d(TAG, "Missing friendly name");
return false;
}
+ // Verify SSIDs specified in the NetworkID
+ if (homeNetworkIds != null) {
+ for (Map.Entry<String, Long> entry : homeNetworkIds.entrySet()) {
+ if (entry.getKey() == null ||
+ entry.getKey().getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+ Log.d(TAG, "Invalid SSID in HomeNetworkIDs");
+ return false;
+ }
+ }
+ }
return true;
}
@@ -125,6 +222,11 @@
HomeSP homeSp = new HomeSP();
homeSp.fqdn = in.readString();
homeSp.friendlyName = in.readString();
+ homeSp.iconUrl = in.readString();
+ homeSp.homeNetworkIds = readHomeNetworkIds(in);
+ homeSp.matchAllOIs = in.createLongArray();
+ homeSp.matchAnyOIs = in.createLongArray();
+ homeSp.otherHomePartners = in.createStringArray();
homeSp.roamingConsortiumOIs = in.createLongArray();
return homeSp;
}
@@ -133,5 +235,51 @@
public HomeSP[] newArray(int size) {
return new HomeSP[size];
}
+
+ /**
+ * Helper function for reading a Home Network IDs map from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return Map of home network IDs
+ */
+ private Map<String, Long> readHomeNetworkIds(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<String, Long> networkIds = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ String key = in.readString();
+ Long value = null;
+ long readValue = in.readLong();
+ if (readValue != NULL_VALUE) {
+ value = Long.valueOf(readValue);
+ }
+ networkIds.put(key, value);
+ }
+ return networkIds;
+ }
};
+
+ /**
+ * Helper function for writing Home Network IDs map to a Parcel.
+ *
+ * @param dest The Parcel to write to
+ * @param networkIds The map of home network IDs
+ */
+ private static void writeHomeNetworkIds(Parcel dest, Map<String, Long> networkIds) {
+ if (networkIds == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(networkIds.size());
+ for (Map.Entry<String, Long> entry : networkIds.entrySet()) {
+ dest.writeString(entry.getKey());
+ if (entry.getValue() == null) {
+ dest.writeLong(NULL_VALUE);
+ } else {
+ dest.writeLong(entry.getValue());
+ }
+ }
+ }
}
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 53d38ad..3969f69 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -23,14 +23,86 @@
<NodeName>RoamingConsortiumOI</NodeName>
<Value>112233,445566</Value>
</Node>
+ <Node>
+ <NodeName>IconURL</NodeName>
+ <Value>icon.test.com</Value>
+ </Node>
+ <Node>
+ <NodeName>NetworkID</NodeName>
+ <Node>
+ <NodeName>n001</NodeName>
+ <Node>
+ <NodeName>SSID</NodeName>
+ <Value>TestSSID</Value>
+ </Node>
+ <Node>
+ <NodeName>HESSID</NodeName>
+ <Value>12345678</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>n002</NodeName>
+ <Node>
+ <NodeName>SSID</NodeName>
+ <Value>NullHESSID</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>HomeOIList</NodeName>
+ <Node>
+ <NodeName>h001</NodeName>
+ <Node>
+ <NodeName>HomeOI</NodeName>
+ <Value>11223344</Value>
+ </Node>
+ <Node>
+ <NodeName>HomeOIRequired</NodeName>
+ <Value>true</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>h002</NodeName>
+ <Node>
+ <NodeName>HomeOI</NodeName>
+ <Value>55667788</Value>
+ </Node>
+ <Node>
+ <NodeName>HomeOIRequired</NodeName>
+ <Value>false</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>OtherHomePartners</NodeName>
+ <Node>
+ <NodeName>o001</NodeName>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>other.fqdn.com</Value>
+ </Node>
+ </Node>
+ </Node>
</Node>
<Node>
<NodeName>Credential</NodeName>
<Node>
+ <NodeName>CreationDate</NodeName>
+ <Value>2016-01-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>ExpirationDate</NodeName>
+ <Value>2016-02-01T10:00:00Z</Value>
+ </Node>
+ <Node>
<NodeName>Realm</NodeName>
<Value>shaken.stirred.com</Value>
</Node>
<Node>
+ <NodeName>CheckAAAServerCertStatus</NodeName>
+ <Value>true</Value>
+ </Node>
+ <Node>
<NodeName>UsernamePassword</NodeName>
<Node>
<NodeName>Username</NodeName>
@@ -41,6 +113,18 @@
<Value>Ym9uZDAwNw==</Value>
</Node>
<Node>
+ <NodeName>MachineManaged</NodeName>
+ <Value>true</Value>
+ </Node>
+ <Node>
+ <NodeName>SoftTokenApp</NodeName>
+ <Value>TestApp</Value>
+ </Node>
+ <Node>
+ <NodeName>AbleToShare</NodeName>
+ <Value>true</Value>
+ </Node>
+ <Node>
<NodeName>EAPMethod</NodeName>
<Node>
<NodeName>EAPType</NodeName>
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
index 10b0267..1c7508e 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -31,7 +31,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
+import java.util.HashMap;
/**
* Unit tests for {@link android.net.wifi.hotspot2.omadm.PPSMOParser}.
@@ -77,7 +80,7 @@
*
* @return {@link PasspointConfiguration}
*/
- private PasspointConfiguration generateConfigurationFromPPSMOTree() {
+ private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
PasspointConfiguration config = new PasspointConfiguration();
// HomeSP configuration.
@@ -85,13 +88,27 @@
config.homeSp.friendlyName = "Century House";
config.homeSp.fqdn = "mi6.co.uk";
config.homeSp.roamingConsortiumOIs = new long[] {0x112233L, 0x445566L};
+ config.homeSp.iconUrl = "icon.test.com";
+ config.homeSp.homeNetworkIds = new HashMap<>();
+ config.homeSp.homeNetworkIds.put("TestSSID", 0x12345678L);
+ config.homeSp.homeNetworkIds.put("NullHESSID", null);
+ config.homeSp.matchAllOIs = new long[] {0x11223344};
+ config.homeSp.matchAnyOIs = new long[] {0x55667788};
+ config.homeSp.otherHomePartners = new String[] {"other.fqdn.com"};
// Credential configuration.
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
config.credential = new Credential();
+ config.credential.creationTimeInMs = format.parse("2016-01-01T10:00:00Z").getTime();
+ config.credential.expirationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
config.credential.realm = "shaken.stirred.com";
+ config.credential.checkAAAServerCertStatus = true;
config.credential.userCredential = new Credential.UserCredential();
config.credential.userCredential.username = "james";
config.credential.userCredential.password = "Ym9uZDAwNw==";
+ config.credential.userCredential.machineManaged = true;
+ config.credential.userCredential.softTokenApp = "TestApp";
+ config.credential.userCredential.ableToShare = true;
config.credential.userCredential.eapType = 21;
config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2";
config.credential.certCredential = new Credential.CertificateCredential();
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 9c8b749..f571c7fc 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -37,6 +37,17 @@
*/
@SmallTest
public class CredentialTest {
+ /**
+ * Helper function for generating Credential for testing.
+ *
+ * @param userCred Instance of UserCredential
+ * @param certCred Instance of CertificateCredential
+ * @param simCred Instance of SimCredential
+ * @param caCert CA certificate
+ * @param clientCertificateChain Chain of client certificates
+ * @param clientPrivateKey Client private key
+ * @return {@link Credential}
+ */
private static Credential createCredential(Credential.UserCredential userCred,
Credential.CertificateCredential certCred,
Credential.SimCredential simCred,
@@ -44,7 +55,10 @@
X509Certificate[] clientCertificateChain,
PrivateKey clientPrivateKey) {
Credential cred = new Credential();
+ cred.creationTimeInMs = 123455L;
+ cred.expirationTimeInMs = 2310093L;
cred.realm = "realm";
+ cred.checkAAAServerCertStatus = true;
cred.userCredential = userCred;
cred.certCredential = certCred;
cred.simCredential = simCred;
@@ -54,6 +68,11 @@
return cred;
}
+ /**
+ * Helper function for generating certificate credential for testing.
+ *
+ * @return {@link Credential}
+ */
private static Credential createCredentialWithCertificateCredential() {
Credential.CertificateCredential certCred = new Credential.CertificateCredential();
certCred.certType = "x509v3";
@@ -62,6 +81,11 @@
new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
}
+ /**
+ * Helper function for generating SIM credential for testing.
+ *
+ * @return {@link Credential}
+ */
private static Credential createCredentialWithSimCredential() {
Credential.SimCredential simCred = new Credential.SimCredential();
simCred.imsi = "1234*";
@@ -69,10 +93,18 @@
return createCredential(null, null, simCred, null, null, null);
}
+ /**
+ * Helper function for generating user credential for testing.
+ *
+ * @return {@link Credential}
+ */
private static Credential createCredentialWithUserCredential() {
Credential.UserCredential userCred = new Credential.UserCredential();
userCred.username = "username";
userCred.password = "password";
+ userCred.machineManaged = true;
+ userCred.ableToShare = true;
+ userCred.softTokenApp = "TestApp";
userCred.eapType = EAPConstants.EAP_TTLS;
userCred.nonEapInnerMethod = "MS-CHAP";
return createCredential(userCred, null, null, FakeKeys.CA_CERT0,
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
index c707993..45fdbea 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
@@ -24,19 +24,71 @@
import org.junit.Test;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Unit tests for {@link android.net.wifi.hotspot2.pps.HomeSP}.
*/
@SmallTest
public class HomeSPTest {
- private static HomeSP createHomeSp() {
+
+ /**
+ * Helper function for creating a map of home network IDs for testing.
+ *
+ * @return Map of home network IDs
+ */
+ private static Map<String, Long> createHomeNetworkIds() {
+ Map<String, Long> homeNetworkIds = new HashMap<>();
+ homeNetworkIds.put("ssid", 0x1234L);
+ homeNetworkIds.put("nullhessid", null);
+ return homeNetworkIds;
+ }
+
+ /**
+ * Helper function for creating a HomeSP for testing.
+ *
+ * @param homeNetworkIds The map of home network IDs associated with HomeSP
+ * @return {@link HomeSP}
+ */
+ private static HomeSP createHomeSp(Map<String, Long> homeNetworkIds) {
HomeSP homeSp = new HomeSP();
homeSp.fqdn = "fqdn";
homeSp.friendlyName = "friendly name";
+ homeSp.iconUrl = "icon.url";
+ homeSp.homeNetworkIds = homeNetworkIds;
+ homeSp.matchAllOIs = new long[] {0x11L, 0x22L};
+ homeSp.matchAnyOIs = new long[] {0x33L, 0x44L};
+ homeSp.otherHomePartners = new String[] {"partner1", "partner2"};
homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
return homeSp;
}
+ /**
+ * Helper function for creating a HomeSP with home network IDs for testing.
+ *
+ * @return {@link HomeSP}
+ */
+ private static HomeSP createHomeSpWithHomeNetworkIds() {
+ return createHomeSp(createHomeNetworkIds());
+ }
+
+ /**
+ * Helper function for creating a HomeSP without home network IDs for testing.
+ *
+ * @return {@link HomeSP}
+ */
+ private static HomeSP createHomeSpWithoutHomeNetworkIds() {
+ return createHomeSp(null);
+ }
+
+ /**
+ * Helper function for verifying HomeSP after parcel write then read.
+ * @param writeHomeSp
+ * @throws Exception
+ */
private static void verifyParcel(HomeSP writeHomeSp) throws Exception {
Parcel parcel = Parcel.obtain();
writeHomeSp.writeToParcel(parcel, 0);
@@ -57,13 +109,23 @@
}
/**
- * Verify parcel read/write for a valid HomeSP.
+ * Verify parcel read/write for a HomeSP containing Home Network IDs.
*
* @throws Exception
*/
@Test
- public void verifyParcelWithValidHomeSP() throws Exception {
- verifyParcel(createHomeSp());
+ public void verifyParcelWithHomeNetworkIds() throws Exception {
+ verifyParcel(createHomeSpWithHomeNetworkIds());
+ }
+
+ /**
+ * Verify parcel read/write for a HomeSP without Home Network IDs.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutHomeNetworkIds() throws Exception {
+ verifyParcel(createHomeSpWithoutHomeNetworkIds());
}
/**
@@ -120,6 +182,49 @@
}
/**
+ * Verify that a HomeSP is valid when the optional Home Network IDs are
+ * provided.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateHomeSpWithHomeNetworkIds() throws Exception {
+ HomeSP homeSp = createHomeSpWithHomeNetworkIds();
+ assertTrue(homeSp.validate());
+ }
+
+ /**
+ * Verify that a HomeSP is valid when the optional Home Network IDs are
+ * not provided.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateHomeSpWithoutHomeNetworkIds() throws Exception {
+ HomeSP homeSp = createHomeSpWithoutHomeNetworkIds();
+ assertTrue(homeSp.validate());
+ }
+
+ /**
+ * Verify that a HomeSP is invalid when the optional Home Network IDs
+ * contained an invalid SSID (exceeding maximum number of bytes).
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateHomeSpWithInvalidHomeNetworkIds() throws Exception {
+ HomeSP homeSp = new HomeSP();
+ homeSp.fqdn = "fqdn";
+ homeSp.friendlyName = "friendly name";
+ homeSp.homeNetworkIds = new HashMap<>();
+ byte[] rawSsidBytes = new byte[33];
+ Arrays.fill(rawSsidBytes, (byte) 'a');
+ homeSp.homeNetworkIds.put(
+ StringFactory.newStringFromBytes(rawSsidBytes, StandardCharsets.UTF_8), 0x1234L);
+ assertFalse(homeSp.validate());
+ }
+
+ /**
* Verify that copy constructor works when pass in a null source.
*
* @throws Exception
@@ -138,10 +243,7 @@
*/
@Test
public void validateCopyConstructorFromValidSource() throws Exception {
- HomeSP sourceSp = new HomeSP();
- sourceSp.fqdn = "fqdn";
- sourceSp.friendlyName = "friendlyName";
- sourceSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
+ HomeSP sourceSp = createHomeSpWithHomeNetworkIds();
HomeSP copySp = new HomeSP(sourceSp);
assertTrue(copySp.equals(sourceSp));
}