Merge "Camera2: Correct EXIF GPS timestamp"
diff --git a/Android.mk b/Android.mk
index 21bd76b..63ad83f 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 \
@@ -460,6 +464,8 @@
telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl \
telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl \
telephony/java/com/android/ims/internal/IImsService.aidl \
+ telephony/java/com/android/ims/internal/IImsServiceController.aidl \
+ telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl \
telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
telephony/java/com/android/ims/internal/IImsUt.aidl \
telephony/java/com/android/ims/internal/IImsUtListener.aidl \
@@ -535,6 +541,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/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk
index 3a7f945..200f92f 100644
--- a/apct-tests/perftests/core/Android.mk
+++ b/apct-tests/perftests/core/Android.mk
@@ -8,7 +8,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
- apct-perftests-utils
+ apct-perftests-utils \
+ legacy-android-test
LOCAL_PACKAGE_NAME := CorePerfTests
diff --git a/api/current.txt b/api/current.txt
index 927414bc..3111484 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -83,6 +83,7 @@
field public static final java.lang.String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
+ field public static final java.lang.String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final java.lang.String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
field public static final java.lang.String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
@@ -113,6 +114,7 @@
field public static final java.lang.String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
field public static final java.lang.String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
+ field public static final java.lang.String REQUEST_DELETE_PACKAGES = "android.permission.REQUEST_DELETE_PACKAGES";
field public static final java.lang.String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
@@ -201,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
@@ -361,6 +365,7 @@
field public static final int centerMedium = 16842959; // 0x10100cf
field public static final int centerX = 16843170; // 0x10101a2
field public static final int centerY = 16843171; // 0x10101a3
+ field public static final int certDigest = 16844106; // 0x101054a
field public static final int checkBoxPreferenceStyle = 16842895; // 0x101008f
field public static final int checkMark = 16843016; // 0x1010108
field public static final int checkMarkTint = 16843943; // 0x10104a7
@@ -403,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
@@ -733,6 +739,7 @@
field public static final int isScrollContainer = 16843342; // 0x101024e
field public static final int isSticky = 16843335; // 0x1010247
field public static final int isolatedProcess = 16843689; // 0x10103a9
+ field public static final int isolatedSplits = 16844109; // 0x101054d
field public static final int itemBackground = 16843056; // 0x1010130
field public static final int itemIconDisabledAlpha = 16843057; // 0x1010131
field public static final int itemPadding = 16843565; // 0x101032d
@@ -755,7 +762,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
@@ -905,7 +911,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
@@ -1053,6 +1058,7 @@
field public static final int resizeable = 16843405; // 0x101028d
field public static final int resizeableActivity = 16844022; // 0x10104f6
field public static final int resource = 16842789; // 0x1010025
+ field public static final int restartOnConfigChanges = 16844105; // 0x1010549
field public static final int restoreAnyVersion = 16843450; // 0x10102ba
field public static final deprecated int restoreNeedsApplication = 16843421; // 0x101029d
field public static final int restrictedAccountType = 16843733; // 0x10103d5
@@ -1165,6 +1171,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
@@ -1377,7 +1384,7 @@
field public static final int toYDelta = 16843209; // 0x10101c9
field public static final int toYScale = 16843205; // 0x10101c5
field public static final int toolbarStyle = 16843946; // 0x10104aa
- field public static final int tooltip = 16844084; // 0x1010534
+ field public static final int tooltipText = 16844084; // 0x1010534
field public static final int top = 16843182; // 0x10101ae
field public static final int topBright = 16842955; // 0x10100cb
field public static final int topDark = 16842951; // 0x10100c7
@@ -1436,7 +1443,7 @@
field public static final int viewportWidth = 16843778; // 0x1010402
field public static final int visibility = 16842972; // 0x10100dc
field public static final int visible = 16843156; // 0x1010194
- field public static final int visibleToEphemeral = 16844095; // 0x101053f
+ field public static final int visibleToInstantApps = 16844095; // 0x101053f
field public static final int vmSafeMode = 16843448; // 0x10102b8
field public static final int voiceIcon = 16843908; // 0x1010484
field public static final int voiceLanguage = 16843349; // 0x1010255
@@ -1813,6 +1820,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
@@ -2879,15 +2887,18 @@
public class AccountManager {
method public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle);
- method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle, int[]);
+ method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle, java.util.Map<java.lang.Integer, java.lang.Integer>);
method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean);
+ method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, java.lang.String[]);
method public java.lang.String blockingGetAuthToken(android.accounts.Account, java.lang.String, boolean) throws android.accounts.AuthenticatorException, java.io.IOException, android.accounts.OperationCanceledException;
method public void clearPassword(android.accounts.Account);
method public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(java.lang.String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public static android.accounts.AccountManager get(android.content.Context);
+ method public int getAccountVisibility(android.accounts.Account, int);
method public android.accounts.Account[] getAccounts();
+ method public java.util.Map<android.accounts.Account, java.lang.Integer> getAccountsAndVisibilityForPackage(java.lang.String, java.lang.String);
method public android.accounts.Account[] getAccountsByType(java.lang.String);
method public android.accounts.AccountManagerFuture<android.accounts.Account[]> getAccountsByTypeAndFeatures(java.lang.String, java.lang.String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler);
method public android.accounts.Account[] getAccountsByTypeForPackage(java.lang.String, java.lang.String);
@@ -2898,13 +2909,11 @@
method public android.accounts.AuthenticatorDescription[] getAuthenticatorTypes();
method public java.lang.String getPassword(android.accounts.Account);
method public java.lang.String getPreviousName(android.accounts.Account);
- method public int[] getRequestingUidsForType(java.lang.String);
+ method public java.util.Map<java.lang.Integer, java.lang.Integer> getUidsAndVisibilityForAccount(android.accounts.Account);
method public java.lang.String getUserData(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void invalidateAuthToken(java.lang.String, java.lang.String);
- method public boolean isAccountVisible(android.accounts.Account, int);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> isCredentialsUpdateSuggested(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
- method public boolean makeAccountVisible(android.accounts.Account, int);
method public static deprecated android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, java.lang.String[], boolean, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.List<android.accounts.Account>, java.lang.String[], java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public boolean notifyAccountAuthenticated(android.accounts.Account);
@@ -2912,9 +2921,9 @@
method public deprecated android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public boolean removeAccountExplicitly(android.accounts.Account);
- method public boolean removeAccountVisibility(android.accounts.Account, int);
method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener);
method public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler);
+ method public boolean setAccountVisibility(android.accounts.Account, int, int);
method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
method public void setPassword(android.accounts.Account, java.lang.String);
method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
@@ -2953,7 +2962,14 @@
field public static final java.lang.String KEY_LAST_AUTHENTICATED_TIME = "lastAuthenticatedTime";
field public static final java.lang.String KEY_PASSWORD = "password";
field public static final java.lang.String KEY_USERDATA = "userdata";
- field public static final java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
+ field public static final deprecated java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
+ field public static final int UID_KEY_DEFAULT_LEGACY_VISIBILITY = -3; // 0xfffffffd
+ field public static final int UID_KEY_DEFAULT_VISIBILITY = -2; // 0xfffffffe
+ field public static final int VISIBILITY_NOT_VISIBLE = 3; // 0x3
+ field public static final int VISIBILITY_UNDEFINED = 0; // 0x0
+ field public static final int VISIBILITY_USER_MANAGED_NOT_VISIBLE = 4; // 0x4
+ field public static final int VISIBILITY_USER_MANAGED_VISIBLE = 2; // 0x2
+ field public static final int VISIBILITY_VISIBLE = 1; // 0x1
}
public abstract interface AccountManagerCallback<V> {
@@ -3052,8 +3068,10 @@
public static abstract interface Animator.AnimatorListener {
method public abstract void onAnimationCancel(android.animation.Animator);
+ method public default void onAnimationEnd(android.animation.Animator, boolean);
method public abstract void onAnimationEnd(android.animation.Animator);
method public abstract void onAnimationRepeat(android.animation.Animator);
+ method public default void onAnimationStart(android.animation.Animator, boolean);
method public abstract void onAnimationStart(android.animation.Animator);
}
@@ -3089,6 +3107,8 @@
method public void playSequentially(java.util.List<android.animation.Animator>);
method public void playTogether(android.animation.Animator...);
method public void playTogether(java.util.Collection<android.animation.Animator>);
+ method public void reverse();
+ method public void setCurrentPlayTime(long);
method public android.animation.AnimatorSet setDuration(long);
method public void setInterpolator(android.animation.TimeInterpolator);
method public void setStartDelay(long);
@@ -3491,8 +3511,7 @@
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
- method public void enterPictureInPictureMode(float);
- method public void enterPictureInPictureModeOnMoveToBackground(boolean);
+ method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -3525,7 +3544,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();
@@ -3665,8 +3683,7 @@
method public void setIntent(android.content.Intent);
method public final void setMediaController(android.media.session.MediaController);
method public void setOverlayWithDecorCaptionEnabled(boolean);
- method public void setPictureInPictureActions(java.util.List<android.app.RemoteAction>);
- method public void setPictureInPictureAspectRatio(float);
+ method public void setPictureInPictureArgs(android.app.PictureInPictureArgs);
method public final deprecated void setProgress(int);
method public final deprecated void setProgressBarIndeterminate(boolean);
method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -3676,7 +3693,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);
@@ -4828,11 +4844,13 @@
public static class Instrumentation.ActivityMonitor {
ctor public Instrumentation.ActivityMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
ctor public Instrumentation.ActivityMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
+ ctor public Instrumentation.ActivityMonitor();
method public final android.content.IntentFilter getFilter();
method public final int getHits();
method public final android.app.Activity getLastActivity();
method public final android.app.Instrumentation.ActivityResult getResult();
method public final boolean isBlocking();
+ method public android.app.Instrumentation.ActivityResult onMatchIntent(android.content.Intent);
method public final android.app.Activity waitForActivity();
method public final android.app.Activity waitForActivityWithTimeout(long);
}
@@ -5004,6 +5022,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";
@@ -5031,8 +5050,10 @@
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+ field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+ field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
@@ -5176,7 +5197,8 @@
}
public static class Notification.Builder {
- ctor public Notification.Builder(android.content.Context);
+ ctor public Notification.Builder(android.content.Context, java.lang.String);
+ ctor public deprecated Notification.Builder(android.content.Context);
method public deprecated android.app.Notification.Builder addAction(int, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.Notification.Builder addAction(android.app.Notification.Action);
method public android.app.Notification.Builder addExtras(android.os.Bundle);
@@ -5195,6 +5217,7 @@
method public android.app.Notification.Builder setChannel(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
method public android.app.Notification.Builder setColor(int);
+ method public android.app.Notification.Builder setColorized(boolean);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
@@ -5232,6 +5255,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);
@@ -5298,9 +5322,11 @@
public static class Notification.MessagingStyle extends android.app.Notification.Style {
ctor public Notification.MessagingStyle(java.lang.CharSequence);
+ method public android.app.Notification.MessagingStyle addHistoricMessage(android.app.Notification.MessagingStyle.Message);
method public android.app.Notification.MessagingStyle addMessage(java.lang.CharSequence, long, java.lang.CharSequence);
method public android.app.Notification.MessagingStyle addMessage(android.app.Notification.MessagingStyle.Message);
method public java.lang.CharSequence getConversationTitle();
+ method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
method public java.lang.CharSequence getUserDisplayName();
method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
@@ -5395,14 +5421,15 @@
method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
+ method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
method public int getLockscreenVisibility();
method public java.lang.CharSequence getName();
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
- method public boolean isAllowed();
method public void setBypassDnd(boolean);
+ method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
@@ -5416,6 +5443,17 @@
field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
}
+ public final class NotificationChannelGroup implements android.os.Parcelable {
+ ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+ ctor protected NotificationChannelGroup(android.os.Parcel);
+ method public int describeContents();
+ method public java.util.List<android.app.NotificationChannel> getChannels();
+ method public java.lang.String getId();
+ method public java.lang.CharSequence getName();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
+ }
+
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
@@ -5423,6 +5461,8 @@
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public void createNotificationChannel(android.app.NotificationChannel);
+ method public void createNotificationChannelGroup(android.app.NotificationChannelGroup);
+ method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>);
method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>);
method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
@@ -5439,6 +5479,7 @@
method public boolean removeAutomaticZenRule(java.lang.String);
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
+ method public android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
@@ -5524,6 +5565,17 @@
method public abstract void onSendFinished(android.app.PendingIntent, android.content.Intent, int, java.lang.String, android.os.Bundle);
}
+ public final class PictureInPictureArgs implements android.os.Parcelable {
+ ctor public PictureInPictureArgs();
+ ctor public PictureInPictureArgs(float, java.util.List<android.app.RemoteAction>);
+ method public android.app.PictureInPictureArgs clone();
+ method public int describeContents();
+ method public void setActions(java.util.List<android.app.RemoteAction>);
+ method public void setAspectRatio(float);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.PictureInPictureArgs> CREATOR;
+ }
+
public class Presentation extends android.app.Dialog {
ctor public Presentation(android.content.Context, android.view.Display);
ctor public Presentation(android.content.Context, android.view.Display, int);
@@ -5560,6 +5612,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();
@@ -6077,6 +6141,7 @@
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+ method public android.content.Intent createAdminSupportIntent(java.lang.String);
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
method public void enableSystemApp(android.content.ComponentName, java.lang.String);
method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
@@ -6085,16 +6150,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);
@@ -6138,7 +6205,8 @@
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
- method public boolean isCallerApplicationRestrictionsManagingPackage();
+ method public boolean isBackupServiceEnabled(android.content.ComponentName);
+ 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);
@@ -6166,13 +6234,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);
@@ -6220,6 +6290,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";
@@ -6229,6 +6300,12 @@
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_BLOCK_UNINSTALL = "delegation-block-uninstall";
+ field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+ field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+ field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+ field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
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
@@ -6236,6 +6313,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";
@@ -6294,6 +6372,8 @@
field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+ field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+ field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -6341,8 +6421,12 @@
public final class SystemUpdateInfo implements android.os.Parcelable {
method public int describeContents();
method public long getReceivedTime();
+ method public int getSecurityPatchState();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdateInfo> CREATOR;
+ field public static final int SECURITY_PATCH_STATE_FALSE = 1; // 0x1
+ field public static final int SECURITY_PATCH_STATE_TRUE = 2; // 0x2
+ field public static final int SECURITY_PATCH_STATE_UNKNOWN = 0; // 0x0
}
public class SystemUpdatePolicy implements android.os.Parcelable {
@@ -6827,6 +6911,7 @@
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
+ method public boolean isRequestPinAppWidgetSupported();
method public void notifyAppWidgetViewDataChanged(int[], int);
method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
@@ -7928,6 +8013,7 @@
ctor public ClipData(android.content.ClipDescription, android.content.ClipData.Item);
ctor public ClipData(android.content.ClipData);
method public void addItem(android.content.ClipData.Item);
+ method public void addItem(android.content.ClipData.Item, android.content.ContentResolver);
method public int describeContents();
method public android.content.ClipDescription getDescription();
method public android.content.ClipData.Item getItemAt(int);
@@ -8313,6 +8399,7 @@
method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public abstract deprecated void clearWallpaper() throws java.io.IOException;
method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+ method public abstract android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.Context createDeviceProtectedStorageContext();
method public abstract android.content.Context createDisplayContext(android.view.Display);
method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8446,6 +8533,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";
@@ -8510,6 +8598,7 @@
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public deprecated void clearWallpaper() throws java.io.IOException;
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+ method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.Context createDeviceProtectedStorageContext();
method public android.content.Context createDisplayContext(android.view.Display);
method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8818,6 +8907,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";
@@ -9532,6 +9622,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 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
@@ -9592,6 +9686,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;
@@ -9688,6 +9783,7 @@
field public int requiresSmallestWidthDp;
field public java.lang.String[] sharedLibraryFiles;
field public java.lang.String sourceDir;
+ field public java.lang.String[] splitNames;
field public java.lang.String[] splitPublicSourceDirs;
field public java.lang.String[] splitSourceDirs;
field public int targetSdkVersion;
@@ -9695,6 +9791,7 @@
field public int theme;
field public int uiOptions;
field public int uid;
+ field public java.lang.String volumeUuid;
}
public static class ApplicationInfo.DisplayNameComparator implements java.util.Comparator {
@@ -9716,6 +9813,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 {
@@ -9769,6 +9867,7 @@
field public boolean handleProfiling;
field public java.lang.String publicSourceDir;
field public java.lang.String sourceDir;
+ field public java.lang.String[] splitNames;
field public java.lang.String[] splitPublicSourceDirs;
field public java.lang.String[] splitSourceDirs;
field public java.lang.String targetPackage;
@@ -9803,6 +9902,7 @@
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
+ method public java.util.List<android.os.UserHandle> getProfiles();
method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
method public android.content.IntentSender getShortcutConfigActivityIntent(android.content.pm.LauncherActivityInfo);
method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(java.lang.String, android.os.UserHandle);
@@ -9820,7 +9920,8 @@
method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
- field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM";
+ field public static final java.lang.String ACTION_CONFIRM_PIN_APPWIDGET = "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
+ field public static final java.lang.String ACTION_CONFIRM_PIN_SHORTCUT = "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}
@@ -9840,7 +9941,7 @@
method public boolean accept(android.os.Bundle);
method public boolean accept();
method public int describeContents();
- method public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo();
+ method public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo(android.content.Context);
method public int getRequestType();
method public android.content.pm.ShortcutInfo getShortcutInfo();
method public boolean isValid();
@@ -9909,6 +10010,7 @@
method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler);
method public void uninstall(java.lang.String, android.content.IntentSender);
+ method public void uninstall(android.content.pm.VersionedPackage, android.content.IntentSender);
method public void unregisterSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
method public void updateSessionAppIcon(int, android.graphics.Bitmap);
method public void updateSessionAppLabel(int, java.lang.CharSequence);
@@ -10017,6 +10119,7 @@
method public abstract boolean addPermission(android.content.pm.PermissionInfo);
method public abstract boolean addPermissionAsync(android.content.pm.PermissionInfo);
method public abstract deprecated void addPreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
+ method public abstract boolean canRequestPackageInstalls();
method public abstract java.lang.String[] canonicalToCurrentPackageNames(java.lang.String[]);
method public abstract int checkPermission(java.lang.String, java.lang.String);
method public abstract int checkSignatures(java.lang.String, java.lang.String);
@@ -10055,6 +10158,7 @@
method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.PackageInstaller getPackageInstaller();
method public abstract int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract java.lang.String[] getPackagesForUid(int);
@@ -10069,6 +10173,7 @@
method public abstract android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
method public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public abstract java.lang.String[] getSystemSharedLibraryNames();
method public abstract java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
@@ -10127,6 +10232,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";
@@ -10227,6 +10333,7 @@
field public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; // 0xfffffffc
field public static final int VERIFICATION_ALLOW = 1; // 0x1
field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
+ field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
}
public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
@@ -10366,6 +10473,20 @@
field public java.lang.String permission;
}
+ public final class SharedLibraryInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.content.pm.VersionedPackage getDeclaringPackage();
+ method public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
+ method public java.lang.String getName();
+ method public int getVersion();
+ method public boolean isBuiltin();
+ method public boolean isDynamic();
+ method public boolean isStatic();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
+ field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
+ }
+
public final class ShortcutInfo implements android.os.Parcelable {
method public int describeContents();
method public android.content.ComponentName getActivity();
@@ -10441,6 +10562,15 @@
field public static final android.os.Parcelable.Creator<android.content.pm.Signature> CREATOR;
}
+ public final class VersionedPackage implements android.os.Parcelable {
+ ctor public VersionedPackage(java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getPackageName();
+ method public long getVersionCode();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.VersionedPackage> CREATOR;
+ }
+
}
package android.content.res {
@@ -10526,7 +10656,9 @@
method public int getLayoutDirection();
method public android.os.LocaleList getLocales();
method public boolean isLayoutSizeAtLeast(int);
+ method public boolean isScreenHdr();
method public boolean isScreenRound();
+ method public boolean isScreenWideColorGamut();
method public static boolean needNewResources(int, int);
method public void readFromParcel(android.os.Parcel);
method public void setLayoutDirection(java.util.Locale);
@@ -10536,6 +10668,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 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
@@ -10601,6 +10743,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 colorMode;
field public int densityDpi;
field public float fontScale;
field public int hardKeyboardHidden;
@@ -10655,6 +10798,7 @@
method public android.graphics.drawable.Drawable getDrawable(int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
method public deprecated android.graphics.drawable.Drawable getDrawableForDensity(int, int) throws android.content.res.Resources.NotFoundException;
method public android.graphics.drawable.Drawable getDrawableForDensity(int, int, android.content.res.Resources.Theme);
+ method public android.graphics.Typeface getFont(int) throws android.content.res.Resources.NotFoundException;
method public float getFraction(int, int, int);
method public int getIdentifier(java.lang.String, java.lang.String, java.lang.String);
method public int[] getIntArray(int) throws android.content.res.Resources.NotFoundException;
@@ -12586,7 +12730,6 @@
method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt);
method public android.graphics.Paint.FontMetricsInt getFontMetricsInt();
method public float getFontSpacing();
- method public java.lang.String getFontVariationSettings();
method public int getHinting();
method public float getLetterSpacing();
method public android.graphics.MaskFilter getMaskFilter();
@@ -12644,7 +12787,6 @@
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
method public void setHinting(int);
method public void setLetterSpacing(float);
method public void setLinearText(boolean);
@@ -13142,6 +13284,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);
@@ -13162,6 +13305,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();
}
@@ -13568,6 +13719,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);
@@ -13716,6 +13884,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 {
@@ -14043,9 +14224,42 @@
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 int BLOB = 33; // 0x21
+ 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 = 22; // 0x16
+ 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();
@@ -14059,6 +14273,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
@@ -14136,6 +14351,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;
@@ -14167,6 +14393,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[]);
@@ -14296,7 +14525,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();
@@ -14711,6 +14940,7 @@
field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -14790,6 +15020,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_STATE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -14937,10 +15168,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
@@ -15013,6 +15246,7 @@
field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
+ field public static final int VIRTUAL_DISPLAY_FLAG_SHOW_WITH_INSECURE_LOCKSCREEN = 32; // 0x20
}
public static abstract interface DisplayManager.DisplayListener {
@@ -17490,6 +17724,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();
@@ -17499,6 +17742,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);
@@ -17518,9 +17763,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);
@@ -17989,6 +18244,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();
@@ -18764,6 +19027,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();
@@ -19292,6 +19583,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();
@@ -20090,7 +20410,7 @@
field public static final android.os.Parcelable.Creator<android.media.AudioAttributes> CREATOR;
field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
- field public static final int FLAG_LOW_LATENCY = 256; // 0x100
+ field public static final deprecated int FLAG_LOW_LATENCY = 256; // 0x100
field public static final int USAGE_ALARM = 4; // 0x4
field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
@@ -20531,6 +20851,7 @@
method protected deprecated int getNativeFrameCount();
method public static int getNativeOutputSampleRate(int);
method public int getNotificationMarkerPosition();
+ method public int getPerformanceMode();
method public int getPlayState();
method public int getPlaybackHeadPosition();
method public android.media.PlaybackParams getPlaybackParams();
@@ -20577,6 +20898,9 @@
field public static final int ERROR_INVALID_OPERATION = -3; // 0xfffffffd
field public static final int MODE_STATIC = 0; // 0x0
field public static final int MODE_STREAM = 1; // 0x1
+ field public static final int PERFORMANCE_MODE_LOW_LATENCY = 1; // 0x1
+ field public static final int PERFORMANCE_MODE_NONE = 0; // 0x0
+ field public static final int PERFORMANCE_MODE_POWER_SAVING = 2; // 0x2
field public static final int PLAYSTATE_PAUSED = 2; // 0x2
field public static final int PLAYSTATE_PLAYING = 3; // 0x3
field public static final int PLAYSTATE_STOPPED = 1; // 0x1
@@ -20594,6 +20918,7 @@
method public android.media.AudioTrack.Builder setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack.Builder setAudioFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
+ method public android.media.AudioTrack.Builder setPerformanceMode(int);
method public android.media.AudioTrack.Builder setSessionId(int) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack.Builder setTransferMode(int) throws java.lang.IllegalArgumentException;
}
@@ -20608,6 +20933,40 @@
method public default void onRoutingChanged(android.media.AudioRouting);
}
+ public final class BufferingParams implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getInitialBufferingMode();
+ method public int getInitialBufferingWatermarkKB();
+ method public int getInitialBufferingWatermarkMs();
+ method public int getRebufferingMode();
+ method public int getRebufferingWatermarkHighKB();
+ method public int getRebufferingWatermarkHighMs();
+ method public int getRebufferingWatermarkLowKB();
+ method public int getRebufferingWatermarkLowMs();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFERING_MODE_NONE = 0; // 0x0
+ field public static final int BUFFERING_MODE_SIZE_ONLY = 2; // 0x2
+ field public static final int BUFFERING_MODE_TIME_ONLY = 1; // 0x1
+ field public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3; // 0x3
+ field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+ }
+
+ public static class BufferingParams.Builder {
+ ctor public BufferingParams.Builder();
+ ctor public BufferingParams.Builder(android.media.BufferingParams);
+ method public android.media.BufferingParams build();
+ method public android.media.BufferingParams.Builder setInitialBufferingMode(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkKB(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingMode(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksKB(int, int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksMs(int, int);
+ }
+
public class CamcorderProfile {
method public static android.media.CamcorderProfile get(int);
method public static android.media.CamcorderProfile get(int, int);
@@ -21752,6 +22111,7 @@
public final class MediaMuxer {
ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException;
+ ctor public MediaMuxer(java.io.FileDescriptor, int) throws java.io.IOException;
method public int addTrack(android.media.MediaFormat);
method public void release();
method public void setLocation(float, float);
@@ -21780,7 +22140,9 @@
method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
method public void deselectTrack(int) throws java.lang.IllegalStateException;
method public int getAudioSessionId();
+ method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
+ method public android.media.BufferingParams getDefaultBufferingParams();
method public int getDuration();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -21803,6 +22165,7 @@
method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public deprecated void setAudioStreamType(int);
method public void setAuxEffectSendLevel(float);
+ method public void setBufferingParams(android.media.BufferingParams);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
@@ -21928,6 +22291,8 @@
method public void setLocation(float, float);
method public void setMaxDuration(int) throws java.lang.IllegalArgumentException;
method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException;
+ method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalStateException;
+ method public void setNextOutputFile(java.lang.String) throws java.io.IOException, java.lang.IllegalStateException;
method public void setOnErrorListener(android.media.MediaRecorder.OnErrorListener);
method public void setOnInfoListener(android.media.MediaRecorder.OnInfoListener);
method public void setOrientationHint(int);
@@ -21946,7 +22311,9 @@
field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64
field public static final int MEDIA_RECORDER_ERROR_UNKNOWN = 1; // 0x1
field public static final int MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800; // 0x320
+ field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING = 802; // 0x322
field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801; // 0x321
+ field public static final int MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED = 803; // 0x323
field public static final int MEDIA_RECORDER_INFO_UNKNOWN = 1; // 0x1
}
@@ -23461,6 +23828,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";
@@ -23487,7 +23855,16 @@
}
public static final class TvContract.Programs implements android.media.tv.TvContract.BaseTvColumns {
+ field public static final java.lang.String ASPECT_RATIO_16_9 = "ASPECT_RATIO_16_9";
+ field public static final java.lang.String ASPECT_RATIO_1_1 = "ASPECT_RATIO_1_1";
+ field public static final java.lang.String ASPECT_RATIO_2_3 = "ASPECT_RATIO_2_3";
+ field public static final java.lang.String ASPECT_RATIO_3_2 = "ASPECT_RATIO_3_2";
+ field public static final java.lang.String AVAILABILITY_AVAILABLE = "AVAILABILITY_AVAILABLE";
+ field public static final java.lang.String AVAILABILITY_FREE_WITH_SUBSCRIPTION = "AVAILABILITY_FREE_WITH_SUBSCRIPTION";
+ field public static final java.lang.String AVAILABILITY_PAID_CONTENT = "AVAILABILITY_PAID_CONTENT";
field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language";
+ field public static final java.lang.String COLUMN_AUTHOR = "author";
+ field public static final java.lang.String COLUMN_AVAILABILITY = "availability";
field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre";
field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id";
@@ -23496,28 +23873,73 @@
field public static final java.lang.String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
field public static final deprecated java.lang.String COLUMN_EPISODE_NUMBER = "episode_number";
field public static final java.lang.String COLUMN_EPISODE_TITLE = "episode_title";
+ field public static final java.lang.String COLUMN_INTERACTION_COUNT = "interaction_count";
+ field public static final java.lang.String COLUMN_INTERACTION_TYPE = "interaction_type";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
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_ITEM_COUNT = "item_count";
+ field public static final java.lang.String COLUMN_LIVE = "live";
+ field public static final java.lang.String COLUMN_LOGO = "logo";
field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
+ field public static final java.lang.String COLUMN_OFFER_PRICE = "offer_price";
+ field public static final java.lang.String COLUMN_POSTER_ART_ASPECT_RATIO = "poster_art_aspect_ratio";
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_RELEASE_DATE = "release_date";
+ field public static final java.lang.String COLUMN_REVIEW_RATING = "review_rating";
+ field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
field public static final deprecated java.lang.String COLUMN_SEASON_NUMBER = "season_number";
field public static final java.lang.String COLUMN_SEASON_TITLE = "season_title";
field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description";
+ field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
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_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
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_TYPE = "type";
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";
+ field public static final java.lang.String COLUMN_WATCH_NEXT_TYPE = "watch_next_type";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/program";
field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String INTERACTION_TYPE_FANS = "INTERACTION_TYPE_FANS";
+ field public static final java.lang.String INTERACTION_TYPE_FOLLOWERS = "INTERACTION_TYPE_FOLLOWERS";
+ field public static final java.lang.String INTERACTION_TYPE_LIKES = "INTERACTION_TYPE_LIKES";
+ field public static final java.lang.String INTERACTION_TYPE_LISTENS = "INTERACTION_TYPE_LISTENS";
+ field public static final java.lang.String INTERACTION_TYPE_THUMBS = "INTERACTION_TYPE_THUMBS";
+ field public static final java.lang.String INTERACTION_TYPE_VIEWERS = "INTERACTION_TYPE_VIEWERS";
+ field public static final java.lang.String INTERACTION_TYPE_VIEWS = "INTERACTION_TYPE_VIEWS";
+ field public static final java.lang.String REVIEW_RATING_STYLE_PERCENTAGE = "REVIEW_RATING_STYLE_PERCENTAGE";
+ field public static final java.lang.String REVIEW_RATING_STYLE_STARS = "REVIEW_RATING_STYLE_STARS";
+ field public static final java.lang.String REVIEW_RATING_STYLE_THUMBS_UP_DOWN = "REVIEW_RATING_STYLE_THUMBS_UP_DOWN";
+ field public static final java.lang.String TYPE_ALBUM = "TYPE_ALBUM";
+ field public static final java.lang.String TYPE_ARTIST = "TYPE_ARTIST";
+ field public static final java.lang.String TYPE_CHANNEL = "TYPE_CHANNEL";
+ field public static final java.lang.String TYPE_CLIP = "TYPE_CLIP";
+ field public static final java.lang.String TYPE_EVENT = "TYPE_EVENT";
+ field public static final java.lang.String TYPE_MOVIE = "TYPE_MOVIE";
+ field public static final java.lang.String TYPE_PLAYLIST = "TYPE_PLAYLIST";
+ field public static final java.lang.String TYPE_STATION = "TYPE_STATION";
+ field public static final java.lang.String TYPE_TRACK = "TYPE_TRACK";
+ field public static final java.lang.String TYPE_TV_EPISODE = "TYPE_TV_EPISODE";
+ field public static final java.lang.String TYPE_TV_SEASON = "TYPE_TV_SEASON";
+ field public static final java.lang.String TYPE_TV_SERIES = "TYPE_TV_SERIES";
+ field public static final java.lang.String WATCH_NEXT_TYPE_CONTINUE = "WATCH_NEXT_TYPE_CONTINUE";
+ field public static final java.lang.String WATCH_NEXT_TYPE_NEW = "WATCH_NEXT_TYPE_NEW";
+ field public static final java.lang.String WATCH_NEXT_TYPE_NEXT = "WATCH_NEXT_TYPE_NEXT";
}
public static final class TvContract.Programs.Genres {
@@ -23628,9 +24050,13 @@
method public void unregisterCallback(android.media.tv.TvInputManager.TvInputCallback);
method public void updateTvInputInfo(android.media.tv.TvInputInfo);
field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
+ field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
+ field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
+ field public static final java.lang.String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID";
+ field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME";
field public static final 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
@@ -24468,6 +24894,7 @@
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
+ method public static int getAndSetThreadStatsTag(int);
method public static long getMobileRxBytes();
method public static long getMobileRxPackets();
method public static long getMobileTxBytes();
@@ -25158,6 +25585,7 @@
method public java.security.cert.X509Certificate getCaCertificate();
method public java.security.cert.X509Certificate[] getCaCertificates();
method public java.security.cert.X509Certificate getClientCertificate();
+ method public java.security.cert.X509Certificate[] getClientCertificateChain();
method public java.lang.String getDomainSuffixMatch();
method public int getEapMethod();
method public java.lang.String getIdentity();
@@ -25171,6 +25599,7 @@
method public void setCaCertificate(java.security.cert.X509Certificate);
method public void setCaCertificates(java.security.cert.X509Certificate[]);
method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+ method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
method public void setDomainSuffixMatch(java.lang.String);
method public void setEapMethod(int);
method public void setIdentity(java.lang.String);
@@ -25246,6 +25675,7 @@
method public boolean isTdlsSupported();
method public boolean isWifiEnabled();
method public boolean pingSupplicant();
+ method public void queryPasspointIcon(long, java.lang.String);
method public boolean reassociate();
method public boolean reconnect();
method public boolean removeNetwork(int);
@@ -25256,6 +25686,10 @@
method public boolean startScan();
method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
method public int updateNetwork(android.net.wifi.WifiConfiguration);
+ field public static final java.lang.String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+ field public static final java.lang.String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+ field public static final java.lang.String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+ field public static final java.lang.String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final int ERROR_AUTHENTICATING = 1; // 0x1
@@ -25263,6 +25697,18 @@
field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
field public static final java.lang.String EXTRA_NEW_STATE = "newState";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated";
field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected";
@@ -29689,6 +30135,7 @@
method public final android.util.SizeF readSizeF();
method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
method public final android.util.SparseBooleanArray readSparseBooleanArray();
+ method public final android.util.SparseIntArray readSparseIntArray();
method public final java.lang.String readString();
method public final void readStringArray(java.lang.String[]);
method public final void readStringList(java.util.List<java.lang.String>);
@@ -29733,6 +30180,7 @@
method public final void writeSizeF(android.util.SizeF);
method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
+ method public final void writeSparseIntArray(android.util.SparseIntArray);
method public final void writeString(java.lang.String);
method public final void writeStringArray(java.lang.String[]);
method public final void writeStringList(java.util.List<java.lang.String>);
@@ -30049,6 +30497,7 @@
method public android.os.StrictMode.VmPolicy.Builder detectLeakedClosableObjects();
method public android.os.StrictMode.VmPolicy.Builder detectLeakedRegistrationObjects();
method public android.os.StrictMode.VmPolicy.Builder detectLeakedSqlLiteObjects();
+ method public android.os.StrictMode.VmPolicy.Builder detectUntaggedSockets();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeath();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
@@ -30345,14 +30794,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";
}
@@ -30480,6 +30937,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);
@@ -32808,7 +33266,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;
@@ -32822,6 +33279,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";
@@ -33187,6 +33654,7 @@
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+ field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
@@ -33204,6 +33672,7 @@
field public static final java.lang.String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
field public static final java.lang.String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
field public static final java.lang.String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
+ field public static final java.lang.String ACTION_MANAGE_EXTERNAL_SOURCES = "android.settings.action.MANAGE_EXTERNAL_SOURCES";
field public static final java.lang.String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
field public static final java.lang.String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
field public static final java.lang.String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
@@ -33239,8 +33708,10 @@
field public static final java.lang.String AUTHORITY = "settings";
field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
field public static final java.lang.String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+ field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -33349,7 +33820,7 @@
field public static final java.lang.String ENABLED_INPUT_METHODS = "enabled_input_methods";
field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility";
- field public static final java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+ field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
field public static final java.lang.String LOCATION_MODE = "location_mode";
field public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2
field public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3
@@ -33904,6 +34375,8 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
+ field public static final java.lang.String ARCHIVED = "archived";
+ field public static final java.lang.String BACKED_UP = "backed_up";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -33911,6 +34384,7 @@
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
+ field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
field public static final java.lang.String IS_READ = "is_read";
field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -33918,6 +34392,7 @@
field public static final java.lang.String NUMBER = "number";
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String RESTORED = "restored";
field public static final java.lang.String SOURCE_DATA = "source_data";
field public static final java.lang.String SOURCE_PACKAGE = "source_package";
field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -35241,6 +35716,7 @@
field public static final int PURPOSE_ENCRYPT = 1; // 0x1
field public static final int PURPOSE_SIGN = 4; // 0x4
field public static final int PURPOSE_VERIFY = 8; // 0x8
+ field public static final int PURPOSE_WRAP_KEY = 16; // 0x10
field public static final java.lang.String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
field public static final java.lang.String SIGNATURE_PADDING_RSA_PSS = "PSS";
}
@@ -35294,17 +35770,25 @@
ctor public AutoFillService();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
+ method public void onDatasetAuthenticationRequest(android.os.Bundle, int);
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
- method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+ method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
+ method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
+ field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
+ field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1
+ field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2
+ field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
public final class FillCallback {
+ method public void onDatasetAuthentication(android.view.autofill.Dataset, int);
method public void onFailure(java.lang.CharSequence);
+ method public void onFillResponseAuthentication(int);
method public void onSuccess(android.view.autofill.FillResponse);
}
@@ -35589,6 +36073,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";
}
@@ -35604,6 +36089,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();
@@ -35622,8 +36108,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
@@ -35648,9 +36132,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
@@ -35658,6 +36142,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();
@@ -35699,7 +36184,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();
@@ -36115,6 +36599,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);
}
@@ -36267,6 +36752,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 {
@@ -37179,6 +37665,7 @@
method public void onReject();
method public void onReject(java.lang.String);
method public void onSeparate();
+ method public void onShowIncomingCallUi();
method public void onStateChanged(int);
method public void onStopDtmfTone();
method public void onUnhold();
@@ -37190,6 +37677,7 @@
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
method public final void setAudioModeIsVoip(boolean);
+ method public final void setAudioRoute(int);
method public final void setCallerDisplayName(java.lang.String, int);
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
@@ -37238,6 +37726,7 @@
field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
+ field public static final int PROPERTY_SELF_MANAGED = 128; // 0x80
field public static final int STATE_ACTIVE = 4; // 0x4
field public static final int STATE_DIALING = 3; // 0x3
field public static final int STATE_DISCONNECTED = 6; // 0x6
@@ -37305,7 +37794,9 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConference(android.telecom.Connection, android.telecom.Connection);
method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest);
method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest);
method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
@@ -37418,6 +37909,7 @@
field public static final int CAPABILITY_CALL_SUBJECT = 64; // 0x40
field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
+ field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
@@ -37599,6 +38091,8 @@
method public boolean handleMmi(java.lang.String);
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
+ method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
+ method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
method public void placeCall(android.net.Uri, android.os.Bundle);
method public void registerPhoneAccount(android.telecom.PhoneAccount);
@@ -37609,7 +38103,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";
@@ -37712,7 +38206,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";
@@ -37723,6 +38218,7 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
+ field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
@@ -37804,9 +38300,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";
}
@@ -38334,6 +38834,7 @@
method public int getSimState();
method public int getSimState(int);
method public java.lang.String getSubscriberId();
+ method public java.lang.String getVisualVoicemailPackageName(android.telecom.PhoneAccountHandle);
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
method public int getVoiceNetworkType();
@@ -38345,6 +38846,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();
@@ -38385,6 +38887,7 @@
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
+ field public static final java.lang.String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
@@ -38393,6 +38896,7 @@
field public static final java.lang.String EXTRA_STATE_OFFHOOK;
field public static final java.lang.String EXTRA_STATE_RINGING;
field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
+ field public static final java.lang.String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -38938,6 +39442,7 @@
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public void clearWallpaper();
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+ method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.Context createDeviceProtectedStorageContext();
method public android.content.Context createDisplayContext(android.view.Display);
method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -39022,6 +39527,7 @@
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public android.content.ComponentName startService(android.content.Intent);
+ method public android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
@@ -39084,6 +39590,7 @@
method public boolean addPermission(android.content.pm.PermissionInfo);
method public boolean addPermissionAsync(android.content.pm.PermissionInfo);
method public void addPreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
+ method public boolean canRequestPackageInstalls();
method public java.lang.String[] canonicalToCurrentPackageNames(java.lang.String[]);
method public int checkPermission(java.lang.String, java.lang.String);
method public int checkSignatures(java.lang.String, java.lang.String);
@@ -39122,6 +39629,7 @@
method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInstaller getPackageInstaller();
method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.lang.String[] getPackagesForUid(int);
@@ -39136,6 +39644,7 @@
method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public java.lang.String[] getSystemSharedLibraryNames();
method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
@@ -39365,6 +39874,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);
}
@@ -39720,22 +40288,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);
@@ -39751,13 +40303,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);
@@ -39770,13 +40315,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...);
@@ -42030,12 +42568,15 @@
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
field public static final int FLAG_ROUND = 16; // 0x10
field public static final int FLAG_SECURE = 2; // 0x2
+ field public static final int FLAG_SHOW_WITH_INSECURE_LOCKSCREEN = 32; // 0x20
field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
field public static final int INVALID_DISPLAY = -1; // 0xffffffff
field public static final int STATE_DOZE = 3; // 0x3
@@ -42099,7 +42640,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();
}
@@ -42282,6 +42823,7 @@
field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
field public static final int SOURCE_KEYBOARD = 257; // 0x101
field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -42884,7 +43426,7 @@
method public abstract android.view.SubMenu getSubMenu();
method public abstract java.lang.CharSequence getTitle();
method public abstract java.lang.CharSequence getTitleCondensed();
- method public default java.lang.CharSequence getTooltip();
+ method public default java.lang.CharSequence getTooltipText();
method public abstract boolean hasSubMenu();
method public abstract boolean isActionViewExpanded();
method public abstract boolean isCheckable();
@@ -42911,7 +43453,7 @@
method public abstract android.view.MenuItem setTitle(java.lang.CharSequence);
method public abstract android.view.MenuItem setTitle(int);
method public abstract android.view.MenuItem setTitleCondensed(java.lang.CharSequence);
- method public default android.view.MenuItem setTooltip(java.lang.CharSequence);
+ method public default android.view.MenuItem setTooltipText(java.lang.CharSequence);
method public abstract android.view.MenuItem setVisible(boolean);
field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
@@ -43397,7 +43939,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>);
@@ -43436,6 +43978,7 @@
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
+ method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
method public boolean dispatchDragEvent(android.view.DragEvent);
@@ -43454,6 +43997,7 @@
method public boolean dispatchNestedPrePerformAccessibilityAction(int, android.os.Bundle);
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
+ method public void dispatchPointerCaptureChanged(boolean);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void dispatchProvideAutoFillStructure(android.view.ViewStructure, int);
method public void dispatchProvideStructure(android.view.ViewStructure);
@@ -43519,6 +44063,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();
@@ -43561,7 +44106,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();
@@ -43605,7 +44149,7 @@
method public java.lang.Object getTag(int);
method public int getTextAlignment();
method public int getTextDirection();
- method public final java.lang.CharSequence getTooltip();
+ method public final java.lang.CharSequence getTooltipText();
method public final int getTop();
method protected float getTopFadingEdgeStrength();
method protected int getTopPaddingOffset();
@@ -43635,6 +44179,7 @@
method public boolean hasNestedScrollingParent();
method public boolean hasOnClickListeners();
method public boolean hasOverlappingRendering();
+ method public boolean hasPointerCapture();
method public boolean hasTransientState();
method public boolean hasWindowFocus();
method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup);
@@ -43666,7 +44211,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();
@@ -43689,7 +44233,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[]);
@@ -43700,6 +44244,7 @@
method public android.view.WindowInsets onApplyWindowInsets(android.view.WindowInsets);
method protected void onAttachedToWindow();
method public void onCancelPendingInputEvents();
+ method public boolean onCapturedPointerEvent(android.view.MotionEvent);
method public boolean onCheckIsTextEditor();
method protected void onConfigurationChanged(android.content.res.Configuration);
method protected void onCreateContextMenu(android.view.ContextMenu);
@@ -43729,6 +44274,7 @@
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
+ method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void onProvideAutoFillStructure(android.view.ViewStructure, int);
method public void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
@@ -43771,6 +44317,7 @@
method public void postOnAnimation(java.lang.Runnable);
method public void postOnAnimationDelayed(java.lang.Runnable, long);
method public void refreshDrawableState();
+ method public void releasePointerCapture();
method public boolean removeCallbacks(java.lang.Runnable);
method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
@@ -43781,6 +44328,7 @@
method public boolean requestFocus(int, android.graphics.Rect);
method public final boolean requestFocusFromTouch();
method public void requestLayout();
+ method public void requestPointerCapture();
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
method public final void requestUnbufferedDispatch(android.view.MotionEvent);
@@ -43824,6 +44372,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);
@@ -43839,7 +44388,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);
@@ -43857,8 +44405,8 @@
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 setOnCapturedPointerListener(android.view.View.OnCapturedPointerListener);
method public void setOnClickListener(android.view.View.OnClickListener);
method public void setOnContextClickListener(android.view.View.OnContextClickListener);
method public void setOnCreateContextMenuListener(android.view.View.OnCreateContextMenuListener);
@@ -43906,7 +44454,7 @@
method public void setTag(int, java.lang.Object);
method public void setTextAlignment(int);
method public void setTextDirection(int);
- method public final void setTooltip(java.lang.CharSequence);
+ method public final void setTooltipText(java.lang.CharSequence);
method public final void setTop(int);
method public void setTouchDelegate(android.view.TouchDelegate);
method public final void setTransitionName(java.lang.String);
@@ -43941,8 +44489,8 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
- field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
- field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+ field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+ field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -43963,8 +44511,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;
@@ -43983,8 +44533,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
@@ -43996,6 +44544,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
@@ -44130,6 +44679,10 @@
method public abstract void onViewDetachedFromWindow(android.view.View);
}
+ public static abstract interface View.OnCapturedPointerListener {
+ method public abstract boolean onCapturedPointer(android.view.View, android.view.MotionEvent);
+ }
+
public static abstract interface View.OnClickListener {
method public abstract void onClick(android.view.View);
}
@@ -44503,7 +45056,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);
@@ -44700,6 +45253,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();
@@ -44756,6 +45310,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);
@@ -44860,6 +45415,7 @@
method public abstract boolean onMenuItemSelected(int, android.view.MenuItem);
method public abstract boolean onMenuOpened(int, android.view.Menu);
method public abstract void onPanelClosed(int, android.view.Menu);
+ method public default void onPointerCaptureChanged(boolean);
method public abstract boolean onPreparePanel(int, android.view.View, android.view.Menu);
method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
method public abstract boolean onSearchRequested();
@@ -44954,8 +45510,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
@@ -45824,6 +46382,13 @@
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
}
+ public final class AutoFillManager {
+ method public void updateAutoFillInput(android.view.View, int);
+ method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
+ field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
+ field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+ }
+
public final class AutoFillType implements android.os.Parcelable {
method public int describeContents();
method public static android.view.autofill.AutoFillType forList();
@@ -45858,6 +46423,8 @@
public static final class Dataset.Builder {
ctor public Dataset.Builder(java.lang.CharSequence);
method public android.view.autofill.Dataset build();
+ method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
}
@@ -45873,6 +46440,8 @@
method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
method public android.view.autofill.FillResponse build();
+ method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
}
@@ -45883,7 +46452,7 @@
public static abstract class VirtualViewDelegate.Callback {
ctor public VirtualViewDelegate.Callback();
- method public void onFocusChanged(int, boolean);
+ method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
method public void onNodeRemoved(int...);
method public void onValueChanged(int);
}
@@ -46290,6 +46859,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 {
@@ -46520,6 +47166,7 @@
public abstract class RenderProcessGoneDetail {
ctor public RenderProcessGoneDetail();
method public abstract boolean didCrash();
+ method public abstract int rendererPriorityAtExit();
}
public class ServiceWorkerClient {
@@ -46936,6 +47583,8 @@
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
method public java.lang.String getOriginalUrl();
method public int getProgress();
+ method public boolean getRendererPriorityWaivedWhenNotVisible();
+ method public int getRendererRequestedPriority();
method public deprecated float getScale();
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
@@ -46983,6 +47632,7 @@
method public deprecated void setMapTrackballToArrowKeys(boolean);
method public void setNetworkAvailable(boolean);
method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
+ method public void setRendererPriorityPolicy(int, boolean);
method public deprecated void setVerticalScrollbarOverlay(boolean);
method public void setWebChromeClient(android.webkit.WebChromeClient);
method public static void setWebContentsDebuggingEnabled(boolean);
@@ -46992,6 +47642,9 @@
method public void zoomBy(float);
method public boolean zoomIn();
method public boolean zoomOut();
+ field public static final int RENDERER_PRIORITY_BOUND = 1; // 0x1
+ field public static final int RENDERER_PRIORITY_IMPORTANT = 2; // 0x2
+ field public static final int RENDERER_PRIORITY_WAIVED = 0; // 0x0
field public static final java.lang.String SCHEME_GEO = "geo:0,0?q=";
field public static final java.lang.String SCHEME_MAILTO = "mailto:";
field public static final java.lang.String SCHEME_TEL = "tel:";
@@ -49297,6 +49950,10 @@
method public void endBatchEdit();
method public boolean extractText(android.view.inputmethod.ExtractedTextRequest, android.view.inputmethod.ExtractedText);
method public final int getAutoLinkMask();
+ method public int getAutoSizeMaxTextSize();
+ method public int getAutoSizeMinTextSize();
+ method public int getAutoSizeStepGranularity();
+ method public int getAutoSizeTextType();
method public int getBreakStrategy();
method public int getCompoundDrawablePadding();
method public android.content.res.ColorStateList getCompoundDrawableTintList();
@@ -49322,7 +49979,6 @@
method public int getExtendedPaddingTop();
method public android.text.InputFilter[] getFilters();
method public java.lang.String getFontFeatureSettings();
- method public java.lang.String getFontVariationSettings();
method public boolean getFreezesText();
method public int getGravity();
method public int getHighlightColor();
@@ -49369,7 +50025,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();
@@ -49406,6 +50062,10 @@
method public void removeTextChangedListener(android.text.TextWatcher);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
+ method public void setAutoSizeMaxTextSize(int, float);
+ method public void setAutoSizeMinTextSize(int, float);
+ method public void setAutoSizeStepGranularity(int, float);
+ method public void setAutoSizeTextType(int);
method public void setBreakStrategy(int);
method public void setCompoundDrawablePadding(int);
method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
@@ -49428,7 +50088,6 @@
method public void setExtractedText(android.view.inputmethod.ExtractedText);
method public void setFilters(android.text.InputFilter[]);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
method protected boolean setFrame(int, int, int, int);
method public void setFreezesText(boolean);
method public void setGravity(int);
@@ -49482,7 +50141,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);
@@ -49497,8 +50156,8 @@
method public void setTypeface(android.graphics.Typeface, int);
method public void setTypeface(android.graphics.Typeface);
method public void setWidth(int);
- field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0
- field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1
+ field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
+ field public static final int AUTO_SIZE_TEXT_TYPE_XY = 1; // 0x1
}
public static final class TextView.BufferType extends java.lang.Enum {
@@ -61625,6 +62284,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>);
@@ -61635,7 +62297,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);
@@ -61664,12 +62330,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 95b85d9..29179cf 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -37,6 +37,7 @@
field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
field public static final java.lang.String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
+ field public static final java.lang.String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
field public static final java.lang.String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
@@ -138,6 +139,7 @@
field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
+ field public static final java.lang.String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final java.lang.String MANAGE_USB = "android.permission.MANAGE_USB";
field public static final java.lang.String MANAGE_USERS = "android.permission.MANAGE_USERS";
field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
@@ -199,6 +201,7 @@
field public static final java.lang.String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
field public static final java.lang.String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
+ field public static final java.lang.String REQUEST_DELETE_PACKAGES = "android.permission.REQUEST_DELETE_PACKAGES";
field public static final java.lang.String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
field public static final java.lang.String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
@@ -215,6 +218,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";
@@ -222,6 +226,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";
@@ -310,6 +315,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
@@ -470,6 +477,7 @@
field public static final int centerMedium = 16842959; // 0x10100cf
field public static final int centerX = 16843170; // 0x10101a2
field public static final int centerY = 16843171; // 0x10101a3
+ field public static final int certDigest = 16844106; // 0x101054a
field public static final int checkBoxPreferenceStyle = 16842895; // 0x101008f
field public static final int checkMark = 16843016; // 0x1010108
field public static final int checkMarkTint = 16843943; // 0x10104a7
@@ -512,6 +520,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
@@ -842,6 +851,7 @@
field public static final int isScrollContainer = 16843342; // 0x101024e
field public static final int isSticky = 16843335; // 0x1010247
field public static final int isolatedProcess = 16843689; // 0x10103a9
+ field public static final int isolatedSplits = 16844109; // 0x101054d
field public static final int itemBackground = 16843056; // 0x1010130
field public static final int itemIconDisabledAlpha = 16843057; // 0x1010131
field public static final int itemPadding = 16843565; // 0x101032d
@@ -864,7 +874,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
@@ -1014,7 +1023,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
@@ -1162,6 +1170,7 @@
field public static final int resizeable = 16843405; // 0x101028d
field public static final int resizeableActivity = 16844022; // 0x10104f6
field public static final int resource = 16842789; // 0x1010025
+ field public static final int restartOnConfigChanges = 16844105; // 0x1010549
field public static final int restoreAnyVersion = 16843450; // 0x10102ba
field public static final deprecated int restoreNeedsApplication = 16843421; // 0x101029d
field public static final int restrictedAccountType = 16843733; // 0x10103d5
@@ -1278,6 +1287,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
@@ -1490,7 +1500,7 @@
field public static final int toYDelta = 16843209; // 0x10101c9
field public static final int toYScale = 16843205; // 0x10101c5
field public static final int toolbarStyle = 16843946; // 0x10104aa
- field public static final int tooltip = 16844084; // 0x1010534
+ field public static final int tooltipText = 16844084; // 0x1010534
field public static final int top = 16843182; // 0x10101ae
field public static final int topBright = 16842955; // 0x10100cb
field public static final int topDark = 16842951; // 0x10100c7
@@ -1549,7 +1559,7 @@
field public static final int viewportWidth = 16843778; // 0x1010402
field public static final int visibility = 16842972; // 0x10100dc
field public static final int visible = 16843156; // 0x1010194
- field public static final int visibleToEphemeral = 16844095; // 0x101053f
+ field public static final int visibleToInstantApps = 16844095; // 0x101053f
field public static final int vmSafeMode = 16843448; // 0x10102b8
field public static final int voiceIcon = 16843908; // 0x1010484
field public static final int voiceLanguage = 16843349; // 0x1010255
@@ -1926,6 +1936,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
@@ -2995,8 +3006,9 @@
public class AccountManager {
method public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle);
- method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle, int[]);
+ method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle, java.util.Map<java.lang.Integer, java.lang.Integer>);
method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean);
+ method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, java.lang.String[]);
method public java.lang.String blockingGetAuthToken(android.accounts.Account, java.lang.String, boolean) throws android.accounts.AuthenticatorException, java.io.IOException, android.accounts.OperationCanceledException;
method public void clearPassword(android.accounts.Account);
method public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
@@ -3004,7 +3016,9 @@
method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSessionAsUser(android.os.Bundle, android.app.Activity, android.os.UserHandle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public static android.accounts.AccountManager get(android.content.Context);
+ method public int getAccountVisibility(android.accounts.Account, int);
method public android.accounts.Account[] getAccounts();
+ method public java.util.Map<android.accounts.Account, java.lang.Integer> getAccountsAndVisibilityForPackage(java.lang.String, java.lang.String);
method public android.accounts.Account[] getAccountsByType(java.lang.String);
method public android.accounts.AccountManagerFuture<android.accounts.Account[]> getAccountsByTypeAndFeatures(java.lang.String, java.lang.String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler);
method public android.accounts.Account[] getAccountsByTypeForPackage(java.lang.String, java.lang.String);
@@ -3015,13 +3029,11 @@
method public android.accounts.AuthenticatorDescription[] getAuthenticatorTypes();
method public java.lang.String getPassword(android.accounts.Account);
method public java.lang.String getPreviousName(android.accounts.Account);
- method public int[] getRequestingUidsForType(java.lang.String);
+ method public java.util.Map<java.lang.Integer, java.lang.Integer> getUidsAndVisibilityForAccount(android.accounts.Account);
method public java.lang.String getUserData(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void invalidateAuthToken(java.lang.String, java.lang.String);
- method public boolean isAccountVisible(android.accounts.Account, int);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> isCredentialsUpdateSuggested(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
- method public boolean makeAccountVisible(android.accounts.Account, int);
method public static deprecated android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, java.lang.String[], boolean, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.List<android.accounts.Account>, java.lang.String[], java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public boolean notifyAccountAuthenticated(android.accounts.Account);
@@ -3029,9 +3041,9 @@
method public deprecated android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public boolean removeAccountExplicitly(android.accounts.Account);
- method public boolean removeAccountVisibility(android.accounts.Account, int);
method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener);
method public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler);
+ method public boolean setAccountVisibility(android.accounts.Account, int, int);
method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
method public void setPassword(android.accounts.Account, java.lang.String);
method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
@@ -3070,7 +3082,14 @@
field public static final java.lang.String KEY_LAST_AUTHENTICATED_TIME = "lastAuthenticatedTime";
field public static final java.lang.String KEY_PASSWORD = "password";
field public static final java.lang.String KEY_USERDATA = "userdata";
- field public static final java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
+ field public static final deprecated java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
+ field public static final int UID_KEY_DEFAULT_LEGACY_VISIBILITY = -3; // 0xfffffffd
+ field public static final int UID_KEY_DEFAULT_VISIBILITY = -2; // 0xfffffffe
+ field public static final int VISIBILITY_NOT_VISIBLE = 3; // 0x3
+ field public static final int VISIBILITY_UNDEFINED = 0; // 0x0
+ field public static final int VISIBILITY_USER_MANAGED_NOT_VISIBLE = 4; // 0x4
+ field public static final int VISIBILITY_USER_MANAGED_VISIBLE = 2; // 0x2
+ field public static final int VISIBILITY_VISIBLE = 1; // 0x1
}
public abstract interface AccountManagerCallback<V> {
@@ -3169,8 +3188,10 @@
public static abstract interface Animator.AnimatorListener {
method public abstract void onAnimationCancel(android.animation.Animator);
+ method public default void onAnimationEnd(android.animation.Animator, boolean);
method public abstract void onAnimationEnd(android.animation.Animator);
method public abstract void onAnimationRepeat(android.animation.Animator);
+ method public default void onAnimationStart(android.animation.Animator, boolean);
method public abstract void onAnimationStart(android.animation.Animator);
}
@@ -3206,6 +3227,8 @@
method public void playSequentially(java.util.List<android.animation.Animator>);
method public void playTogether(android.animation.Animator...);
method public void playTogether(java.util.Collection<android.animation.Animator>);
+ method public void reverse();
+ method public void setCurrentPlayTime(long);
method public android.animation.AnimatorSet setDuration(long);
method public void setInterpolator(android.animation.TimeInterpolator);
method public void setStartDelay(long);
@@ -3610,8 +3633,7 @@
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
- method public void enterPictureInPictureMode(float);
- method public void enterPictureInPictureModeOnMoveToBackground(boolean);
+ method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -3644,7 +3666,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();
@@ -3786,8 +3807,7 @@
method public void setIntent(android.content.Intent);
method public final void setMediaController(android.media.session.MediaController);
method public void setOverlayWithDecorCaptionEnabled(boolean);
- method public void setPictureInPictureActions(java.util.List<android.app.RemoteAction>);
- method public void setPictureInPictureAspectRatio(float);
+ method public void setPictureInPictureArgs(android.app.PictureInPictureArgs);
method public final deprecated void setProgress(int);
method public final deprecated void setProgressBarIndeterminate(boolean);
method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -3797,7 +3817,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);
@@ -4985,11 +5004,13 @@
public static class Instrumentation.ActivityMonitor {
ctor public Instrumentation.ActivityMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
ctor public Instrumentation.ActivityMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
+ ctor public Instrumentation.ActivityMonitor();
method public final android.content.IntentFilter getFilter();
method public final int getHits();
method public final android.app.Activity getLastActivity();
method public final android.app.Instrumentation.ActivityResult getResult();
method public final boolean isBlocking();
+ method public android.app.Instrumentation.ActivityResult onMatchIntent(android.content.Intent);
method public final android.app.Activity waitForActivity();
method public final android.app.Activity waitForActivityWithTimeout(long);
}
@@ -5162,6 +5183,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";
@@ -5189,8 +5211,10 @@
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+ field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+ field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
@@ -5336,7 +5360,8 @@
}
public static class Notification.Builder {
- ctor public Notification.Builder(android.content.Context);
+ ctor public Notification.Builder(android.content.Context, java.lang.String);
+ ctor public deprecated Notification.Builder(android.content.Context);
method public deprecated android.app.Notification.Builder addAction(int, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.Notification.Builder addAction(android.app.Notification.Action);
method public android.app.Notification.Builder addExtras(android.os.Bundle);
@@ -5355,6 +5380,7 @@
method public android.app.Notification.Builder setChannel(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
method public android.app.Notification.Builder setColor(int);
+ method public android.app.Notification.Builder setColorized(boolean);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
@@ -5392,6 +5418,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);
@@ -5458,9 +5485,11 @@
public static class Notification.MessagingStyle extends android.app.Notification.Style {
ctor public Notification.MessagingStyle(java.lang.CharSequence);
+ method public android.app.Notification.MessagingStyle addHistoricMessage(android.app.Notification.MessagingStyle.Message);
method public android.app.Notification.MessagingStyle addMessage(java.lang.CharSequence, long, java.lang.CharSequence);
method public android.app.Notification.MessagingStyle addMessage(android.app.Notification.MessagingStyle.Message);
method public java.lang.CharSequence getConversationTitle();
+ method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
method public java.lang.CharSequence getUserDisplayName();
method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
@@ -5492,9 +5521,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);
}
@@ -5566,6 +5597,7 @@
method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
+ method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
method public int getLockscreenVisibility();
@@ -5573,10 +5605,12 @@
method public android.net.Uri getSound();
method public int getUserLockedFields();
method public long[] getVibrationPattern();
- method public boolean isAllowed();
+ method public boolean isDeleted();
method public void lockFields(int);
method public void populateFromXml(org.xmlpull.v1.XmlPullParser);
method public void setBypassDnd(boolean);
+ method public void setDeleted(boolean);
+ method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
@@ -5601,6 +5635,20 @@
field public static final int USER_LOCKED_VISIBILITY = 2; // 0x2
}
+ public final class NotificationChannelGroup implements android.os.Parcelable {
+ ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+ ctor protected NotificationChannelGroup(android.os.Parcel);
+ method public void addChannel(android.app.NotificationChannel);
+ method public int describeContents();
+ method public java.util.List<android.app.NotificationChannel> getChannels();
+ method public java.lang.String getId();
+ method public java.lang.CharSequence getName();
+ method public org.json.JSONObject toJson() throws org.json.JSONException;
+ method public void writeToParcel(android.os.Parcel, int);
+ method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
+ field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
+ }
+
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
@@ -5608,6 +5656,8 @@
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public void createNotificationChannel(android.app.NotificationChannel);
+ method public void createNotificationChannelGroup(android.app.NotificationChannelGroup);
+ method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>);
method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>);
method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
@@ -5624,6 +5674,7 @@
method public boolean removeAutomaticZenRule(java.lang.String);
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
+ method public android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
@@ -5709,6 +5760,17 @@
method public abstract void onSendFinished(android.app.PendingIntent, android.content.Intent, int, java.lang.String, android.os.Bundle);
}
+ public final class PictureInPictureArgs implements android.os.Parcelable {
+ ctor public PictureInPictureArgs();
+ ctor public PictureInPictureArgs(float, java.util.List<android.app.RemoteAction>);
+ method public android.app.PictureInPictureArgs clone();
+ method public int describeContents();
+ method public void setActions(java.util.List<android.app.RemoteAction>);
+ method public void setAspectRatio(float);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.PictureInPictureArgs> CREATOR;
+ }
+
public class Presentation extends android.app.Dialog {
ctor public Presentation(android.content.Context, android.view.Display);
ctor public Presentation(android.content.Context, android.view.Display, int);
@@ -5745,6 +5807,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();
@@ -6267,6 +6341,7 @@
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+ method public android.content.Intent createAdminSupportIntent(java.lang.String);
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
method public void enableSystemApp(android.content.ComponentName, java.lang.String);
method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
@@ -6275,16 +6350,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();
@@ -6339,7 +6416,8 @@
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
- method public boolean isCallerApplicationRestrictionsManagingPackage();
+ method public boolean isBackupServiceEnabled(android.content.ComponentName);
+ method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
method public boolean isDeviceManaged();
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isDeviceProvisioned();
@@ -6356,6 +6434,8 @@
method public void lockNow();
method public void lockNow(int);
method public void notifyPendingSystemUpdate(long);
+ method public void notifyPendingSystemUpdate(long, boolean);
+ method public boolean packageHasActiveAdmins(java.lang.String);
method public void reboot(android.content.ComponentName);
method public void removeActiveAdmin(android.content.ComponentName);
method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -6372,13 +6452,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);
@@ -6427,6 +6509,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";
@@ -6439,6 +6522,12 @@
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_BLOCK_UNINSTALL = "delegation-block-uninstall";
+ field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+ field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+ field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+ field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
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
@@ -6446,6 +6535,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";
@@ -6509,6 +6599,8 @@
field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+ field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+ field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -6561,8 +6653,12 @@
public final class SystemUpdateInfo implements android.os.Parcelable {
method public int describeContents();
method public long getReceivedTime();
+ method public int getSecurityPatchState();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdateInfo> CREATOR;
+ field public static final int SECURITY_PATCH_STATE_FALSE = 1; // 0x1
+ field public static final int SECURITY_PATCH_STATE_TRUE = 2; // 0x2
+ field public static final int SECURITY_PATCH_STATE_UNKNOWN = 0; // 0x0
}
public class SystemUpdatePolicy implements android.os.Parcelable {
@@ -6738,16 +6834,22 @@
method public boolean isBackupEnabled();
method public java.lang.String[] listAllTransports();
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
}
@@ -6860,6 +6962,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);
@@ -7158,6 +7266,7 @@
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
+ method public boolean isRequestPinAppWidgetSupported();
method public void notifyAppWidgetViewDataChanged(int[], int);
method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
@@ -8289,6 +8398,7 @@
ctor public ClipData(android.content.ClipDescription, android.content.ClipData.Item);
ctor public ClipData(android.content.ClipData);
method public void addItem(android.content.ClipData.Item);
+ method public void addItem(android.content.ClipData.Item, android.content.ContentResolver);
method public int describeContents();
method public android.content.ClipDescription getDescription();
method public android.content.ClipData.Item getItemAt(int);
@@ -8675,6 +8785,7 @@
method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public abstract deprecated void clearWallpaper() throws java.io.IOException;
method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+ method public abstract android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.Context createCredentialProtectedStorageContext();
method public abstract android.content.Context createDeviceProtectedStorageContext();
method public abstract android.content.Context createDisplayContext(android.view.Display);
@@ -8815,6 +8926,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";
@@ -8884,6 +8996,7 @@
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public deprecated void clearWallpaper() throws java.io.IOException;
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+ method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.Context createCredentialProtectedStorageContext();
method public android.content.Context createDeviceProtectedStorageContext();
method public android.content.Context createDisplayContext(android.view.Display);
@@ -9197,6 +9310,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";
@@ -9926,6 +10040,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 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
@@ -9986,6 +10104,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;
@@ -10083,6 +10202,7 @@
field public int requiresSmallestWidthDp;
field public java.lang.String[] sharedLibraryFiles;
field public java.lang.String sourceDir;
+ field public java.lang.String[] splitNames;
field public java.lang.String[] splitPublicSourceDirs;
field public java.lang.String[] splitSourceDirs;
field public int targetSdkVersion;
@@ -10090,6 +10210,7 @@
field public int theme;
field public int uiOptions;
field public int uid;
+ field public java.lang.String volumeUuid;
}
public static class ApplicationInfo.DisplayNameComparator implements java.util.Comparator {
@@ -10111,6 +10232,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 {
@@ -10141,7 +10263,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();
@@ -10149,6 +10272,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";
@@ -10197,11 +10321,21 @@
field public boolean handleProfiling;
field public java.lang.String publicSourceDir;
field public java.lang.String sourceDir;
+ field public java.lang.String[] splitNames;
field public java.lang.String[] splitPublicSourceDirs;
field public java.lang.String[] splitSourceDirs;
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);
@@ -10231,6 +10365,7 @@
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
+ method public java.util.List<android.os.UserHandle> getProfiles();
method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
method public android.content.IntentSender getShortcutConfigActivityIntent(android.content.pm.LauncherActivityInfo);
method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(java.lang.String, android.os.UserHandle);
@@ -10248,7 +10383,8 @@
method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
- field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM";
+ field public static final java.lang.String ACTION_CONFIRM_PIN_APPWIDGET = "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
+ field public static final java.lang.String ACTION_CONFIRM_PIN_SHORTCUT = "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}
@@ -10268,7 +10404,7 @@
method public boolean accept(android.os.Bundle);
method public boolean accept();
method public int describeContents();
- method public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo();
+ method public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo(android.content.Context);
method public int getRequestType();
method public android.content.pm.ShortcutInfo getShortcutInfo();
method public boolean isValid();
@@ -10337,6 +10473,7 @@
method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler);
method public void uninstall(java.lang.String, android.content.IntentSender);
+ method public void uninstall(android.content.pm.VersionedPackage, android.content.IntentSender);
method public void unregisterSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
method public void updateSessionAppIcon(int, android.graphics.Bitmap);
method public void updateSessionAppLabel(int, java.lang.CharSequence);
@@ -10451,6 +10588,7 @@
method public abstract boolean addPermission(android.content.pm.PermissionInfo);
method public abstract boolean addPermissionAsync(android.content.pm.PermissionInfo);
method public abstract deprecated void addPreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
+ method public abstract boolean canRequestPackageInstalls();
method public abstract java.lang.String[] canonicalToCurrentPackageNames(java.lang.String[]);
method public abstract int checkPermission(java.lang.String, java.lang.String);
method public abstract int checkSignatures(java.lang.String, java.lang.String);
@@ -10465,6 +10603,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;
@@ -10477,12 +10616,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);
@@ -10490,6 +10632,7 @@
method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.PackageInstaller getPackageInstaller();
method public abstract int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract java.lang.String[] getPackagesForUid(int);
@@ -10505,6 +10648,7 @@
method public abstract android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
method public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public abstract java.lang.String[] getSystemSharedLibraryNames();
method public abstract java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
@@ -10536,7 +10680,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);
@@ -10572,6 +10718,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";
@@ -10615,6 +10762,7 @@
field public static final java.lang.String FEATURE_SIP = "android.software.sip";
field public static final java.lang.String FEATURE_SIP_VOIP = "android.software.sip.voip";
field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony";
+ field public static final java.lang.String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
field public static final java.lang.String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
field public static final java.lang.String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
field public static final deprecated java.lang.String FEATURE_TELEVISION = "android.hardware.type.television";
@@ -10696,6 +10844,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
@@ -10719,6 +10872,7 @@
field public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; // 0xfffffffc
field public static final int VERIFICATION_ALLOW = 1; // 0x1
field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
+ field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
}
public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
@@ -10867,6 +11021,20 @@
field public java.lang.String permission;
}
+ public final class SharedLibraryInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.content.pm.VersionedPackage getDeclaringPackage();
+ method public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
+ method public java.lang.String getName();
+ method public int getVersion();
+ method public boolean isBuiltin();
+ method public boolean isDynamic();
+ method public boolean isStatic();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
+ field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
+ }
+
public final class ShortcutInfo implements android.os.Parcelable {
method public int describeContents();
method public android.content.ComponentName getActivity();
@@ -10942,6 +11110,15 @@
field public static final android.os.Parcelable.Creator<android.content.pm.Signature> CREATOR;
}
+ public final class VersionedPackage implements android.os.Parcelable {
+ ctor public VersionedPackage(java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getPackageName();
+ method public long getVersionCode();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.VersionedPackage> CREATOR;
+ }
+
}
package android.content.pm.permission {
@@ -11041,7 +11218,9 @@
method public int getLayoutDirection();
method public android.os.LocaleList getLocales();
method public boolean isLayoutSizeAtLeast(int);
+ method public boolean isScreenHdr();
method public boolean isScreenRound();
+ method public boolean isScreenWideColorGamut();
method public static boolean needNewResources(int, int);
method public void readFromParcel(android.os.Parcel);
method public void setLayoutDirection(java.util.Locale);
@@ -11051,6 +11230,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 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
@@ -11116,6 +11305,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 colorMode;
field public int densityDpi;
field public float fontScale;
field public int hardKeyboardHidden;
@@ -11170,6 +11360,7 @@
method public android.graphics.drawable.Drawable getDrawable(int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
method public deprecated android.graphics.drawable.Drawable getDrawableForDensity(int, int) throws android.content.res.Resources.NotFoundException;
method public android.graphics.drawable.Drawable getDrawableForDensity(int, int, android.content.res.Resources.Theme);
+ method public android.graphics.Typeface getFont(int) throws android.content.res.Resources.NotFoundException;
method public float getFraction(int, int, int);
method public int getIdentifier(java.lang.String, java.lang.String, java.lang.String);
method public int[] getIntArray(int) throws android.content.res.Resources.NotFoundException;
@@ -13101,7 +13292,6 @@
method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt);
method public android.graphics.Paint.FontMetricsInt getFontMetricsInt();
method public float getFontSpacing();
- method public java.lang.String getFontVariationSettings();
method public int getHinting();
method public float getLetterSpacing();
method public android.graphics.MaskFilter getMaskFilter();
@@ -13159,7 +13349,6 @@
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
method public void setHinting(int);
method public void setLetterSpacing(float);
method public void setLinearText(boolean);
@@ -13657,6 +13846,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);
@@ -13677,6 +13867,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();
}
@@ -14083,6 +14281,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);
@@ -14231,6 +14446,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 {
@@ -14558,9 +14786,42 @@
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 int BLOB = 33; // 0x21
+ 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 = 22; // 0x16
+ 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();
@@ -14576,6 +14837,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
@@ -14657,6 +14919,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;
@@ -14688,6 +14961,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[]);
@@ -14819,7 +15095,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();
@@ -15234,6 +15510,7 @@
field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -15313,6 +15590,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_STATE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -15462,11 +15740,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
@@ -15543,6 +15823,7 @@
field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
+ field public static final int VIRTUAL_DISPLAY_FLAG_SHOW_WITH_INSECURE_LOCKSCREEN = 32; // 0x20
}
public static abstract interface DisplayManager.DisplayListener {
@@ -18727,6 +19008,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();
@@ -18736,6 +19026,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);
@@ -18755,9 +19047,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);
@@ -19226,6 +19528,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();
@@ -20001,6 +20311,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();
@@ -20529,6 +20867,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();
@@ -21602,7 +21969,7 @@
field public static final int FLAG_BYPASS_MUTE = 128; // 0x80
field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
field public static final int FLAG_HW_HOTWORD = 32; // 0x20
- field public static final int FLAG_LOW_LATENCY = 256; // 0x100
+ field public static final deprecated int FLAG_LOW_LATENCY = 256; // 0x100
field public static final int USAGE_ALARM = 4; // 0x4
field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
@@ -22088,6 +22455,7 @@
method protected deprecated int getNativeFrameCount();
method public static int getNativeOutputSampleRate(int);
method public int getNotificationMarkerPosition();
+ method public int getPerformanceMode();
method public int getPlayState();
method public int getPlaybackHeadPosition();
method public android.media.PlaybackParams getPlaybackParams();
@@ -22134,6 +22502,9 @@
field public static final int ERROR_INVALID_OPERATION = -3; // 0xfffffffd
field public static final int MODE_STATIC = 0; // 0x0
field public static final int MODE_STREAM = 1; // 0x1
+ field public static final int PERFORMANCE_MODE_LOW_LATENCY = 1; // 0x1
+ field public static final int PERFORMANCE_MODE_NONE = 0; // 0x0
+ field public static final int PERFORMANCE_MODE_POWER_SAVING = 2; // 0x2
field public static final int PLAYSTATE_PAUSED = 2; // 0x2
field public static final int PLAYSTATE_PLAYING = 3; // 0x3
field public static final int PLAYSTATE_STOPPED = 1; // 0x1
@@ -22151,6 +22522,7 @@
method public android.media.AudioTrack.Builder setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack.Builder setAudioFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
+ method public android.media.AudioTrack.Builder setPerformanceMode(int);
method public android.media.AudioTrack.Builder setSessionId(int) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack.Builder setTransferMode(int) throws java.lang.IllegalArgumentException;
}
@@ -22165,6 +22537,40 @@
method public default void onRoutingChanged(android.media.AudioRouting);
}
+ public final class BufferingParams implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getInitialBufferingMode();
+ method public int getInitialBufferingWatermarkKB();
+ method public int getInitialBufferingWatermarkMs();
+ method public int getRebufferingMode();
+ method public int getRebufferingWatermarkHighKB();
+ method public int getRebufferingWatermarkHighMs();
+ method public int getRebufferingWatermarkLowKB();
+ method public int getRebufferingWatermarkLowMs();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFERING_MODE_NONE = 0; // 0x0
+ field public static final int BUFFERING_MODE_SIZE_ONLY = 2; // 0x2
+ field public static final int BUFFERING_MODE_TIME_ONLY = 1; // 0x1
+ field public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3; // 0x3
+ field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+ }
+
+ public static class BufferingParams.Builder {
+ ctor public BufferingParams.Builder();
+ ctor public BufferingParams.Builder(android.media.BufferingParams);
+ method public android.media.BufferingParams build();
+ method public android.media.BufferingParams.Builder setInitialBufferingMode(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkKB(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingMode(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksKB(int, int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksMs(int, int);
+ }
+
public class CamcorderProfile {
method public static android.media.CamcorderProfile get(int);
method public static android.media.CamcorderProfile get(int, int);
@@ -23309,6 +23715,7 @@
public final class MediaMuxer {
ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException;
+ ctor public MediaMuxer(java.io.FileDescriptor, int) throws java.io.IOException;
method public int addTrack(android.media.MediaFormat);
method public void release();
method public void setLocation(float, float);
@@ -23337,7 +23744,9 @@
method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
method public void deselectTrack(int) throws java.lang.IllegalStateException;
method public int getAudioSessionId();
+ method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
+ method public android.media.BufferingParams getDefaultBufferingParams();
method public int getDuration();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -23360,6 +23769,7 @@
method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public deprecated void setAudioStreamType(int);
method public void setAuxEffectSendLevel(float);
+ method public void setBufferingParams(android.media.BufferingParams);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
@@ -23485,6 +23895,8 @@
method public void setLocation(float, float);
method public void setMaxDuration(int) throws java.lang.IllegalArgumentException;
method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException;
+ method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalStateException;
+ method public void setNextOutputFile(java.lang.String) throws java.io.IOException, java.lang.IllegalStateException;
method public void setOnErrorListener(android.media.MediaRecorder.OnErrorListener);
method public void setOnInfoListener(android.media.MediaRecorder.OnInfoListener);
method public void setOrientationHint(int);
@@ -23503,7 +23915,9 @@
field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64
field public static final int MEDIA_RECORDER_ERROR_UNKNOWN = 1; // 0x1
field public static final int MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800; // 0x320
+ field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING = 802; // 0x322
field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801; // 0x321
+ field public static final int MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED = 803; // 0x323
field public static final int MEDIA_RECORDER_INFO_UNKNOWN = 1; // 0x1
}
@@ -24918,12 +25332,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();
@@ -25119,6 +25543,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";
@@ -25150,6 +25575,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";
@@ -25176,7 +25602,16 @@
}
public static final class TvContract.Programs implements android.media.tv.TvContract.BaseTvColumns {
+ field public static final java.lang.String ASPECT_RATIO_16_9 = "ASPECT_RATIO_16_9";
+ field public static final java.lang.String ASPECT_RATIO_1_1 = "ASPECT_RATIO_1_1";
+ field public static final java.lang.String ASPECT_RATIO_2_3 = "ASPECT_RATIO_2_3";
+ field public static final java.lang.String ASPECT_RATIO_3_2 = "ASPECT_RATIO_3_2";
+ field public static final java.lang.String AVAILABILITY_AVAILABLE = "AVAILABILITY_AVAILABLE";
+ field public static final java.lang.String AVAILABILITY_FREE_WITH_SUBSCRIPTION = "AVAILABILITY_FREE_WITH_SUBSCRIPTION";
+ field public static final java.lang.String AVAILABILITY_PAID_CONTENT = "AVAILABILITY_PAID_CONTENT";
field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language";
+ field public static final java.lang.String COLUMN_AUTHOR = "author";
+ field public static final java.lang.String COLUMN_AVAILABILITY = "availability";
field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre";
field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id";
@@ -25185,28 +25620,74 @@
field public static final java.lang.String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
field public static final deprecated java.lang.String COLUMN_EPISODE_NUMBER = "episode_number";
field public static final java.lang.String COLUMN_EPISODE_TITLE = "episode_title";
+ field public static final java.lang.String COLUMN_INTERACTION_COUNT = "interaction_count";
+ field public static final java.lang.String COLUMN_INTERACTION_TYPE = "interaction_type";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
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_ITEM_COUNT = "item_count";
+ field public static final java.lang.String COLUMN_LIVE = "live";
+ field public static final java.lang.String COLUMN_LOGO = "logo";
field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
+ field public static final java.lang.String COLUMN_OFFER_PRICE = "offer_price";
+ field public static final java.lang.String COLUMN_POSTER_ART_ASPECT_RATIO = "poster_art_aspect_ratio";
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_RELEASE_DATE = "release_date";
+ field public static final java.lang.String COLUMN_REVIEW_RATING = "review_rating";
+ field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
field public static final deprecated java.lang.String COLUMN_SEASON_NUMBER = "season_number";
field public static final java.lang.String COLUMN_SEASON_TITLE = "season_title";
field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description";
+ field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
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_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
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_TYPE = "type";
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";
+ field public static final java.lang.String COLUMN_WATCH_NEXT_TYPE = "watch_next_type";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/program";
field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String INTERACTION_TYPE_FANS = "INTERACTION_TYPE_FANS";
+ field public static final java.lang.String INTERACTION_TYPE_FOLLOWERS = "INTERACTION_TYPE_FOLLOWERS";
+ field public static final java.lang.String INTERACTION_TYPE_LIKES = "INTERACTION_TYPE_LIKES";
+ field public static final java.lang.String INTERACTION_TYPE_LISTENS = "INTERACTION_TYPE_LISTENS";
+ field public static final java.lang.String INTERACTION_TYPE_THUMBS = "INTERACTION_TYPE_THUMBS";
+ field public static final java.lang.String INTERACTION_TYPE_VIEWERS = "INTERACTION_TYPE_VIEWERS";
+ field public static final java.lang.String INTERACTION_TYPE_VIEWS = "INTERACTION_TYPE_VIEWS";
+ field public static final java.lang.String REVIEW_RATING_STYLE_PERCENTAGE = "REVIEW_RATING_STYLE_PERCENTAGE";
+ field public static final java.lang.String REVIEW_RATING_STYLE_STARS = "REVIEW_RATING_STYLE_STARS";
+ field public static final java.lang.String REVIEW_RATING_STYLE_THUMBS_UP_DOWN = "REVIEW_RATING_STYLE_THUMBS_UP_DOWN";
+ field public static final java.lang.String TYPE_ALBUM = "TYPE_ALBUM";
+ field public static final java.lang.String TYPE_ARTIST = "TYPE_ARTIST";
+ field public static final java.lang.String TYPE_CHANNEL = "TYPE_CHANNEL";
+ field public static final java.lang.String TYPE_CLIP = "TYPE_CLIP";
+ field public static final java.lang.String TYPE_EVENT = "TYPE_EVENT";
+ field public static final java.lang.String TYPE_MOVIE = "TYPE_MOVIE";
+ field public static final java.lang.String TYPE_PLAYLIST = "TYPE_PLAYLIST";
+ field public static final java.lang.String TYPE_STATION = "TYPE_STATION";
+ field public static final java.lang.String TYPE_TRACK = "TYPE_TRACK";
+ field public static final java.lang.String TYPE_TV_EPISODE = "TYPE_TV_EPISODE";
+ field public static final java.lang.String TYPE_TV_SEASON = "TYPE_TV_SEASON";
+ field public static final java.lang.String TYPE_TV_SERIES = "TYPE_TV_SERIES";
+ field public static final java.lang.String WATCH_NEXT_TYPE_CONTINUE = "WATCH_NEXT_TYPE_CONTINUE";
+ field public static final java.lang.String WATCH_NEXT_TYPE_NEW = "WATCH_NEXT_TYPE_NEW";
+ field public static final java.lang.String WATCH_NEXT_TYPE_NEXT = "WATCH_NEXT_TYPE_NEXT";
}
public static final class TvContract.Programs.Genres {
@@ -25397,9 +25878,13 @@
method public void unregisterCallback(android.media.tv.TvInputManager.TvInputCallback);
method public void updateTvInputInfo(android.media.tv.TvInputInfo);
field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
+ field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
+ field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
+ field public static final java.lang.String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID";
+ field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME";
field public static final 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
@@ -25655,6 +26140,48 @@
}
+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 android.metrics.LogMaker clearTaggedData(int);
+ 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 {
@@ -26171,6 +26698,10 @@
field public static final android.os.Parcelable.Creator<android.net.Network> CREATOR;
}
+ public class NetworkBadging {
+ method public static android.graphics.drawable.Drawable getWifiIcon(int, int, android.content.res.Resources.Theme);
+ }
+
public final class NetworkCapabilities implements android.os.Parcelable {
ctor public NetworkCapabilities(android.net.NetworkCapabilities);
method public int describeContents();
@@ -26343,6 +26874,8 @@
method public android.net.wifi.WifiConfiguration[] getConnectableConfigs();
method public android.net.wifi.WifiConfiguration getConnectedConfig();
method public android.net.wifi.WifiConfiguration getDefaultWifiConfig();
+ method public int getLastSelectedNetworkId();
+ method public long getLastSelectedNetworkTimestamp();
method public android.net.wifi.ScanResult[] getScanResults();
method public void setConnectableConfigs(android.net.wifi.WifiConfiguration[]);
method public void setConnectedConfig(android.net.wifi.WifiConfiguration);
@@ -26356,6 +26889,7 @@
method public android.net.RecommendationRequest.Builder setConnectableConfigs(android.net.wifi.WifiConfiguration[]);
method public android.net.RecommendationRequest.Builder setConnectedWifiConfig(android.net.wifi.WifiConfiguration);
method public android.net.RecommendationRequest.Builder setDefaultWifiConfig(android.net.wifi.WifiConfiguration);
+ method public android.net.RecommendationRequest.Builder setLastSelectedNetwork(int, long);
method public android.net.RecommendationRequest.Builder setScanResults(android.net.wifi.ScanResult[]);
}
@@ -26447,6 +26981,7 @@
ctor public TrafficStats();
method public static void clearThreadStatsTag();
method public static void clearThreadStatsUid();
+ method public static int getAndSetThreadStatsTag(int);
method public static long getMobileRxBytes();
method public static long getMobileRxPackets();
method public static long getMobileTxBytes();
@@ -27453,6 +27988,7 @@
method public int describeContents();
method public android.net.ProxyInfo getHttpProxy();
method public boolean hasNoInternetAccess();
+ method public boolean isEphemeral();
method public boolean isNoInternetAccessExpected();
method public boolean isPasspoint();
method public void setHttpProxy(android.net.ProxyInfo);
@@ -27559,6 +28095,7 @@
method public java.security.cert.X509Certificate getCaCertificate();
method public java.security.cert.X509Certificate[] getCaCertificates();
method public java.security.cert.X509Certificate getClientCertificate();
+ method public java.security.cert.X509Certificate[] getClientCertificateChain();
method public java.lang.String getDomainSuffixMatch();
method public int getEapMethod();
method public java.lang.String getIdentity();
@@ -27572,6 +28109,7 @@
method public void setCaCertificate(java.security.cert.X509Certificate);
method public void setCaCertificates(java.security.cert.X509Certificate[]);
method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+ method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
method public void setDomainSuffixMatch(java.lang.String);
method public void setEapMethod(int);
method public void setIdentity(java.lang.String);
@@ -27658,6 +28196,7 @@
method public boolean isWifiEnabled();
method public boolean isWifiScannerSupported();
method public boolean pingSupplicant();
+ method public void queryPasspointIcon(long, java.lang.String);
method public boolean reassociate();
method public boolean reconnect();
method public boolean removeNetwork(int);
@@ -27672,6 +28211,10 @@
method public boolean startScan(android.os.WorkSource);
method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
method public int updateNetwork(android.net.wifi.WifiConfiguration);
+ field public static final java.lang.String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+ field public static final java.lang.String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+ field public static final java.lang.String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+ field public static final java.lang.String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final int CHANGE_REASON_ADDED = 0; // 0x0
@@ -27685,6 +28228,18 @@
field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
field public static final java.lang.String EXTRA_NEW_STATE = "newState";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
field public static final java.lang.String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated";
@@ -32305,6 +32860,7 @@
method public final android.util.SizeF readSizeF();
method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
method public final android.util.SparseBooleanArray readSparseBooleanArray();
+ method public final android.util.SparseIntArray readSparseIntArray();
method public final java.lang.String readString();
method public final void readStringArray(java.lang.String[]);
method public final void readStringList(java.util.List<java.lang.String>);
@@ -32349,6 +32905,7 @@
method public final void writeSizeF(android.util.SizeF);
method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
+ method public final void writeSparseIntArray(android.util.SparseIntArray);
method public final void writeString(java.lang.String);
method public final void writeStringArray(java.lang.String[]);
method public final void writeStringList(java.util.List<java.lang.String>);
@@ -32693,6 +33250,7 @@
method public android.os.StrictMode.VmPolicy.Builder detectLeakedClosableObjects();
method public android.os.StrictMode.VmPolicy.Builder detectLeakedRegistrationObjects();
method public android.os.StrictMode.VmPolicy.Builder detectLeakedSqlLiteObjects();
+ method public android.os.StrictMode.VmPolicy.Builder detectUntaggedSockets();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeath();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
@@ -33065,14 +33623,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";
}
@@ -33213,6 +33779,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);
@@ -35597,7 +36164,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;
@@ -35611,6 +36177,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";
@@ -36074,10 +36650,12 @@
field public static final java.lang.String ACTION_APPLICATION_DETAILS_SETTINGS = "android.settings.APPLICATION_DETAILS_SETTINGS";
field public static final java.lang.String ACTION_APPLICATION_DEVELOPMENT_SETTINGS = "android.settings.APPLICATION_DEVELOPMENT_SETTINGS";
field public static final java.lang.String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
+ field public static final java.lang.String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+ field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_CONFIGURE_WIFI_SETTINGS = "android.settings.CONFIGURE_WIFI_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
@@ -36097,6 +36675,7 @@
field public static final java.lang.String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
field public static final java.lang.String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
field public static final java.lang.String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
+ field public static final java.lang.String ACTION_MANAGE_EXTERNAL_SOURCES = "android.settings.action.MANAGE_EXTERNAL_SOURCES";
field public static final java.lang.String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
field public static final java.lang.String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
field public static final java.lang.String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
@@ -36133,8 +36712,10 @@
field public static final java.lang.String AUTHORITY = "settings";
field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
field public static final java.lang.String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+ field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -36169,6 +36750,7 @@
field public static final java.lang.String BOOT_COUNT = "boot_count";
field public static final java.lang.String CONTACT_METADATA_SYNC_ENABLED = "contact_metadata_sync_enabled";
field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String CURATE_SAVED_OPEN_NETWORKS = "curate_saved_open_networks";
field public static final java.lang.String DATA_ROAMING = "data_roaming";
field public static final java.lang.String DEBUG_APP = "debug_app";
field public static final java.lang.String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled";
@@ -36252,7 +36834,7 @@
field public static final java.lang.String ENABLED_INPUT_METHODS = "enabled_input_methods";
field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility";
- field public static final java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+ field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
field public static final java.lang.String LOCATION_MODE = "location_mode";
field public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2
field public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3
@@ -36807,6 +37389,8 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
+ field public static final java.lang.String ARCHIVED = "archived";
+ field public static final java.lang.String BACKED_UP = "backed_up";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -36814,6 +37398,7 @@
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
+ field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
field public static final java.lang.String IS_READ = "is_read";
field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -36821,6 +37406,7 @@
field public static final java.lang.String NUMBER = "number";
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String RESTORED = "restored";
field public static final java.lang.String SOURCE_DATA = "source_data";
field public static final java.lang.String SOURCE_PACKAGE = "source_package";
field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -38025,6 +38611,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);
@@ -38144,6 +38742,7 @@
field public static final int PURPOSE_ENCRYPT = 1; // 0x1
field public static final int PURPOSE_SIGN = 4; // 0x4
field public static final int PURPOSE_VERIFY = 8; // 0x8
+ field public static final int PURPOSE_WRAP_KEY = 16; // 0x10
field public static final java.lang.String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
field public static final java.lang.String SIGNATURE_PADDING_RSA_PSS = "PSS";
}
@@ -38197,17 +38796,25 @@
ctor public AutoFillService();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
+ method public void onDatasetAuthenticationRequest(android.os.Bundle, int);
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
- method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+ method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
+ method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
+ field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
+ field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1
+ field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2
+ field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
public final class FillCallback {
+ method public void onDatasetAuthentication(android.view.autofill.Dataset, int);
method public void onFailure(java.lang.CharSequence);
+ method public void onFillResponseAuthentication(int);
method public void onSuccess(android.view.autofill.FillResponse);
}
@@ -38492,6 +39099,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";
}
@@ -38509,6 +39117,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();
@@ -38529,9 +39138,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
@@ -38556,9 +39163,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
@@ -38568,6 +39175,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();
@@ -38609,7 +39217,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();
@@ -38732,6 +39339,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public boolean onConfigure(java.util.List<android.os.PersistableBundle>);
method public void onDeviceLocked();
+ method public void onDeviceUnlockLockout(long);
method public void onDeviceUnlocked();
method public void onTrustTimeout();
method public void onUnlockAttempt(boolean);
@@ -39069,6 +39677,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);
}
@@ -39221,6 +39830,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 {
@@ -40165,6 +40775,7 @@
method public void onReject();
method public void onReject(java.lang.String);
method public void onSeparate();
+ method public void onShowIncomingCallUi();
method public void onStateChanged(int);
method public void onStopDtmfTone();
method public void onUnhold();
@@ -40176,6 +40787,7 @@
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
method public final void setAudioModeIsVoip(boolean);
+ method public final void setAudioRoute(int);
method public final void setCallerDisplayName(java.lang.String, int);
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
@@ -40224,6 +40836,7 @@
field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
+ field public static final int PROPERTY_SELF_MANAGED = 128; // 0x80
field public static final int STATE_ACTIVE = 4; // 0x4
field public static final int STATE_DIALING = 3; // 0x3
field public static final int STATE_DISCONNECTED = 6; // 0x6
@@ -40291,7 +40904,9 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConference(android.telecom.Connection, android.telecom.Connection);
method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest);
method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest);
method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
@@ -40528,6 +41143,7 @@
field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
field public static final int CAPABILITY_MULTI_USER = 32; // 0x20
field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
+ field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
@@ -40766,6 +41382,8 @@
method public boolean handleMmi(java.lang.String);
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
+ method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
+ method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isRinging();
method public boolean isTtySupported();
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
@@ -40778,7 +41396,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";
@@ -40888,7 +41506,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";
@@ -40899,6 +41518,7 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
+ field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
@@ -40980,9 +41600,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";
}
@@ -41508,6 +42132,7 @@
method public void enableVideoCalling(boolean);
method public boolean endCall();
method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
+ method public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
method public int getCallState();
method public android.os.PersistableBundle getCarrierConfig();
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
@@ -41551,6 +42176,7 @@
method public int getSimState(int);
method public java.lang.String getSubscriberId();
method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
+ method public java.lang.String getVisualVoicemailPackageName(android.telecom.PhoneAccountHandle);
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
method public int getVoiceNetworkType();
@@ -41564,6 +42190,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();
@@ -41583,6 +42210,7 @@
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
+ method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method public void setDataEnabled(boolean);
method public void setDataEnabled(int, boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
@@ -41627,6 +42255,7 @@
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
+ field public static final java.lang.String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
@@ -41635,6 +42264,7 @@
field public static final java.lang.String EXTRA_STATE_OFFHOOK;
field public static final java.lang.String EXTRA_STATE_RINGING;
field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
+ field public static final java.lang.String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -41841,6 +42471,15 @@
}
+package android.telephony.ims {
+
+ public class ImsServiceBase extends android.app.Service {
+ ctor public ImsServiceBase();
+ method public android.os.IBinder onBind(android.content.Intent);
+ }
+
+}
+
package android.test {
public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
@@ -42185,6 +42824,7 @@
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public void clearWallpaper();
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+ method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.Context createCredentialProtectedStorageContext();
method public android.content.Context createDeviceProtectedStorageContext();
method public android.content.Context createDisplayContext(android.view.Display);
@@ -42274,6 +42914,7 @@
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public android.content.ComponentName startService(android.content.Intent);
+ method public android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
@@ -42337,6 +42978,7 @@
method public boolean addPermission(android.content.pm.PermissionInfo);
method public boolean addPermissionAsync(android.content.pm.PermissionInfo);
method public void addPreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
+ method public boolean canRequestPackageInstalls();
method public java.lang.String[] canonicalToCurrentPackageNames(java.lang.String[]);
method public int checkPermission(java.lang.String, java.lang.String);
method public int checkSignatures(java.lang.String, java.lang.String);
@@ -42364,18 +43006,22 @@
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);
method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInstaller getPackageInstaller();
method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.lang.String[] getPackagesForUid(int);
@@ -42391,6 +43037,7 @@
method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public java.lang.String[] getSystemSharedLibraryNames();
method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
@@ -42421,7 +43068,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);
@@ -42625,6 +43274,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);
}
@@ -42980,22 +43688,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);
@@ -43011,13 +43703,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);
@@ -43030,13 +43715,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...);
@@ -44590,6 +45268,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);
@@ -45290,12 +45969,15 @@
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
field public static final int FLAG_ROUND = 16; // 0x10
field public static final int FLAG_SECURE = 2; // 0x2
+ field public static final int FLAG_SHOW_WITH_INSECURE_LOCKSCREEN = 32; // 0x20
field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
field public static final int INVALID_DISPLAY = -1; // 0xffffffff
field public static final int STATE_DOZE = 3; // 0x3
@@ -45359,7 +46041,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();
}
@@ -45542,6 +46224,7 @@
field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
field public static final int SOURCE_KEYBOARD = 257; // 0x101
field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -46144,7 +46827,7 @@
method public abstract android.view.SubMenu getSubMenu();
method public abstract java.lang.CharSequence getTitle();
method public abstract java.lang.CharSequence getTitleCondensed();
- method public default java.lang.CharSequence getTooltip();
+ method public default java.lang.CharSequence getTooltipText();
method public abstract boolean hasSubMenu();
method public abstract boolean isActionViewExpanded();
method public abstract boolean isCheckable();
@@ -46171,7 +46854,7 @@
method public abstract android.view.MenuItem setTitle(java.lang.CharSequence);
method public abstract android.view.MenuItem setTitle(int);
method public abstract android.view.MenuItem setTitleCondensed(java.lang.CharSequence);
- method public default android.view.MenuItem setTooltip(java.lang.CharSequence);
+ method public default android.view.MenuItem setTooltipText(java.lang.CharSequence);
method public abstract android.view.MenuItem setVisible(boolean);
field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
@@ -46657,7 +47340,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>);
@@ -46696,6 +47379,7 @@
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
+ method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
method public boolean dispatchDragEvent(android.view.DragEvent);
@@ -46714,6 +47398,7 @@
method public boolean dispatchNestedPrePerformAccessibilityAction(int, android.os.Bundle);
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
+ method public void dispatchPointerCaptureChanged(boolean);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void dispatchProvideAutoFillStructure(android.view.ViewStructure, int);
method public void dispatchProvideStructure(android.view.ViewStructure);
@@ -46779,6 +47464,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();
@@ -46821,7 +47507,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();
@@ -46865,7 +47550,7 @@
method public java.lang.Object getTag(int);
method public int getTextAlignment();
method public int getTextDirection();
- method public final java.lang.CharSequence getTooltip();
+ method public final java.lang.CharSequence getTooltipText();
method public final int getTop();
method protected float getTopFadingEdgeStrength();
method protected int getTopPaddingOffset();
@@ -46895,6 +47580,7 @@
method public boolean hasNestedScrollingParent();
method public boolean hasOnClickListeners();
method public boolean hasOverlappingRendering();
+ method public boolean hasPointerCapture();
method public boolean hasTransientState();
method public boolean hasWindowFocus();
method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup);
@@ -46926,7 +47612,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();
@@ -46949,7 +47634,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[]);
@@ -46960,6 +47645,7 @@
method public android.view.WindowInsets onApplyWindowInsets(android.view.WindowInsets);
method protected void onAttachedToWindow();
method public void onCancelPendingInputEvents();
+ method public boolean onCapturedPointerEvent(android.view.MotionEvent);
method public boolean onCheckIsTextEditor();
method protected void onConfigurationChanged(android.content.res.Configuration);
method protected void onCreateContextMenu(android.view.ContextMenu);
@@ -46989,6 +47675,7 @@
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
+ method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void onProvideAutoFillStructure(android.view.ViewStructure, int);
method public void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
@@ -47031,6 +47718,7 @@
method public void postOnAnimation(java.lang.Runnable);
method public void postOnAnimationDelayed(java.lang.Runnable, long);
method public void refreshDrawableState();
+ method public void releasePointerCapture();
method public boolean removeCallbacks(java.lang.Runnable);
method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
@@ -47041,6 +47729,7 @@
method public boolean requestFocus(int, android.graphics.Rect);
method public final boolean requestFocusFromTouch();
method public void requestLayout();
+ method public void requestPointerCapture();
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
method public final void requestUnbufferedDispatch(android.view.MotionEvent);
@@ -47084,6 +47773,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);
@@ -47099,7 +47789,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);
@@ -47117,8 +47806,8 @@
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 setOnCapturedPointerListener(android.view.View.OnCapturedPointerListener);
method public void setOnClickListener(android.view.View.OnClickListener);
method public void setOnContextClickListener(android.view.View.OnContextClickListener);
method public void setOnCreateContextMenuListener(android.view.View.OnCreateContextMenuListener);
@@ -47166,7 +47855,7 @@
method public void setTag(int, java.lang.Object);
method public void setTextAlignment(int);
method public void setTextDirection(int);
- method public final void setTooltip(java.lang.CharSequence);
+ method public final void setTooltipText(java.lang.CharSequence);
method public final void setTop(int);
method public void setTouchDelegate(android.view.TouchDelegate);
method public final void setTransitionName(java.lang.String);
@@ -47201,8 +47890,8 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
- field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
- field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+ field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+ field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -47223,8 +47912,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;
@@ -47243,8 +47934,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
@@ -47256,6 +47945,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
@@ -47390,6 +48080,10 @@
method public abstract void onViewDetachedFromWindow(android.view.View);
}
+ public static abstract interface View.OnCapturedPointerListener {
+ method public abstract boolean onCapturedPointer(android.view.View, android.view.MotionEvent);
+ }
+
public static abstract interface View.OnClickListener {
method public abstract void onClick(android.view.View);
}
@@ -47763,7 +48457,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);
@@ -47960,6 +48654,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();
@@ -48016,6 +48711,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);
@@ -48121,6 +48817,7 @@
method public abstract boolean onMenuItemSelected(int, android.view.MenuItem);
method public abstract boolean onMenuOpened(int, android.view.Menu);
method public abstract void onPanelClosed(int, android.view.Menu);
+ method public default void onPointerCaptureChanged(boolean);
method public abstract boolean onPreparePanel(int, android.view.View, android.view.Menu);
method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
method public abstract boolean onSearchRequested();
@@ -48215,9 +48912,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);
@@ -49087,6 +49786,13 @@
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
}
+ public final class AutoFillManager {
+ method public void updateAutoFillInput(android.view.View, int);
+ method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
+ field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
+ field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+ }
+
public final class AutoFillType implements android.os.Parcelable {
method public int describeContents();
method public static android.view.autofill.AutoFillType forList();
@@ -49121,6 +49827,8 @@
public static final class Dataset.Builder {
ctor public Dataset.Builder(java.lang.CharSequence);
method public android.view.autofill.Dataset build();
+ method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
}
@@ -49136,6 +49844,8 @@
method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
method public android.view.autofill.FillResponse build();
+ method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
}
@@ -49146,7 +49856,7 @@
public static abstract class VirtualViewDelegate.Callback {
ctor public VirtualViewDelegate.Callback();
- method public void onFocusChanged(int, boolean);
+ method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
method public void onNodeRemoved(int...);
method public void onValueChanged(int);
}
@@ -49553,6 +50263,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 {
@@ -49837,6 +50624,7 @@
public abstract class RenderProcessGoneDetail {
ctor public RenderProcessGoneDetail();
method public abstract boolean didCrash();
+ method public abstract int rendererPriorityAtExit();
}
public class ServiceWorkerClient {
@@ -50292,6 +51080,8 @@
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
method public java.lang.String getOriginalUrl();
method public int getProgress();
+ method public boolean getRendererPriorityWaivedWhenNotVisible();
+ method public int getRendererRequestedPriority();
method public deprecated float getScale();
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
@@ -50340,6 +51130,7 @@
method public deprecated void setMapTrackballToArrowKeys(boolean);
method public void setNetworkAvailable(boolean);
method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
+ method public void setRendererPriorityPolicy(int, boolean);
method public deprecated void setVerticalScrollbarOverlay(boolean);
method public void setWebChromeClient(android.webkit.WebChromeClient);
method public static void setWebContentsDebuggingEnabled(boolean);
@@ -50350,6 +51141,9 @@
method public boolean zoomIn();
method public boolean zoomOut();
field public static final java.lang.String DATA_REDUCTION_PROXY_SETTING_CHANGED = "android.webkit.DATA_REDUCTION_PROXY_SETTING_CHANGED";
+ field public static final int RENDERER_PRIORITY_BOUND = 1; // 0x1
+ field public static final int RENDERER_PRIORITY_IMPORTANT = 2; // 0x2
+ field public static final int RENDERER_PRIORITY_WAIVED = 0; // 0x0
field public static final java.lang.String SCHEME_GEO = "geo:0,0?q=";
field public static final java.lang.String SCHEME_MAILTO = "mailto:";
field public static final java.lang.String SCHEME_TEL = "tel:";
@@ -50483,6 +51277,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);
}
@@ -50571,6 +51366,8 @@
method public abstract java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
method public abstract java.lang.String getOriginalUrl();
method public abstract int getProgress();
+ method public abstract boolean getRendererPriorityWaivedWhenNotVisible();
+ method public abstract int getRendererRequestedPriority();
method public abstract float getScale();
method public abstract android.webkit.WebViewProvider.ScrollDelegate getScrollDelegate();
method public abstract android.webkit.WebSettings getSettings();
@@ -50625,6 +51422,7 @@
method public abstract void setMapTrackballToArrowKeys(boolean);
method public abstract void setNetworkAvailable(boolean);
method public abstract void setPictureListener(android.webkit.WebView.PictureListener);
+ method public abstract void setRendererPriorityPolicy(int, boolean);
method public abstract void setVerticalScrollbarOverlay(boolean);
method public abstract void setWebChromeClient(android.webkit.WebChromeClient);
method public abstract void setWebViewClient(android.webkit.WebViewClient);
@@ -52917,6 +53715,10 @@
method public void endBatchEdit();
method public boolean extractText(android.view.inputmethod.ExtractedTextRequest, android.view.inputmethod.ExtractedText);
method public final int getAutoLinkMask();
+ method public int getAutoSizeMaxTextSize();
+ method public int getAutoSizeMinTextSize();
+ method public int getAutoSizeStepGranularity();
+ method public int getAutoSizeTextType();
method public int getBreakStrategy();
method public int getCompoundDrawablePadding();
method public android.content.res.ColorStateList getCompoundDrawableTintList();
@@ -52942,7 +53744,6 @@
method public int getExtendedPaddingTop();
method public android.text.InputFilter[] getFilters();
method public java.lang.String getFontFeatureSettings();
- method public java.lang.String getFontVariationSettings();
method public boolean getFreezesText();
method public int getGravity();
method public int getHighlightColor();
@@ -52989,7 +53790,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();
@@ -53026,6 +53827,10 @@
method public void removeTextChangedListener(android.text.TextWatcher);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
+ method public void setAutoSizeMaxTextSize(int, float);
+ method public void setAutoSizeMinTextSize(int, float);
+ method public void setAutoSizeStepGranularity(int, float);
+ method public void setAutoSizeTextType(int);
method public void setBreakStrategy(int);
method public void setCompoundDrawablePadding(int);
method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
@@ -53048,7 +53853,6 @@
method public void setExtractedText(android.view.inputmethod.ExtractedText);
method public void setFilters(android.text.InputFilter[]);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
method protected boolean setFrame(int, int, int, int);
method public void setFreezesText(boolean);
method public void setGravity(int);
@@ -53102,7 +53906,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);
@@ -53117,8 +53921,8 @@
method public void setTypeface(android.graphics.Typeface, int);
method public void setTypeface(android.graphics.Typeface);
method public void setWidth(int);
- field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0
- field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1
+ field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
+ field public static final int AUTO_SIZE_TEXT_TYPE_XY = 1; // 0x1
}
public static final class TextView.BufferType extends java.lang.Enum {
@@ -65245,6 +66049,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>);
@@ -65255,7 +66062,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);
@@ -65284,12 +66095,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 5fe855d..334f4e6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -83,6 +83,7 @@
field public static final java.lang.String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
+ field public static final java.lang.String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final java.lang.String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
field public static final java.lang.String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
@@ -113,6 +114,7 @@
field public static final java.lang.String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
field public static final java.lang.String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
+ field public static final java.lang.String REQUEST_DELETE_PACKAGES = "android.permission.REQUEST_DELETE_PACKAGES";
field public static final java.lang.String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
@@ -201,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
@@ -361,6 +365,7 @@
field public static final int centerMedium = 16842959; // 0x10100cf
field public static final int centerX = 16843170; // 0x10101a2
field public static final int centerY = 16843171; // 0x10101a3
+ field public static final int certDigest = 16844106; // 0x101054a
field public static final int checkBoxPreferenceStyle = 16842895; // 0x101008f
field public static final int checkMark = 16843016; // 0x1010108
field public static final int checkMarkTint = 16843943; // 0x10104a7
@@ -403,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
@@ -733,6 +739,7 @@
field public static final int isScrollContainer = 16843342; // 0x101024e
field public static final int isSticky = 16843335; // 0x1010247
field public static final int isolatedProcess = 16843689; // 0x10103a9
+ field public static final int isolatedSplits = 16844109; // 0x101054d
field public static final int itemBackground = 16843056; // 0x1010130
field public static final int itemIconDisabledAlpha = 16843057; // 0x1010131
field public static final int itemPadding = 16843565; // 0x101032d
@@ -755,7 +762,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
@@ -905,7 +911,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
@@ -1053,6 +1058,7 @@
field public static final int resizeable = 16843405; // 0x101028d
field public static final int resizeableActivity = 16844022; // 0x10104f6
field public static final int resource = 16842789; // 0x1010025
+ field public static final int restartOnConfigChanges = 16844105; // 0x1010549
field public static final int restoreAnyVersion = 16843450; // 0x10102ba
field public static final deprecated int restoreNeedsApplication = 16843421; // 0x101029d
field public static final int restrictedAccountType = 16843733; // 0x10103d5
@@ -1165,6 +1171,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
@@ -1377,7 +1384,7 @@
field public static final int toYDelta = 16843209; // 0x10101c9
field public static final int toYScale = 16843205; // 0x10101c5
field public static final int toolbarStyle = 16843946; // 0x10104aa
- field public static final int tooltip = 16844084; // 0x1010534
+ field public static final int tooltipText = 16844084; // 0x1010534
field public static final int top = 16843182; // 0x10101ae
field public static final int topBright = 16842955; // 0x10100cb
field public static final int topDark = 16842951; // 0x10100c7
@@ -1436,7 +1443,7 @@
field public static final int viewportWidth = 16843778; // 0x1010402
field public static final int visibility = 16842972; // 0x10100dc
field public static final int visible = 16843156; // 0x1010194
- field public static final int visibleToEphemeral = 16844095; // 0x101053f
+ field public static final int visibleToInstantApps = 16844095; // 0x101053f
field public static final int vmSafeMode = 16843448; // 0x10102b8
field public static final int voiceIcon = 16843908; // 0x1010484
field public static final int voiceLanguage = 16843349; // 0x1010255
@@ -1813,6 +1820,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
@@ -2879,15 +2887,18 @@
public class AccountManager {
method public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle);
- method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle, int[]);
+ method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle, java.util.Map<java.lang.Integer, java.lang.Integer>);
method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean);
+ method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, java.lang.String[]);
method public java.lang.String blockingGetAuthToken(android.accounts.Account, java.lang.String, boolean) throws android.accounts.AuthenticatorException, java.io.IOException, android.accounts.OperationCanceledException;
method public void clearPassword(android.accounts.Account);
method public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(java.lang.String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public static android.accounts.AccountManager get(android.content.Context);
+ method public int getAccountVisibility(android.accounts.Account, int);
method public android.accounts.Account[] getAccounts();
+ method public java.util.Map<android.accounts.Account, java.lang.Integer> getAccountsAndVisibilityForPackage(java.lang.String, java.lang.String);
method public android.accounts.Account[] getAccountsByType(java.lang.String);
method public android.accounts.AccountManagerFuture<android.accounts.Account[]> getAccountsByTypeAndFeatures(java.lang.String, java.lang.String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler);
method public android.accounts.Account[] getAccountsByTypeForPackage(java.lang.String, java.lang.String);
@@ -2898,13 +2909,11 @@
method public android.accounts.AuthenticatorDescription[] getAuthenticatorTypes();
method public java.lang.String getPassword(android.accounts.Account);
method public java.lang.String getPreviousName(android.accounts.Account);
- method public int[] getRequestingUidsForType(java.lang.String);
+ method public java.util.Map<java.lang.Integer, java.lang.Integer> getUidsAndVisibilityForAccount(android.accounts.Account);
method public java.lang.String getUserData(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void invalidateAuthToken(java.lang.String, java.lang.String);
- method public boolean isAccountVisible(android.accounts.Account, int);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> isCredentialsUpdateSuggested(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
- method public boolean makeAccountVisible(android.accounts.Account, int);
method public static deprecated android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, java.lang.String[], boolean, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public static android.content.Intent newChooseAccountIntent(android.accounts.Account, java.util.List<android.accounts.Account>, java.lang.String[], java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle);
method public boolean notifyAccountAuthenticated(android.accounts.Account);
@@ -2912,9 +2921,9 @@
method public deprecated android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public boolean removeAccountExplicitly(android.accounts.Account);
- method public boolean removeAccountVisibility(android.accounts.Account, int);
method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener);
method public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler);
+ method public boolean setAccountVisibility(android.accounts.Account, int, int);
method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
method public void setPassword(android.accounts.Account, java.lang.String);
method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
@@ -2953,7 +2962,14 @@
field public static final java.lang.String KEY_LAST_AUTHENTICATED_TIME = "lastAuthenticatedTime";
field public static final java.lang.String KEY_PASSWORD = "password";
field public static final java.lang.String KEY_USERDATA = "userdata";
- field public static final java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
+ field public static final deprecated java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
+ field public static final int UID_KEY_DEFAULT_LEGACY_VISIBILITY = -3; // 0xfffffffd
+ field public static final int UID_KEY_DEFAULT_VISIBILITY = -2; // 0xfffffffe
+ field public static final int VISIBILITY_NOT_VISIBLE = 3; // 0x3
+ field public static final int VISIBILITY_UNDEFINED = 0; // 0x0
+ field public static final int VISIBILITY_USER_MANAGED_NOT_VISIBLE = 4; // 0x4
+ field public static final int VISIBILITY_USER_MANAGED_VISIBLE = 2; // 0x2
+ field public static final int VISIBILITY_VISIBLE = 1; // 0x1
}
public abstract interface AccountManagerCallback<V> {
@@ -3052,8 +3068,10 @@
public static abstract interface Animator.AnimatorListener {
method public abstract void onAnimationCancel(android.animation.Animator);
+ method public default void onAnimationEnd(android.animation.Animator, boolean);
method public abstract void onAnimationEnd(android.animation.Animator);
method public abstract void onAnimationRepeat(android.animation.Animator);
+ method public default void onAnimationStart(android.animation.Animator, boolean);
method public abstract void onAnimationStart(android.animation.Animator);
}
@@ -3089,6 +3107,8 @@
method public void playSequentially(java.util.List<android.animation.Animator>);
method public void playTogether(android.animation.Animator...);
method public void playTogether(java.util.Collection<android.animation.Animator>);
+ method public void reverse();
+ method public void setCurrentPlayTime(long);
method public android.animation.AnimatorSet setDuration(long);
method public void setInterpolator(android.animation.TimeInterpolator);
method public void setStartDelay(long);
@@ -3493,8 +3513,7 @@
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
- method public void enterPictureInPictureMode(float);
- method public void enterPictureInPictureModeOnMoveToBackground(boolean);
+ method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -3527,7 +3546,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();
@@ -3667,8 +3685,7 @@
method public void setIntent(android.content.Intent);
method public final void setMediaController(android.media.session.MediaController);
method public void setOverlayWithDecorCaptionEnabled(boolean);
- method public void setPictureInPictureActions(java.util.List<android.app.RemoteAction>);
- method public void setPictureInPictureAspectRatio(float);
+ method public void setPictureInPictureArgs(android.app.PictureInPictureArgs);
method public final deprecated void setProgress(int);
method public final deprecated void setProgressBarIndeterminate(boolean);
method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -3678,7 +3695,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);
@@ -4838,11 +4854,13 @@
public static class Instrumentation.ActivityMonitor {
ctor public Instrumentation.ActivityMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
ctor public Instrumentation.ActivityMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
+ ctor public Instrumentation.ActivityMonitor();
method public final android.content.IntentFilter getFilter();
method public final int getHits();
method public final android.app.Activity getLastActivity();
method public final android.app.Instrumentation.ActivityResult getResult();
method public final boolean isBlocking();
+ method public android.app.Instrumentation.ActivityResult onMatchIntent(android.content.Intent);
method public final android.app.Activity waitForActivity();
method public final android.app.Activity waitForActivityWithTimeout(long);
}
@@ -5014,6 +5032,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";
@@ -5041,8 +5060,10 @@
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+ field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+ field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
@@ -5186,7 +5207,8 @@
}
public static class Notification.Builder {
- ctor public Notification.Builder(android.content.Context);
+ ctor public Notification.Builder(android.content.Context, java.lang.String);
+ ctor public deprecated Notification.Builder(android.content.Context);
method public deprecated android.app.Notification.Builder addAction(int, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.Notification.Builder addAction(android.app.Notification.Action);
method public android.app.Notification.Builder addExtras(android.os.Bundle);
@@ -5205,6 +5227,7 @@
method public android.app.Notification.Builder setChannel(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
method public android.app.Notification.Builder setColor(int);
+ method public android.app.Notification.Builder setColorized(boolean);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
@@ -5242,6 +5265,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);
@@ -5308,9 +5332,11 @@
public static class Notification.MessagingStyle extends android.app.Notification.Style {
ctor public Notification.MessagingStyle(java.lang.CharSequence);
+ method public android.app.Notification.MessagingStyle addHistoricMessage(android.app.Notification.MessagingStyle.Message);
method public android.app.Notification.MessagingStyle addMessage(java.lang.CharSequence, long, java.lang.CharSequence);
method public android.app.Notification.MessagingStyle addMessage(android.app.Notification.MessagingStyle.Message);
method public java.lang.CharSequence getConversationTitle();
+ method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
method public java.lang.CharSequence getUserDisplayName();
method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
@@ -5405,14 +5431,15 @@
method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
+ method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
method public int getLockscreenVisibility();
method public java.lang.CharSequence getName();
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
- method public boolean isAllowed();
method public void setBypassDnd(boolean);
+ method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
@@ -5426,6 +5453,17 @@
field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
}
+ public final class NotificationChannelGroup implements android.os.Parcelable {
+ ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+ ctor protected NotificationChannelGroup(android.os.Parcel);
+ method public int describeContents();
+ method public java.util.List<android.app.NotificationChannel> getChannels();
+ method public java.lang.String getId();
+ method public java.lang.CharSequence getName();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
+ }
+
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
@@ -5433,6 +5471,8 @@
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public void createNotificationChannel(android.app.NotificationChannel);
+ method public void createNotificationChannelGroup(android.app.NotificationChannelGroup);
+ method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>);
method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>);
method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
@@ -5450,6 +5490,7 @@
method public boolean removeAutomaticZenRule(java.lang.String);
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
+ method public android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
@@ -5535,6 +5576,17 @@
method public abstract void onSendFinished(android.app.PendingIntent, android.content.Intent, int, java.lang.String, android.os.Bundle);
}
+ public final class PictureInPictureArgs implements android.os.Parcelable {
+ ctor public PictureInPictureArgs();
+ ctor public PictureInPictureArgs(float, java.util.List<android.app.RemoteAction>);
+ method public android.app.PictureInPictureArgs clone();
+ method public int describeContents();
+ method public void setActions(java.util.List<android.app.RemoteAction>);
+ method public void setAspectRatio(float);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.PictureInPictureArgs> CREATOR;
+ }
+
public class Presentation extends android.app.Dialog {
ctor public Presentation(android.content.Context, android.view.Display);
ctor public Presentation(android.content.Context, android.view.Display, int);
@@ -5571,6 +5623,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();
@@ -6094,6 +6158,7 @@
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+ method public android.content.Intent createAdminSupportIntent(java.lang.String);
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
method public void enableSystemApp(android.content.ComponentName, java.lang.String);
method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
@@ -6102,16 +6167,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);
@@ -6159,7 +6226,8 @@
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
- method public boolean isCallerApplicationRestrictionsManagingPackage();
+ method public boolean isBackupServiceEnabled(android.content.ComponentName);
+ method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
method public boolean isDeviceManaged();
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isLockTaskPermitted(java.lang.String);
@@ -6188,13 +6256,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);
@@ -6242,6 +6312,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";
@@ -6251,6 +6322,12 @@
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_BLOCK_UNINSTALL = "delegation-block-uninstall";
+ field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+ field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+ field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+ field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
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
@@ -6258,6 +6335,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";
@@ -6289,6 +6367,7 @@
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID";
+ field public static final java.lang.String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
field public static final int FLAG_EVICT_CE_KEY = 1; // 0x1
field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
@@ -6316,6 +6395,8 @@
field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+ field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+ field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -6363,8 +6444,12 @@
public final class SystemUpdateInfo implements android.os.Parcelable {
method public int describeContents();
method public long getReceivedTime();
+ method public int getSecurityPatchState();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdateInfo> CREATOR;
+ field public static final int SECURITY_PATCH_STATE_FALSE = 1; // 0x1
+ field public static final int SECURITY_PATCH_STATE_TRUE = 2; // 0x2
+ field public static final int SECURITY_PATCH_STATE_UNKNOWN = 0; // 0x0
}
public class SystemUpdatePolicy implements android.os.Parcelable {
@@ -6849,6 +6934,7 @@
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
+ method public boolean isRequestPinAppWidgetSupported();
method public void notifyAppWidgetViewDataChanged(int[], int);
method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
@@ -7950,6 +8036,7 @@
ctor public ClipData(android.content.ClipDescription, android.content.ClipData.Item);
ctor public ClipData(android.content.ClipData);
method public void addItem(android.content.ClipData.Item);
+ method public void addItem(android.content.ClipData.Item, android.content.ContentResolver);
method public int describeContents();
method public android.content.ClipDescription getDescription();
method public android.content.ClipData.Item getItemAt(int);
@@ -8336,6 +8423,7 @@
method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public abstract deprecated void clearWallpaper() throws java.io.IOException;
method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+ method public abstract android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.Context createDeviceProtectedStorageContext();
method public abstract android.content.Context createDisplayContext(android.view.Display);
method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8470,6 +8558,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";
@@ -8534,6 +8623,7 @@
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public deprecated void clearWallpaper() throws java.io.IOException;
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+ method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.Context createDeviceProtectedStorageContext();
method public android.content.Context createDisplayContext(android.view.Display);
method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8843,6 +8933,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";
@@ -9557,6 +9648,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 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
@@ -9617,6 +9712,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;
@@ -9715,6 +9811,7 @@
field public int requiresSmallestWidthDp;
field public java.lang.String[] sharedLibraryFiles;
field public java.lang.String sourceDir;
+ field public java.lang.String[] splitNames;
field public java.lang.String[] splitPublicSourceDirs;
field public java.lang.String[] splitSourceDirs;
field public int targetSdkVersion;
@@ -9722,6 +9819,7 @@
field public int theme;
field public int uiOptions;
field public int uid;
+ field public java.lang.String volumeUuid;
}
public static class ApplicationInfo.DisplayNameComparator implements java.util.Comparator {
@@ -9743,6 +9841,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 {
@@ -9796,6 +9895,7 @@
field public boolean handleProfiling;
field public java.lang.String publicSourceDir;
field public java.lang.String sourceDir;
+ field public java.lang.String[] splitNames;
field public java.lang.String[] splitPublicSourceDirs;
field public java.lang.String[] splitSourceDirs;
field public java.lang.String targetPackage;
@@ -9831,6 +9931,7 @@
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
+ method public java.util.List<android.os.UserHandle> getProfiles();
method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
method public android.content.IntentSender getShortcutConfigActivityIntent(android.content.pm.LauncherActivityInfo);
method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(java.lang.String, android.os.UserHandle);
@@ -9848,7 +9949,8 @@
method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
- field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM";
+ field public static final java.lang.String ACTION_CONFIRM_PIN_APPWIDGET = "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
+ field public static final java.lang.String ACTION_CONFIRM_PIN_SHORTCUT = "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}
@@ -9868,7 +9970,7 @@
method public boolean accept(android.os.Bundle);
method public boolean accept();
method public int describeContents();
- method public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo();
+ method public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo(android.content.Context);
method public int getRequestType();
method public android.content.pm.ShortcutInfo getShortcutInfo();
method public boolean isValid();
@@ -9937,6 +10039,7 @@
method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler);
method public void uninstall(java.lang.String, android.content.IntentSender);
+ method public void uninstall(android.content.pm.VersionedPackage, android.content.IntentSender);
method public void unregisterSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
method public void updateSessionAppIcon(int, android.graphics.Bitmap);
method public void updateSessionAppLabel(int, java.lang.CharSequence);
@@ -10045,6 +10148,7 @@
method public abstract boolean addPermission(android.content.pm.PermissionInfo);
method public abstract boolean addPermissionAsync(android.content.pm.PermissionInfo);
method public abstract deprecated void addPreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
+ method public abstract boolean canRequestPackageInstalls();
method public abstract java.lang.String[] canonicalToCurrentPackageNames(java.lang.String[]);
method public abstract int checkPermission(java.lang.String, java.lang.String);
method public abstract int checkSignatures(java.lang.String, java.lang.String);
@@ -10085,6 +10189,7 @@
method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.PackageInstaller getPackageInstaller();
method public abstract int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract java.lang.String[] getPackagesForUid(int);
@@ -10099,6 +10204,7 @@
method public abstract android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
method public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public abstract java.lang.String[] getSystemSharedLibraryNames();
method public abstract java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
@@ -10157,6 +10263,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";
@@ -10257,6 +10364,7 @@
field public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; // 0xfffffffc
field public static final int VERIFICATION_ALLOW = 1; // 0x1
field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
+ field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
}
public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
@@ -10397,6 +10505,20 @@
field public java.lang.String permission;
}
+ public final class SharedLibraryInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.content.pm.VersionedPackage getDeclaringPackage();
+ method public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
+ method public java.lang.String getName();
+ method public int getVersion();
+ method public boolean isBuiltin();
+ method public boolean isDynamic();
+ method public boolean isStatic();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
+ field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
+ }
+
public final class ShortcutInfo implements android.os.Parcelable {
method public int describeContents();
method public android.content.ComponentName getActivity();
@@ -10473,6 +10595,15 @@
field public static final android.os.Parcelable.Creator<android.content.pm.Signature> CREATOR;
}
+ public final class VersionedPackage implements android.os.Parcelable {
+ ctor public VersionedPackage(java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getPackageName();
+ method public long getVersionCode();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.VersionedPackage> CREATOR;
+ }
+
}
package android.content.res {
@@ -10558,7 +10689,9 @@
method public int getLayoutDirection();
method public android.os.LocaleList getLocales();
method public boolean isLayoutSizeAtLeast(int);
+ method public boolean isScreenHdr();
method public boolean isScreenRound();
+ method public boolean isScreenWideColorGamut();
method public static boolean needNewResources(int, int);
method public void readFromParcel(android.os.Parcel);
method public void setLayoutDirection(java.util.Locale);
@@ -10568,6 +10701,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 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
@@ -10633,6 +10776,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 colorMode;
field public int densityDpi;
field public float fontScale;
field public int hardKeyboardHidden;
@@ -10687,6 +10831,7 @@
method public android.graphics.drawable.Drawable getDrawable(int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
method public deprecated android.graphics.drawable.Drawable getDrawableForDensity(int, int) throws android.content.res.Resources.NotFoundException;
method public android.graphics.drawable.Drawable getDrawableForDensity(int, int, android.content.res.Resources.Theme);
+ method public android.graphics.Typeface getFont(int) throws android.content.res.Resources.NotFoundException;
method public float getFraction(int, int, int);
method public int getIdentifier(java.lang.String, java.lang.String, java.lang.String);
method public int[] getIntArray(int) throws android.content.res.Resources.NotFoundException;
@@ -12618,7 +12763,6 @@
method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt);
method public android.graphics.Paint.FontMetricsInt getFontMetricsInt();
method public float getFontSpacing();
- method public java.lang.String getFontVariationSettings();
method public int getHinting();
method public float getLetterSpacing();
method public android.graphics.MaskFilter getMaskFilter();
@@ -12676,7 +12820,6 @@
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
method public void setHinting(int);
method public void setLetterSpacing(float);
method public void setLinearText(boolean);
@@ -13174,6 +13317,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);
@@ -13194,6 +13338,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();
}
@@ -13600,6 +13752,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);
@@ -13748,6 +13917,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 {
@@ -14075,9 +14257,42 @@
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 int BLOB = 33; // 0x21
+ 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 = 22; // 0x16
+ 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();
@@ -14091,6 +14306,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
@@ -14168,6 +14384,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;
@@ -14199,6 +14426,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[]);
@@ -14328,7 +14558,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();
@@ -14743,6 +14973,7 @@
field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -14822,6 +15053,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_STATE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -14969,10 +15201,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
@@ -15045,6 +15279,7 @@
field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
+ field public static final int VIRTUAL_DISPLAY_FLAG_SHOW_WITH_INSECURE_LOCKSCREEN = 32; // 0x20
}
public static abstract interface DisplayManager.DisplayListener {
@@ -17522,6 +17757,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();
@@ -17531,6 +17775,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);
@@ -17550,9 +17796,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);
@@ -18021,6 +18277,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();
@@ -18796,6 +19060,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();
@@ -19324,6 +19616,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();
@@ -20180,7 +20501,7 @@
field public static final android.os.Parcelable.Creator<android.media.AudioAttributes> CREATOR;
field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
- field public static final int FLAG_LOW_LATENCY = 256; // 0x100
+ field public static final deprecated int FLAG_LOW_LATENCY = 256; // 0x100
field public static final int USAGE_ALARM = 4; // 0x4
field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
@@ -20621,6 +20942,7 @@
method protected deprecated int getNativeFrameCount();
method public static int getNativeOutputSampleRate(int);
method public int getNotificationMarkerPosition();
+ method public int getPerformanceMode();
method public int getPlayState();
method public int getPlaybackHeadPosition();
method public android.media.PlaybackParams getPlaybackParams();
@@ -20667,6 +20989,9 @@
field public static final int ERROR_INVALID_OPERATION = -3; // 0xfffffffd
field public static final int MODE_STATIC = 0; // 0x0
field public static final int MODE_STREAM = 1; // 0x1
+ field public static final int PERFORMANCE_MODE_LOW_LATENCY = 1; // 0x1
+ field public static final int PERFORMANCE_MODE_NONE = 0; // 0x0
+ field public static final int PERFORMANCE_MODE_POWER_SAVING = 2; // 0x2
field public static final int PLAYSTATE_PAUSED = 2; // 0x2
field public static final int PLAYSTATE_PLAYING = 3; // 0x3
field public static final int PLAYSTATE_STOPPED = 1; // 0x1
@@ -20684,6 +21009,7 @@
method public android.media.AudioTrack.Builder setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack.Builder setAudioFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
+ method public android.media.AudioTrack.Builder setPerformanceMode(int);
method public android.media.AudioTrack.Builder setSessionId(int) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack.Builder setTransferMode(int) throws java.lang.IllegalArgumentException;
}
@@ -20698,6 +21024,40 @@
method public default void onRoutingChanged(android.media.AudioRouting);
}
+ public final class BufferingParams implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getInitialBufferingMode();
+ method public int getInitialBufferingWatermarkKB();
+ method public int getInitialBufferingWatermarkMs();
+ method public int getRebufferingMode();
+ method public int getRebufferingWatermarkHighKB();
+ method public int getRebufferingWatermarkHighMs();
+ method public int getRebufferingWatermarkLowKB();
+ method public int getRebufferingWatermarkLowMs();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFERING_MODE_NONE = 0; // 0x0
+ field public static final int BUFFERING_MODE_SIZE_ONLY = 2; // 0x2
+ field public static final int BUFFERING_MODE_TIME_ONLY = 1; // 0x1
+ field public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3; // 0x3
+ field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+ }
+
+ public static class BufferingParams.Builder {
+ ctor public BufferingParams.Builder();
+ ctor public BufferingParams.Builder(android.media.BufferingParams);
+ method public android.media.BufferingParams build();
+ method public android.media.BufferingParams.Builder setInitialBufferingMode(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkKB(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingMode(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksKB(int, int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksMs(int, int);
+ }
+
public class CamcorderProfile {
method public static android.media.CamcorderProfile get(int);
method public static android.media.CamcorderProfile get(int, int);
@@ -21842,6 +22202,7 @@
public final class MediaMuxer {
ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException;
+ ctor public MediaMuxer(java.io.FileDescriptor, int) throws java.io.IOException;
method public int addTrack(android.media.MediaFormat);
method public void release();
method public void setLocation(float, float);
@@ -21870,7 +22231,9 @@
method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
method public void deselectTrack(int) throws java.lang.IllegalStateException;
method public int getAudioSessionId();
+ method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
+ method public android.media.BufferingParams getDefaultBufferingParams();
method public int getDuration();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -21893,6 +22256,7 @@
method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public deprecated void setAudioStreamType(int);
method public void setAuxEffectSendLevel(float);
+ method public void setBufferingParams(android.media.BufferingParams);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
@@ -22018,6 +22382,8 @@
method public void setLocation(float, float);
method public void setMaxDuration(int) throws java.lang.IllegalArgumentException;
method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException;
+ method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalStateException;
+ method public void setNextOutputFile(java.lang.String) throws java.io.IOException, java.lang.IllegalStateException;
method public void setOnErrorListener(android.media.MediaRecorder.OnErrorListener);
method public void setOnInfoListener(android.media.MediaRecorder.OnInfoListener);
method public void setOrientationHint(int);
@@ -22036,7 +22402,9 @@
field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64
field public static final int MEDIA_RECORDER_ERROR_UNKNOWN = 1; // 0x1
field public static final int MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800; // 0x320
+ field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING = 802; // 0x322
field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801; // 0x321
+ field public static final int MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED = 803; // 0x323
field public static final int MEDIA_RECORDER_INFO_UNKNOWN = 1; // 0x1
}
@@ -23551,6 +23919,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";
@@ -23577,7 +23946,16 @@
}
public static final class TvContract.Programs implements android.media.tv.TvContract.BaseTvColumns {
+ field public static final java.lang.String ASPECT_RATIO_16_9 = "ASPECT_RATIO_16_9";
+ field public static final java.lang.String ASPECT_RATIO_1_1 = "ASPECT_RATIO_1_1";
+ field public static final java.lang.String ASPECT_RATIO_2_3 = "ASPECT_RATIO_2_3";
+ field public static final java.lang.String ASPECT_RATIO_3_2 = "ASPECT_RATIO_3_2";
+ field public static final java.lang.String AVAILABILITY_AVAILABLE = "AVAILABILITY_AVAILABLE";
+ field public static final java.lang.String AVAILABILITY_FREE_WITH_SUBSCRIPTION = "AVAILABILITY_FREE_WITH_SUBSCRIPTION";
+ field public static final java.lang.String AVAILABILITY_PAID_CONTENT = "AVAILABILITY_PAID_CONTENT";
field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language";
+ field public static final java.lang.String COLUMN_AUTHOR = "author";
+ field public static final java.lang.String COLUMN_AVAILABILITY = "availability";
field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre";
field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id";
@@ -23586,28 +23964,73 @@
field public static final java.lang.String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
field public static final deprecated java.lang.String COLUMN_EPISODE_NUMBER = "episode_number";
field public static final java.lang.String COLUMN_EPISODE_TITLE = "episode_title";
+ field public static final java.lang.String COLUMN_INTERACTION_COUNT = "interaction_count";
+ field public static final java.lang.String COLUMN_INTERACTION_TYPE = "interaction_type";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
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_ITEM_COUNT = "item_count";
+ field public static final java.lang.String COLUMN_LIVE = "live";
+ field public static final java.lang.String COLUMN_LOGO = "logo";
field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
+ field public static final java.lang.String COLUMN_OFFER_PRICE = "offer_price";
+ field public static final java.lang.String COLUMN_POSTER_ART_ASPECT_RATIO = "poster_art_aspect_ratio";
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_RELEASE_DATE = "release_date";
+ field public static final java.lang.String COLUMN_REVIEW_RATING = "review_rating";
+ field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
field public static final deprecated java.lang.String COLUMN_SEASON_NUMBER = "season_number";
field public static final java.lang.String COLUMN_SEASON_TITLE = "season_title";
field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description";
+ field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
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_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
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_TYPE = "type";
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";
+ field public static final java.lang.String COLUMN_WATCH_NEXT_TYPE = "watch_next_type";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/program";
field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String INTERACTION_TYPE_FANS = "INTERACTION_TYPE_FANS";
+ field public static final java.lang.String INTERACTION_TYPE_FOLLOWERS = "INTERACTION_TYPE_FOLLOWERS";
+ field public static final java.lang.String INTERACTION_TYPE_LIKES = "INTERACTION_TYPE_LIKES";
+ field public static final java.lang.String INTERACTION_TYPE_LISTENS = "INTERACTION_TYPE_LISTENS";
+ field public static final java.lang.String INTERACTION_TYPE_THUMBS = "INTERACTION_TYPE_THUMBS";
+ field public static final java.lang.String INTERACTION_TYPE_VIEWERS = "INTERACTION_TYPE_VIEWERS";
+ field public static final java.lang.String INTERACTION_TYPE_VIEWS = "INTERACTION_TYPE_VIEWS";
+ field public static final java.lang.String REVIEW_RATING_STYLE_PERCENTAGE = "REVIEW_RATING_STYLE_PERCENTAGE";
+ field public static final java.lang.String REVIEW_RATING_STYLE_STARS = "REVIEW_RATING_STYLE_STARS";
+ field public static final java.lang.String REVIEW_RATING_STYLE_THUMBS_UP_DOWN = "REVIEW_RATING_STYLE_THUMBS_UP_DOWN";
+ field public static final java.lang.String TYPE_ALBUM = "TYPE_ALBUM";
+ field public static final java.lang.String TYPE_ARTIST = "TYPE_ARTIST";
+ field public static final java.lang.String TYPE_CHANNEL = "TYPE_CHANNEL";
+ field public static final java.lang.String TYPE_CLIP = "TYPE_CLIP";
+ field public static final java.lang.String TYPE_EVENT = "TYPE_EVENT";
+ field public static final java.lang.String TYPE_MOVIE = "TYPE_MOVIE";
+ field public static final java.lang.String TYPE_PLAYLIST = "TYPE_PLAYLIST";
+ field public static final java.lang.String TYPE_STATION = "TYPE_STATION";
+ field public static final java.lang.String TYPE_TRACK = "TYPE_TRACK";
+ field public static final java.lang.String TYPE_TV_EPISODE = "TYPE_TV_EPISODE";
+ field public static final java.lang.String TYPE_TV_SEASON = "TYPE_TV_SEASON";
+ field public static final java.lang.String TYPE_TV_SERIES = "TYPE_TV_SERIES";
+ field public static final java.lang.String WATCH_NEXT_TYPE_CONTINUE = "WATCH_NEXT_TYPE_CONTINUE";
+ field public static final java.lang.String WATCH_NEXT_TYPE_NEW = "WATCH_NEXT_TYPE_NEW";
+ field public static final java.lang.String WATCH_NEXT_TYPE_NEXT = "WATCH_NEXT_TYPE_NEXT";
}
public static final class TvContract.Programs.Genres {
@@ -23718,9 +24141,13 @@
method public void unregisterCallback(android.media.tv.TvInputManager.TvInputCallback);
method public void updateTvInputInfo(android.media.tv.TvInputInfo);
field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
+ field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
+ field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
+ field public static final java.lang.String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID";
+ field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME";
field public static final 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
@@ -24558,6 +24985,7 @@
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
+ method public static int getAndSetThreadStatsTag(int);
method public static long getMobileRxBytes();
method public static long getMobileRxPackets();
method public static long getMobileTxBytes();
@@ -25248,6 +25676,7 @@
method public java.security.cert.X509Certificate getCaCertificate();
method public java.security.cert.X509Certificate[] getCaCertificates();
method public java.security.cert.X509Certificate getClientCertificate();
+ method public java.security.cert.X509Certificate[] getClientCertificateChain();
method public java.lang.String getDomainSuffixMatch();
method public int getEapMethod();
method public java.lang.String getIdentity();
@@ -25261,6 +25690,7 @@
method public void setCaCertificate(java.security.cert.X509Certificate);
method public void setCaCertificates(java.security.cert.X509Certificate[]);
method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+ method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
method public void setDomainSuffixMatch(java.lang.String);
method public void setEapMethod(int);
method public void setIdentity(java.lang.String);
@@ -25336,6 +25766,7 @@
method public boolean isTdlsSupported();
method public boolean isWifiEnabled();
method public boolean pingSupplicant();
+ method public void queryPasspointIcon(long, java.lang.String);
method public boolean reassociate();
method public boolean reconnect();
method public boolean removeNetwork(int);
@@ -25346,6 +25777,10 @@
method public boolean startScan();
method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
method public int updateNetwork(android.net.wifi.WifiConfiguration);
+ field public static final java.lang.String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+ field public static final java.lang.String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+ field public static final java.lang.String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+ field public static final java.lang.String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final int ERROR_AUTHENTICATING = 1; // 0x1
@@ -25353,6 +25788,18 @@
field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
field public static final java.lang.String EXTRA_NEW_STATE = "newState";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated";
field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected";
@@ -29800,6 +30247,7 @@
method public final android.util.SizeF readSizeF();
method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
method public final android.util.SparseBooleanArray readSparseBooleanArray();
+ method public final android.util.SparseIntArray readSparseIntArray();
method public final java.lang.String readString();
method public final void readStringArray(java.lang.String[]);
method public final void readStringList(java.util.List<java.lang.String>);
@@ -29844,6 +30292,7 @@
method public final void writeSizeF(android.util.SizeF);
method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
+ method public final void writeSparseIntArray(android.util.SparseIntArray);
method public final void writeString(java.lang.String);
method public final void writeStringArray(java.lang.String[]);
method public final void writeStringList(java.util.List<java.lang.String>);
@@ -30161,6 +30610,7 @@
method public android.os.StrictMode.VmPolicy.Builder detectLeakedClosableObjects();
method public android.os.StrictMode.VmPolicy.Builder detectLeakedRegistrationObjects();
method public android.os.StrictMode.VmPolicy.Builder detectLeakedSqlLiteObjects();
+ method public android.os.StrictMode.VmPolicy.Builder detectUntaggedSockets();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeath();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
@@ -30458,14 +30908,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";
}
@@ -30593,6 +31051,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);
@@ -32924,7 +33383,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;
@@ -32938,6 +33396,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";
@@ -33303,6 +33771,7 @@
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+ field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
@@ -33321,6 +33790,7 @@
field public static final java.lang.String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
field public static final java.lang.String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
field public static final java.lang.String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
+ field public static final java.lang.String ACTION_MANAGE_EXTERNAL_SOURCES = "android.settings.action.MANAGE_EXTERNAL_SOURCES";
field public static final java.lang.String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
field public static final java.lang.String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
field public static final java.lang.String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
@@ -33356,8 +33826,10 @@
field public static final java.lang.String AUTHORITY = "settings";
field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
field public static final java.lang.String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+ field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -33468,7 +33940,7 @@
field public static final java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility";
- field public static final java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+ field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
field public static final java.lang.String LOCATION_MODE = "location_mode";
field public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2
field public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3
@@ -34025,6 +34497,8 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
+ field public static final java.lang.String ARCHIVED = "archived";
+ field public static final java.lang.String BACKED_UP = "backed_up";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -34032,6 +34506,7 @@
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
+ field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
field public static final java.lang.String IS_READ = "is_read";
field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -34039,6 +34514,7 @@
field public static final java.lang.String NUMBER = "number";
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String RESTORED = "restored";
field public static final java.lang.String SOURCE_DATA = "source_data";
field public static final java.lang.String SOURCE_PACKAGE = "source_package";
field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -35243,6 +35719,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);
@@ -35362,6 +35850,7 @@
field public static final int PURPOSE_ENCRYPT = 1; // 0x1
field public static final int PURPOSE_SIGN = 4; // 0x4
field public static final int PURPOSE_VERIFY = 8; // 0x8
+ field public static final int PURPOSE_WRAP_KEY = 16; // 0x10
field public static final java.lang.String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
field public static final java.lang.String SIGNATURE_PADDING_RSA_PSS = "PSS";
}
@@ -35415,17 +35904,25 @@
ctor public AutoFillService();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
+ method public void onDatasetAuthenticationRequest(android.os.Bundle, int);
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
- method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+ method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
+ method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
+ field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
+ field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1
+ field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2
+ field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
public final class FillCallback {
+ method public void onDatasetAuthentication(android.view.autofill.Dataset, int);
method public void onFailure(java.lang.CharSequence);
+ method public void onFillResponseAuthentication(int);
method public void onSuccess(android.view.autofill.FillResponse);
}
@@ -35710,6 +36207,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";
}
@@ -35725,6 +36223,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();
@@ -35743,8 +36242,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
@@ -35769,9 +36266,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
@@ -35779,6 +36276,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();
@@ -35820,7 +36318,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();
@@ -36236,6 +36733,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);
}
@@ -36388,6 +36886,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 {
@@ -37300,6 +37799,7 @@
method public void onReject();
method public void onReject(java.lang.String);
method public void onSeparate();
+ method public void onShowIncomingCallUi();
method public void onStateChanged(int);
method public void onStopDtmfTone();
method public void onUnhold();
@@ -37311,6 +37811,7 @@
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
method public final void setAudioModeIsVoip(boolean);
+ method public final void setAudioRoute(int);
method public final void setCallerDisplayName(java.lang.String, int);
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
@@ -37359,6 +37860,7 @@
field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
+ field public static final int PROPERTY_SELF_MANAGED = 128; // 0x80
field public static final int STATE_ACTIVE = 4; // 0x4
field public static final int STATE_DIALING = 3; // 0x3
field public static final int STATE_DISCONNECTED = 6; // 0x6
@@ -37426,7 +37928,9 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConference(android.telecom.Connection, android.telecom.Connection);
method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest);
method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest);
method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
@@ -37539,6 +38043,7 @@
field public static final int CAPABILITY_CALL_SUBJECT = 64; // 0x40
field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
+ field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
@@ -37720,6 +38225,8 @@
method public boolean handleMmi(java.lang.String);
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
+ method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
+ method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
method public void placeCall(android.net.Uri, android.os.Bundle);
method public void registerPhoneAccount(android.telecom.PhoneAccount);
@@ -37730,7 +38237,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";
@@ -37833,7 +38340,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";
@@ -37844,6 +38352,7 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
+ field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
@@ -37925,9 +38434,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";
}
@@ -38455,6 +38968,7 @@
method public int getSimState();
method public int getSimState(int);
method public java.lang.String getSubscriberId();
+ method public java.lang.String getVisualVoicemailPackageName(android.telecom.PhoneAccountHandle);
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
method public int getVoiceNetworkType();
@@ -38466,6 +38980,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();
@@ -38506,6 +39021,7 @@
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
+ field public static final java.lang.String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
@@ -38514,6 +39030,7 @@
field public static final java.lang.String EXTRA_STATE_OFFHOOK;
field public static final java.lang.String EXTRA_STATE_RINGING;
field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
+ field public static final java.lang.String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -39059,6 +39576,7 @@
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
method public void clearWallpaper();
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+ method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.Context createDeviceProtectedStorageContext();
method public android.content.Context createDisplayContext(android.view.Display);
method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -39144,6 +39662,7 @@
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public android.content.ComponentName startService(android.content.Intent);
+ method public android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
@@ -39206,6 +39725,7 @@
method public boolean addPermission(android.content.pm.PermissionInfo);
method public boolean addPermissionAsync(android.content.pm.PermissionInfo);
method public void addPreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
+ method public boolean canRequestPackageInstalls();
method public java.lang.String[] canonicalToCurrentPackageNames(java.lang.String[]);
method public int checkPermission(java.lang.String, java.lang.String);
method public int checkSignatures(java.lang.String, java.lang.String);
@@ -39246,6 +39766,7 @@
method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.PackageInstaller getPackageInstaller();
method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.lang.String[] getPackagesForUid(int);
@@ -39260,6 +39781,7 @@
method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public java.lang.String[] getSystemSharedLibraryNames();
method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
@@ -39489,6 +40011,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);
}
@@ -39844,22 +40425,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);
@@ -39875,13 +40440,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);
@@ -39894,13 +40452,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...);
@@ -42319,12 +42870,15 @@
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
field public static final int FLAG_ROUND = 16; // 0x10
field public static final int FLAG_SECURE = 2; // 0x2
+ field public static final int FLAG_SHOW_WITH_INSECURE_LOCKSCREEN = 32; // 0x20
field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
field public static final int INVALID_DISPLAY = -1; // 0xffffffff
field public static final int STATE_DOZE = 3; // 0x3
@@ -42388,7 +42942,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();
}
@@ -42571,6 +43125,7 @@
field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
field public static final int SOURCE_KEYBOARD = 257; // 0x101
field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -43173,7 +43728,7 @@
method public abstract android.view.SubMenu getSubMenu();
method public abstract java.lang.CharSequence getTitle();
method public abstract java.lang.CharSequence getTitleCondensed();
- method public default java.lang.CharSequence getTooltip();
+ method public default java.lang.CharSequence getTooltipText();
method public abstract boolean hasSubMenu();
method public abstract boolean isActionViewExpanded();
method public abstract boolean isCheckable();
@@ -43200,7 +43755,7 @@
method public abstract android.view.MenuItem setTitle(java.lang.CharSequence);
method public abstract android.view.MenuItem setTitle(int);
method public abstract android.view.MenuItem setTitleCondensed(java.lang.CharSequence);
- method public default android.view.MenuItem setTooltip(java.lang.CharSequence);
+ method public default android.view.MenuItem setTooltipText(java.lang.CharSequence);
method public abstract android.view.MenuItem setVisible(boolean);
field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
@@ -43688,7 +44243,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>);
@@ -43727,6 +44282,7 @@
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
+ method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
method public boolean dispatchDragEvent(android.view.DragEvent);
@@ -43745,6 +44301,7 @@
method public boolean dispatchNestedPrePerformAccessibilityAction(int, android.os.Bundle);
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
+ method public void dispatchPointerCaptureChanged(boolean);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void dispatchProvideAutoFillStructure(android.view.ViewStructure, int);
method public void dispatchProvideStructure(android.view.ViewStructure);
@@ -43810,6 +44367,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();
@@ -43852,7 +44410,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();
@@ -43896,7 +44453,7 @@
method public java.lang.Object getTag(int);
method public int getTextAlignment();
method public int getTextDirection();
- method public final java.lang.CharSequence getTooltip();
+ method public final java.lang.CharSequence getTooltipText();
method public android.view.View getTooltipView();
method public final int getTop();
method protected float getTopFadingEdgeStrength();
@@ -43927,6 +44484,7 @@
method public boolean hasNestedScrollingParent();
method public boolean hasOnClickListeners();
method public boolean hasOverlappingRendering();
+ method public boolean hasPointerCapture();
method public boolean hasTransientState();
method public boolean hasWindowFocus();
method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup);
@@ -43958,7 +44516,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();
@@ -43981,7 +44538,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[]);
@@ -43992,6 +44549,7 @@
method public android.view.WindowInsets onApplyWindowInsets(android.view.WindowInsets);
method protected void onAttachedToWindow();
method public void onCancelPendingInputEvents();
+ method public boolean onCapturedPointerEvent(android.view.MotionEvent);
method public boolean onCheckIsTextEditor();
method protected void onConfigurationChanged(android.content.res.Configuration);
method protected void onCreateContextMenu(android.view.ContextMenu);
@@ -44021,6 +44579,7 @@
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
+ method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void onProvideAutoFillStructure(android.view.ViewStructure, int);
method public void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
@@ -44063,6 +44622,7 @@
method public void postOnAnimation(java.lang.Runnable);
method public void postOnAnimationDelayed(java.lang.Runnable, long);
method public void refreshDrawableState();
+ method public void releasePointerCapture();
method public boolean removeCallbacks(java.lang.Runnable);
method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
@@ -44073,6 +44633,7 @@
method public boolean requestFocus(int, android.graphics.Rect);
method public final boolean requestFocusFromTouch();
method public void requestLayout();
+ method public void requestPointerCapture();
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
method public final void requestUnbufferedDispatch(android.view.MotionEvent);
@@ -44116,6 +44677,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);
@@ -44131,7 +44693,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);
@@ -44149,8 +44710,8 @@
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 setOnCapturedPointerListener(android.view.View.OnCapturedPointerListener);
method public void setOnClickListener(android.view.View.OnClickListener);
method public void setOnContextClickListener(android.view.View.OnContextClickListener);
method public void setOnCreateContextMenuListener(android.view.View.OnCreateContextMenuListener);
@@ -44198,7 +44759,7 @@
method public void setTag(int, java.lang.Object);
method public void setTextAlignment(int);
method public void setTextDirection(int);
- method public final void setTooltip(java.lang.CharSequence);
+ method public final void setTooltipText(java.lang.CharSequence);
method public final void setTop(int);
method public void setTouchDelegate(android.view.TouchDelegate);
method public final void setTransitionName(java.lang.String);
@@ -44233,8 +44794,8 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
- field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
- field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+ field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+ field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -44255,8 +44816,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;
@@ -44275,8 +44838,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
@@ -44288,6 +44849,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
@@ -44422,6 +44984,10 @@
method public abstract void onViewDetachedFromWindow(android.view.View);
}
+ public static abstract interface View.OnCapturedPointerListener {
+ method public abstract boolean onCapturedPointer(android.view.View, android.view.MotionEvent);
+ }
+
public static abstract interface View.OnClickListener {
method public abstract void onClick(android.view.View);
}
@@ -44799,7 +45365,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);
@@ -44996,6 +45562,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();
@@ -45052,6 +45619,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);
@@ -45156,6 +45724,7 @@
method public abstract boolean onMenuItemSelected(int, android.view.MenuItem);
method public abstract boolean onMenuOpened(int, android.view.Menu);
method public abstract void onPanelClosed(int, android.view.Menu);
+ method public default void onPointerCaptureChanged(boolean);
method public abstract boolean onPreparePanel(int, android.view.View, android.view.Menu);
method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
method public abstract boolean onSearchRequested();
@@ -45250,8 +45819,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
@@ -46122,6 +46693,13 @@
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
}
+ public final class AutoFillManager {
+ method public void updateAutoFillInput(android.view.View, int);
+ method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
+ field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
+ field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+ }
+
public final class AutoFillType implements android.os.Parcelable {
method public int describeContents();
method public static android.view.autofill.AutoFillType forList();
@@ -46156,6 +46734,8 @@
public static final class Dataset.Builder {
ctor public Dataset.Builder(java.lang.CharSequence);
method public android.view.autofill.Dataset build();
+ method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
}
@@ -46171,6 +46751,8 @@
method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
method public android.view.autofill.FillResponse build();
+ method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
}
@@ -46181,7 +46763,7 @@
public static abstract class VirtualViewDelegate.Callback {
ctor public VirtualViewDelegate.Callback();
- method public void onFocusChanged(int, boolean);
+ method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
method public void onNodeRemoved(int...);
method public void onValueChanged(int);
}
@@ -46588,6 +47170,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 {
@@ -46818,6 +47477,7 @@
public abstract class RenderProcessGoneDetail {
ctor public RenderProcessGoneDetail();
method public abstract boolean didCrash();
+ method public abstract int rendererPriorityAtExit();
}
public class ServiceWorkerClient {
@@ -47234,6 +47894,8 @@
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
method public java.lang.String getOriginalUrl();
method public int getProgress();
+ method public boolean getRendererPriorityWaivedWhenNotVisible();
+ method public int getRendererRequestedPriority();
method public deprecated float getScale();
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
@@ -47281,6 +47943,7 @@
method public deprecated void setMapTrackballToArrowKeys(boolean);
method public void setNetworkAvailable(boolean);
method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
+ method public void setRendererPriorityPolicy(int, boolean);
method public deprecated void setVerticalScrollbarOverlay(boolean);
method public void setWebChromeClient(android.webkit.WebChromeClient);
method public static void setWebContentsDebuggingEnabled(boolean);
@@ -47290,6 +47953,9 @@
method public void zoomBy(float);
method public boolean zoomIn();
method public boolean zoomOut();
+ field public static final int RENDERER_PRIORITY_BOUND = 1; // 0x1
+ field public static final int RENDERER_PRIORITY_IMPORTANT = 2; // 0x2
+ field public static final int RENDERER_PRIORITY_WAIVED = 0; // 0x0
field public static final java.lang.String SCHEME_GEO = "geo:0,0?q=";
field public static final java.lang.String SCHEME_MAILTO = "mailto:";
field public static final java.lang.String SCHEME_TEL = "tel:";
@@ -48768,6 +49434,7 @@
method public int getGravity();
method public android.view.Menu getMenu();
method public android.view.MenuInflater getMenuInflater();
+ method public android.widget.ListView getMenuListView();
method public void inflate(int);
method public void setGravity(int);
method public void setOnDismissListener(android.widget.PopupMenu.OnDismissListener);
@@ -49601,6 +50268,10 @@
method public void endBatchEdit();
method public boolean extractText(android.view.inputmethod.ExtractedTextRequest, android.view.inputmethod.ExtractedText);
method public final int getAutoLinkMask();
+ method public int getAutoSizeMaxTextSize();
+ method public int getAutoSizeMinTextSize();
+ method public int getAutoSizeStepGranularity();
+ method public int getAutoSizeTextType();
method public int getBreakStrategy();
method public int getCompoundDrawablePadding();
method public android.content.res.ColorStateList getCompoundDrawableTintList();
@@ -49626,7 +50297,6 @@
method public int getExtendedPaddingTop();
method public android.text.InputFilter[] getFilters();
method public java.lang.String getFontFeatureSettings();
- method public java.lang.String getFontVariationSettings();
method public boolean getFreezesText();
method public int getGravity();
method public int getHighlightColor();
@@ -49673,7 +50343,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();
@@ -49710,6 +50380,10 @@
method public void removeTextChangedListener(android.text.TextWatcher);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
+ method public void setAutoSizeMaxTextSize(int, float);
+ method public void setAutoSizeMinTextSize(int, float);
+ method public void setAutoSizeStepGranularity(int, float);
+ method public void setAutoSizeTextType(int);
method public void setBreakStrategy(int);
method public void setCompoundDrawablePadding(int);
method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
@@ -49732,7 +50406,6 @@
method public void setExtractedText(android.view.inputmethod.ExtractedText);
method public void setFilters(android.text.InputFilter[]);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
method protected boolean setFrame(int, int, int, int);
method public void setFreezesText(boolean);
method public void setGravity(int);
@@ -49786,7 +50459,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);
@@ -49801,8 +50474,8 @@
method public void setTypeface(android.graphics.Typeface, int);
method public void setTypeface(android.graphics.Typeface);
method public void setWidth(int);
- field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0
- field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1
+ field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
+ field public static final int AUTO_SIZE_TEXT_TYPE_XY = 1; // 0x1
}
public static final class TextView.BufferType extends java.lang.Enum {
@@ -61937,6 +62610,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>);
@@ -61947,7 +62623,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);
@@ -61976,12 +62656,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 5bf8076..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;
@@ -270,7 +274,7 @@
}
}
- private void backupNowAllPackages() {
+ private void backupNowAllPackages(boolean nonIncrementalBackup) {
int userId = UserHandle.USER_SYSTEM;
IPackageManager mPm =
IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
@@ -297,14 +301,19 @@
System.err.println(BMGR_NOT_RUNNING_ERR);
}
}
- backupNowPackages(packages);
+ backupNowPackages(packages, nonIncrementalBackup);
}
}
- private void backupNowPackages(List<String> packages) {
+ private void backupNowPackages(List<String> packages, boolean nonIncrementalBackup) {
+ int flags = 0;
+ if (nonIncrementalBackup) {
+ flags |= BackupManager.FLAG_NON_INCREMENTAL_BACKUP;
+ }
try {
BackupObserver observer = new BackupObserver();
- int err = mBmgr.requestBackup(packages.toArray(new String[packages.size()]), observer);
+ int err = mBmgr.requestBackup(packages.toArray(new String[packages.size()]), observer,
+ flags);
if (err == 0) {
// Off and running -- wait for the backup to complete
observer.waitForCompletion();
@@ -320,24 +329,31 @@
private void doBackupNow() {
String pkg;
boolean backupAll = false;
+ boolean nonIncrementalBackup = false;
ArrayList<String> allPkgs = new ArrayList<String>();
while ((pkg = nextArg()) != null) {
if (pkg.equals("--all")) {
backupAll = true;
+ } else if (pkg.equals("--non-incremental")) {
+ nonIncrementalBackup = true;
+ } else if (pkg.equals("--incremental")) {
+ nonIncrementalBackup = false;
} else {
allPkgs.add(pkg);
}
}
if (backupAll) {
if (allPkgs.size() == 0) {
- System.out.println("Running backup for all packages.");
- backupNowAllPackages();
+ System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") +
+ "incremental backup for all packages.");
+ backupNowAllPackages(nonIncrementalBackup);
} else {
System.err.println("Provide only '--all' flag or list of packages.");
}
} else if (allPkgs.size() > 0) {
- System.out.println("Running backup for " + allPkgs.size() +" requested packages.");
- backupNowPackages(allPkgs);
+ System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") +
+ "incremental backup for " + allPkgs.size() +" requested packages.");
+ backupNowPackages(allPkgs, nonIncrementalBackup);
} else {
System.err.println("Provide '--all' flag or list of packages.");
}
@@ -351,6 +367,11 @@
return;
}
+ if ("-c".equals(which)) {
+ doTransportByComponent();
+ return;
+ }
+
String old = mBmgr.selectBackupTransport(which);
if (old == null) {
System.out.println("Unknown transport '" + which
@@ -358,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) {
@@ -415,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) {
@@ -637,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");
@@ -661,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/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 810d201..7015381 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -408,7 +408,7 @@
if (file.isFile()) {
try {
ApkLite baseApk = PackageParser.parseApkLite(file, 0);
- PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null);
+ PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
params.sessionParams.setSize(
PackageHelper.calculateInstalledSize(pkgLite, false,
params.sessionParams.abiOverride));
@@ -1556,7 +1556,7 @@
System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]");
System.err.println(" pm install-commit SESSION_ID");
System.err.println(" pm install-abandon SESSION_ID");
- System.err.println(" pm uninstall [-k] [--user USER_ID] PACKAGE");
+ System.err.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE");
System.err.println(" pm set-installer PACKAGE INSTALLER");
System.err.println(" pm move-package PACKAGE [internal|UUID]");
System.err.println(" pm move-primary-storage [internal|UUID]");
diff --git a/cmds/uiautomator/library/Android.mk b/cmds/uiautomator/library/Android.mk
index de46b8c..f932388 100644
--- a/cmds/uiautomator/library/Android.mk
+++ b/cmds/uiautomator/library/Android.mk
@@ -36,8 +36,7 @@
# Generate the stub source files
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(uiautomator.core_src_files)
-LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries)
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries) legacy-android-test
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/core-src \
$(LOCAL_PATH)/testrunner-src
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
index 751bbe8..ef6d55f 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
@@ -808,7 +808,7 @@
*
* @return Rect
* @throws UiObjectNotFoundException
- * @see {@link #getBounds()}
+ * @see #getBounds()
* @since API Level 17
*/
public Rect getVisibleBounds() throws UiObjectNotFoundException {
diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
index d98b4ff..71561c3 100644
--- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
+++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
@@ -40,7 +40,7 @@
* actions such as dialing 911 or posting messages to public forums, etc.
*
* @param isSet True to set as monkey test. False to set as regular functional test (default).
- * @see {@link ActivityManager#isUserAMonkey()}
+ * @see ActivityManager#isUserAMonkey()
*/
public void setRunAsMonkey(boolean isSet) {
IActivityManager am = ActivityManager.getService();
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/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index c9e09e4..87e512c 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -707,7 +707,7 @@
* @param account the account to clone, will never be null
* @return a Bundle result or null if the result is to be returned via the response.
* @throws NetworkErrorException
- * @see {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}
+ * @see #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)
*/
public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
final Account account) throws NetworkErrorException {
@@ -732,7 +732,7 @@
* provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}.
* @return a Bundle result or null if the result is to be returned via the response.
* @throws NetworkErrorException
- * @see {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}
+ * @see #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)
*/
public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response,
Account account,
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 8185818..def0ff9 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -53,9 +53,12 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.SuppressWarnings;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
@@ -286,46 +289,31 @@
/**
* Account visibility was not set.
- * @hide
*/
public static final int VISIBILITY_UNDEFINED = 0;
/**
* Account is always visible to given application and only authenticator can revoke visibility.
- * @hide
*/
public static final int VISIBILITY_VISIBLE = 1;
/**
* Account is visible to given application, but user can revoke visibility.
- * @hide
*/
public static final int VISIBILITY_USER_MANAGED_VISIBLE = 2;
/**
* Account is not visible to given application and only authenticator can grant visibility.
- * @hide
*/
public static final int VISIBILITY_NOT_VISIBLE = 3;
/**
* Account is not visible to given application, but user can reveal it, for example, using
* {@link #newChooseAccountIntent(Account, List, String[], String, String, String[], Bundle)}
- * @hide
*/
public static final int VISIBILITY_USER_MANAGED_NOT_VISIBLE = 4;
/**
- * Key to manifest entry with a list of account types in which application is interested.
- * Example value: "com.google;com.customtype". If it is specified then the application
- * will only get notifications related to the types in the list (see
- * {@link #ACTION_VISIBLE_ACCOUNTS_CHANGED}). Authenticators managing whitelisted types will be
- * able to know about the application using {@link #ACTION_ACCOUNTS_LISTENER_PACKAGE_INSTALLED}
- * @hide
- */
- public static final String SUPPORTED_ACCOUNT_TYPES = "android.accounts.SupportedAccountTypes";
-
- /**
* Token type for the special case where a UID has access only to an account
* but no authenticator specific auth token types.
*
@@ -344,48 +332,27 @@
*
* @see #addOnAccountsUpdatedListener
*
- * Deprecated - use ACTION_VISIBLE_ACCOUNTS_CHANGED instead.
+ * @deprecated use #addOnAccountsUpdatedListener to get account updates in runtime.
*/
public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
"android.accounts.LOGIN_ACCOUNTS_CHANGED";
/**
- * Action sent as a broadcast Intent by the AccountsService when accounts potentially visible to
- * the applications are added, accounts are removed, or an account's credentials (saved
- * password, etc) are changed. List of supported account types shoud be specified in the
- * Manifest file using {@link #SUPPORTED_ACCOUNT_TYPES}
- *
- * @see #addOnAccountsUpdatedListener
- * @hide
- */
- public static final String ACTION_VISIBLE_ACCOUNTS_CHANGED =
- "android.accounts.action.VISIBLE_ACCOUNTS_CHANGED";
-
- /**
- * Authenticators may subscribe to get notifications about apps interested in their managed account
- * types using {@link #SUPPORTED_ACCOUNT_TYPES}.
- * @hide
- */
- public static final String ACTION_ACCOUNTS_LISTENER_PACKAGE_INSTALLED =
- "android.accounts.action.ACCOUNTS_LISTENER_PACKAGE_INSTALLED";
-
- /**
* Uid key to set default visibility for applications targeting API level
- * {@link android.os.Build.VERSION_CODES#O} or above. See {@link #getAccountVisibility}. If the
- * value was not set by authenticator USER_MANAGED_NOT_VISIBLE is used.
- * @hide
+ * {@link android.os.Build.VERSION_CODES#O} or above and don't have the same signature as
+ * authenticator See {@link #getAccountVisibility}. If the value was not set by authenticator
+ * USER_MANAGED_NOT_VISIBLE is used.
*/
- public static final int DEFAULT_VISIBILITY = -2;
+ public static final int UID_KEY_DEFAULT_VISIBILITY = -2;
/**
* Uid key to set visibility for applications targeting API level below
- * {@link android.os.Build.VERSION_CODES#O}, which were able to see the account before. It
- * includes applications with GET_ACCOUNTS permission or with the same signature as
- * authenticator. See {@link #getAccountVisibility}. If the value was not set by authenticator
- * USER_MANAGED_VISIBLE is used.
- * @hide
+ * {@link android.os.Build.VERSION_CODES#O} with GET_ACCOUNS permission, or applications with
+ * any targeting API level with the same signature as authenticator. See
+ * {@link #getAccountVisibility}. If the value was not set by authenticator USER_MANAGED_VISIBLE
+ * is used.
*/
- public static final int DEFAULT_LEGACY_VISIBILITY = -3;
+ public static final int UID_KEY_DEFAULT_LEGACY_VISIBILITY = -3;
/**
* @hide
@@ -874,43 +841,11 @@
* @param account The {@link Account} to add
* @param password The password to associate with the account, null for none
* @param extras String values to use for the account's userdata, null for none
- * @param selectedUids Array of uids whose associated applications can access this account
- * without any additional user approval.
- *
- * @return True if the account was successfully added, false if the account already exists, the
- * account is null, or another error occurs.
- */
- public boolean addAccountExplicitly(Account account, String password, Bundle extras,
- int[] selectedUids) {
- return false; // TODO remove this method.
- }
-
- /**
- * Adds an account directly to the AccountManager. Additionally this makes the Account visible
- * to desired UIDs of applications on the device, and sends directed broadcasts to these
- * individual applications.
- * <p>
- * Normally used by sign-up wizards associated with authenticators, not directly by
- * applications.
- * <p>
- * Calling this method does not update the last authenticated timestamp, referred by
- * {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
- * {@link #notifyAccountAuthenticated(Account)} after getting success.
- * <p>
- * It is safe to call this method from the main thread.
- * <p>
- * This method requires the caller to have a signature match with the authenticator that owns
- * the specified account.
- *
- * @param account The {@link Account} to add
- * @param password The password to associate with the account, null for none
- * @param extras String values to use for the account's userdata, null for none
* @param visibility Map from uid to visibility values which will be set before account is
* added. See getAccountVisibility for possilbe values.
*
* @return True if the account was successfully added, false if the account already exists, the
* account is null, or another error occurs.
- * @hide
*/
public boolean addAccountExplicitly(Account account, String password, Bundle extras,
Map<Integer, Integer> visibility) {
@@ -925,19 +860,21 @@
}
/**
- * Returns all UIDs for applications that requested the account type.
- * <p>This method requires the caller to have a signature match with the authenticator
- * that owns the specified account.
+ * Returns UIDs of applications for which visibility of given account was explicitly set.
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
*
- * @param accountType The account type to be authenticated.
+ * @param account The account for which visibility data should be returned.
*
- * @return array of all UIDs that support accounts of this
- * account type that seek approval (to be used to know which accounts for
- * the authenticator to include in addAccountExplicitly). Null if none.
+ * @return Map from uid to visibility for given account.
*/
- public int[] getRequestingUidsForType(String accountType) {
+ public Map<Integer, Integer> getUidsAndVisibilityForAccount(Account account) {
try {
- return mService.getRequestingUidsForType(accountType);
+ @SuppressWarnings("unchecked")
+ Map<Integer, Integer> result = (Map<Integer, Integer>) mService
+ .getUidsAndVisibilityForAccount(account);
+ return result;
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -954,9 +891,8 @@
* @param packageName Package name.
* @param accountType Account type.
*
- * @return Map with visibility for all accounts of given type. See {@link #getAccountVisibility}
- * for possilbe values.
- * @hide
+ * @return Map with visibility for all accounts of given type.
+ * See {@link #getAccountVisibility} for possilbe values.
*/
public Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
String accountType) {
@@ -971,67 +907,6 @@
}
/**
- * Gives a certain UID, represented a application, access to an account
- * <p>
- * This method requires the caller to have a signature match with the authenticator that owns
- * the specified account.
- *
- * @param account Account to make visible.
- * @param uid The UID of the application to add account access.
- *
- * @return True if account made visible to application and was not previously visible.
- */
- public boolean makeAccountVisible(Account account, int uid) {
- try {
- return mService.setAccountVisibility(account, uid, VISIBILITY_USER_MANAGED_VISIBLE);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
- * Removes visibility of certain account of a process identified by a given UID to an
- * application. This is called by the Authenticator.
- * <p>
- * This method requires the caller to have a signature match with the authenticator that owns
- * the specified account.
- *
- * @param account Remove visibility of this account..
- * @param uid The UID of the application to remove account access.
- *
- * @return True if application access to account removed and was previously visible.
- */
- public boolean removeAccountVisibility(Account account, int uid) {
- try {
- return mService.setAccountVisibility(account, uid, VISIBILITY_USER_MANAGED_NOT_VISIBLE);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
- * Checks visibility of certain account of a process identified by a given UID. This is called
- * by the Authenticator.
- * <p>
- * This method requires the caller to have a signature match with the authenticator that owns
- * the specified account.
- *
- * @param account Account to check visibility.
- * @param uid The UID of the application to check account access.
- *
- * @return True if application has access to the account
- */
- public boolean isAccountVisible(Account account, int uid) {
- try {
- Integer visibility = mService.getAccountVisibility(account, uid);
- return visibility == VISIBILITY_USER_MANAGED_NOT_VISIBLE
- || visibility == VISIBILITY_VISIBLE;
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
* Set visibility value of given account to certain UID.
* <p>
* See {@link #getAccountVisibility} for possible values.
@@ -1044,7 +919,6 @@
* @param visibility - new visibility value.
*
* @return True if visibility value was succesfully updated.
- * @hide
*/
public boolean setAccountVisibility(Account account, int uid,
@AccountVisibility int visibility) {
@@ -1058,6 +932,7 @@
/**
* Gets visibility of certain account for given UID. Possible returned values are:
* <ul>
+ * <li>{@link #VISIBILITY_UNDEFINED}</li>
* <li>{@link #VISIBILITY_VISIBLE}</li>
* <li>{@link #VISIBILITY_USER_MANAGED_VISIBLE}</li>
* <li>{@link #VISIBILITY_NOT_VISIBLE}
@@ -1072,7 +947,6 @@
* @param uid The UID of the application to get account visibility.
*
* @return int Visibility for given account and uid.
- * @hide
*/
public @AccountVisibility int getAccountVisibility(Account account, int uid) {
try {
@@ -2245,10 +2119,23 @@
synchronized (mAccountsUpdatedListeners) {
try {
if (mAccountsUpdatedListeners.containsKey(listener)) {
- listener.onAccountsUpdated(accountsCopy);
+ Set<String> types = mAccountsUpdatedListenersTypes.get(listener);
+ if (types != null) {
+ // filter by account type;
+ ArrayList<Account> filtered = new ArrayList<>();
+ for (Account account : accountsCopy) {
+ if (types.contains(account.type)) {
+ filtered.add(account);
+ }
+ }
+ listener.onAccountsUpdated(
+ filtered.toArray(new Account[filtered.size()]));
+ } else {
+ listener.onAccountsUpdated(accountsCopy);
+ }
}
} catch (SQLException e) {
- // Better luck next time. If the problem was disk-full,
+ // Better luck next time. If the problem was disk-full,
// the STORAGE_OK intent will re-trigger the update.
Log.e(TAG, "Can't update accounts", e);
}
@@ -2894,6 +2781,9 @@
private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
Maps.newHashMap();
+ private final HashMap<OnAccountsUpdateListener, Set<String> > mAccountsUpdatedListenersTypes =
+ Maps.newHashMap();
+
/**
* BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
* so that it can read the updated list of accounts and send them to the listener
@@ -2916,20 +2806,29 @@
/**
* Adds an {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. This
* listener will be notified whenever user or AbstractAcccountAuthenticator made changes to
- * accounts related to the caller - either list of accounts returned by {@link #getAccounts()}
- * was changed, or new account was added for which user can grant access to the caller.
+ * accounts of any type related to the caller. This method is equivalent to
+ * addOnAccountsUpdatedListener(listener, handler, updateImmediately, null)
*
+ * @see #addOnAccountsUpdatedListener(OnAccountsUpdateListener, Handler, boolean,
+ * String[])
+ */
+ public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
+ Handler handler, boolean updateImmediately) {
+ addOnAccountsUpdatedListener(listener, handler,updateImmediately, null);
+ }
+
+ /**
+ * Adds an {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. This
+ * listener will be notified whenever user or AbstractAcccountAuthenticator made changes to
+ * accounts of given types related to the caller -
+ * either list of accounts returned by {@link #getAccounts()}
+ * was changed, or new account was added for which user can grant access to the caller.
* <p>
* As long as this listener is present, the AccountManager instance will not be
* garbage-collected, and neither will the {@link Context} used to retrieve it, which may be a
* large Activity instance. To avoid memory leaks, you must remove this listener before then.
* Normally listeners are added in an Activity or Service's {@link Activity#onCreate} and
* removed in {@link Activity#onDestroy}.
- *
- *
- * If SUPPORTED_ACCOUNT_TYPES is specified in the manifest file, listener will only be
- * notified about whitelisted types.
- *
* <p>
* It is safe to call this method from the main thread.
*
@@ -2938,11 +2837,12 @@
* main thread
* @param updateImmediately If true, the listener will be invoked (on the handler thread) right
* away with the current account list
+ * @param accountTypes If set, only changes to accounts of given types will be reported.
* @throws IllegalArgumentException if listener is null
* @throws IllegalStateException if listener was already added
*/
public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
- Handler handler, boolean updateImmediately) {
+ Handler handler, boolean updateImmediately, String[] accountTypes) {
if (listener == null) {
throw new IllegalArgumentException("the listener is null");
}
@@ -2953,16 +2853,19 @@
final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
mAccountsUpdatedListeners.put(listener, handler);
-
+ if (accountTypes != null) {
+ mAccountsUpdatedListenersTypes.put(listener,
+ new HashSet<String>(Arrays.asList(accountTypes)));
+ }
if (wasEmpty) {
// Register a broadcast receiver to monitor account changes
IntentFilter intentFilter = new IntentFilter();
- if (isVisibleAccountsChangedBroadcastSupported()) {
- intentFilter.addAction(ACTION_VISIBLE_ACCOUNTS_CHANGED);
- } else {
- intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
- }
+ // TODO get rid of the broadcast receiver
+ // create android.os.ResultReceiver
+ // send it to the service via aidl
+ // handle onReceiveResult
+ intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
// To recover from disk-full.
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
// Register a broadcast receiver to monitor account changes
@@ -2975,26 +2878,6 @@
}
/**
- * @hide
- */
- private boolean isVisibleAccountsChangedBroadcastSupported() {
- String interestedTypes = null;
- try {
- String packageName = mContext.getOpPackageName();
- ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(packageName,
- PackageManager.GET_META_DATA);
- Bundle b = ai.metaData;
- if (b == null) {
- return false;
- }
- interestedTypes = b.getString(SUPPORTED_ACCOUNT_TYPES);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- return !TextUtils.isEmpty(interestedTypes);
- }
-
- /**
* Removes an {@link OnAccountsUpdateListener} previously registered with
* {@link #addOnAccountsUpdatedListener}. The listener will no longer
* receive notifications of account changes.
@@ -3015,6 +2898,7 @@
return;
}
mAccountsUpdatedListeners.remove(listener);
+ mAccountsUpdatedListenersTypes.remove(listener);
if (mAccountsUpdatedListeners.isEmpty()) {
mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
}
diff --git a/core/java/android/accounts/ChooseAccountActivity.java b/core/java/android/accounts/ChooseAccountActivity.java
index 242b3ea..16a45ba 100644
--- a/core/java/android/accounts/ChooseAccountActivity.java
+++ b/core/java/android/accounts/ChooseAccountActivity.java
@@ -52,7 +52,9 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+ // TODO This activity is only used by getAuthTokenByFeatures and can not see
+ // VISIBILITY_USER_MANAGED_NOT_VISIBLE accounts. It should be moved to account managed
+ // service.
mAccounts = getIntent().getParcelableArrayExtra(AccountManager.KEY_ACCOUNTS);
mAccountManagerResponse =
getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE);
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 8c71f50..95fdfef 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -40,7 +40,9 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -110,7 +112,7 @@
private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
- private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountList";
+ private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountAndVisibilityList";
private static final int SELECTED_ITEM_NONE = -1;
@@ -120,7 +122,11 @@
private boolean mSelectedAddNewAccount = false;
private String mDescriptionOverride;
- private ArrayList<Account> mAccounts;
+ private Map<Account, Integer> mAccounts;
+ // TODO Redesign flow to show NOT_VISIBLE accounts
+ // and display a warning if they are selected.
+ // Currently NOT_VISBILE accounts are not shown at all.
+ private ArrayList<Account> mPossiblyVisibleAccounts;
private int mPendingRequest = REQUEST_NULL;
private Parcelable[] mExistingAccounts = null;
private int mSelectedItemIndex;
@@ -164,12 +170,12 @@
savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS);
// Makes sure that any user selection is preserved across orientation changes.
- mSelectedAccountName = savedInstanceState.getString(
- KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
-
- mSelectedAddNewAccount = savedInstanceState.getBoolean(
- KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
- mAccounts = savedInstanceState.getParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST);
+ mSelectedAccountName =
+ savedInstanceState.getString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
+ mSelectedAddNewAccount =
+ savedInstanceState.getBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
+ mAccounts = (Map<Account, Integer>) savedInstanceState
+ .getSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST);
} else {
mPendingRequest = REQUEST_NULL;
mExistingAccounts = null;
@@ -220,9 +226,15 @@
}
}
- String[] listItems = getListOfDisplayableOptions(mAccounts);
- mSelectedItemIndex = getItemIndexToSelect(
- mAccounts, mSelectedAccountName, mSelectedAddNewAccount);
+ mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
+ for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
+ if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
+ mPossiblyVisibleAccounts.add(entry.getKey());
+ }
+ }
+ String[] listItems = getListOfDisplayableOptions(mPossiblyVisibleAccounts);
+ mSelectedItemIndex = getItemIndexToSelect(mPossiblyVisibleAccounts, mSelectedAccountName,
+ mSelectedAddNewAccount);
super.onCreate(savedInstanceState);
setContentView(R.layout.choose_type_and_account);
@@ -250,15 +262,18 @@
outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts);
}
if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
- if (mSelectedItemIndex == mAccounts.size()) {
+ if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true);
} else {
outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME,
- mAccounts.get(mSelectedItemIndex).name);
+ mPossiblyVisibleAccounts.get(mSelectedItemIndex).name);
}
}
- outState.putParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST, mAccounts);
+ // should be HashMap by default.
+ HashMap<Account, Integer> accountsHashMap = (mAccounts instanceof HashMap)
+ ? (HashMap) mAccounts : new HashMap<Account, Integer>(mAccounts);
+ outState.putSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST, accountsHashMap);
}
public void onCancelButtonClicked(View view) {
@@ -266,11 +281,11 @@
}
public void onOkButtonClicked(View view) {
- if (mSelectedItemIndex == mAccounts.size()) {
+ if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
// Selected "Add New Account" option
startChooseAccountTypeActivity();
} else if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
- onAccountSelected(mAccounts.get(mSelectedItemIndex));
+ onAccountSelected(mPossiblyVisibleAccounts.get(mSelectedItemIndex));
}
}
@@ -321,6 +336,7 @@
}
if (accountName == null || accountType == null) {
+ // new account was added.
Account[] currentAccounts = AccountManager.get(this).getAccountsForPackage(
mCallingPackage, mCallingUid);
Set<Account> preExistingAccounts = new HashSet<Account>();
@@ -328,6 +344,7 @@
preExistingAccounts.add((Account) accountParcel);
}
for (Account account : currentAccounts) {
+ // New account is visible to the app - return it.
if (!preExistingAccounts.contains(account)) {
accountName = account.name;
accountType = account.type;
@@ -409,13 +426,27 @@
}
private void setResultAndFinish(final String accountName, final String accountType) {
+ // Mark account as visible since user chose it.
+ Account account = new Account(accountName, accountType);
+ Integer oldVisibility = mAccounts.get(account);
+ // oldVisibility is null if new account was added
+ if (oldVisibility == null) {
+ Map<Account, Integer> accountsAndVisibility = AccountManager.get(this)
+ .getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
+ oldVisibility = accountsAndVisibility.get(account);
+ }
+ if (oldVisibility != null
+ && oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
+ AccountManager.get(this).setAccountVisibility(account, mCallingUid,
+ AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+ }
Bundle bundle = new Bundle();
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: "
- + "selected account " + accountName + ", " + accountType);
+ Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: selected account "
+ + accountName + ", " + accountType);
}
finish();
}
@@ -474,25 +505,28 @@
}
/**
- * Create a list of Account objects for each account that is acceptable. Filter out
- * accounts that don't match the allowable types, if provided, or that don't match the
- * allowable accounts, if provided.
+ * Create a list of Account objects for each account that is acceptable. Filter out accounts
+ * that don't match the allowable types, if provided, or that don't match the allowable
+ * accounts, if provided.
*/
- private ArrayList<Account> getAcceptableAccountChoices(AccountManager accountManager) {
- final Account[] accounts = accountManager.getAccountsForPackage(mCallingPackage,
- mCallingUid);
- ArrayList<Account> accountsToPopulate = new ArrayList<Account>(accounts.length);
- for (Account account : accounts) {
- if (mSetOfAllowableAccounts != null && !mSetOfAllowableAccounts.contains(account)) {
- continue;
- }
- if (mSetOfRelevantAccountTypes != null
- && !mSetOfRelevantAccountTypes.contains(account.type)) {
- continue;
- }
- accountsToPopulate.add(account);
- }
- return accountsToPopulate;
+ private Map<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
+ Map<Account, Integer> accountsAndVisibility =
+ accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
+
+ Map<Account, Integer> accountsToPopulate =
+ new HashMap<Account, Integer>(accountsAndVisibility.size());
+ for (Map.Entry<Account, Integer> entry : accountsAndVisibility.entrySet()) {
+ if (mSetOfAllowableAccounts != null
+ && !mSetOfAllowableAccounts.contains(entry.getKey())) {
+ continue;
+ }
+ if (mSetOfRelevantAccountTypes != null
+ && !mSetOfRelevantAccountTypes.contains(entry.getKey().type)) {
+ continue;
+ }
+ accountsToPopulate.put(entry.getKey(), entry.getValue());
+ }
+ return accountsToPopulate;
}
/**
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 66c3ca3..63a0919 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -108,8 +108,8 @@
void isCredentialsUpdateSuggested(in IAccountManagerResponse response, in Account account,
String statusToken);
- /* Allows Authenticator to get UIDs of packages which registered to receive updates about given account type.*/
- int[] getRequestingUidsForType(String accountType);
+ /* Returns Map<Integer, Integer> from UID to visibility with all values stored for given account*/
+ Map getUidsAndVisibilityForAccount(in Account account);
boolean addAccountExplicitlyWithVisibility(in Account account, String password, in Bundle extras,
in Map visibility);
@@ -117,7 +117,7 @@
boolean setAccountVisibility(in Account a, int uid, int newVisibility);
int getAccountVisibility(in Account a, int uid);
- /* Type may be null returns Map <Account, Integer>*/
+ /* Type may be null returns Map <Account, Integer>*/
Map getAccountsAndVisibilityForPackage(in String packageName, in String accountType);
/* Check if the package in a user can access an account */
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 95262ab..e2e5a8f 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -276,8 +276,9 @@
* Run animation based on the frame time.
* @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
* base.
+ * @return if the animation has finished.
*/
- void doAnimationFrame(long frameTime);
+ boolean doAnimationFrame(long frameTime);
/**
* This notifies the callback of frame commit time. Frame commit time is the time after
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index c51725a..08ad976 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -26,7 +26,7 @@
* This is the superclass for classes which provide basic support for animations which can be
* started, ended, and have <code>AnimatorListeners</code> added to them.
*/
-public abstract class Animator implements Cloneable {
+public abstract class Animator implements Cloneable, AnimationHandler.AnimationFrameCallback {
/**
* The value used to indicate infinite duration (e.g. when Animators repeat infinitely).
@@ -465,11 +465,103 @@
}
/**
+ * @hide
+ */
+ @Override
+ public boolean doAnimationFrame(long frameTime) {
+ // TODO: Need to find a better signal than this. There's a bug in SystemUI that's preventing
+ // returning !isStarted() from working.
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void commitAnimationFrame(long frameTime) {}
+
+
+ /**
+ * Internal use only.
+ * This call starts the animation in regular or reverse direction without requiring them to
+ * register frame callbacks. The caller will be responsible for all the subsequent animation
+ * pulses. Specifically, the caller needs to call doAnimationFrame(...) for the animation on
+ * every frame.
+ *
+ * @param inReverse whether the animation should play in reverse direction
+ */
+ void startWithoutPulsing(boolean inReverse) {
+ if (inReverse) {
+ reverse();
+ } else {
+ start();
+ }
+ }
+
+ /**
+ * Internal use only.
+ * Skips the animation value to end/start, depending on whether the play direction is forward
+ * or backward.
+ *
+ * @param inReverse whether the end value is based on a reverse direction. If yes, this is
+ * equivalent to skip to start value in a forward playing direction.
+ */
+ void skipToEndValue(boolean inReverse) {}
+
+
+ /**
+ * Internal use only.
+ *
+ * Returns whether the animation has start/end values setup. For most of the animations, this
+ * should always be true. For ObjectAnimators, the start values are setup in the initialization
+ * of the animation.
+ */
+ boolean isInitialized() {
+ return true;
+ }
+
+ /**
+ * Internal use only.
+ */
+ void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
+
+ /**
* <p>An animation listener receives notifications from an animation.
* Notifications indicate animation related events, such as the end or the
* repetition of the animation.</p>
*/
public static interface AnimatorListener {
+
+ /**
+ * <p>Notifies the start of the animation as well as the animation's overall play direction.
+ * This method's default behavior is to call {@link #onAnimationStart(Animator)}. This
+ * method can be overridden, though not required, to get the additional play direction info
+ * when an animation starts. Skipping calling super when overriding this method results in
+ * {@link #onAnimationStart(Animator)} not getting called.
+ *
+ * @param animation The started animation.
+ * @param isReverse Whether the animation is playing in reverse.
+ */
+ default void onAnimationStart(Animator animation, boolean isReverse) {
+ onAnimationStart(animation);
+ }
+
+ /**
+ * <p>Notifies the end of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * <p>This method's default behavior is to call {@link #onAnimationEnd(Animator)}. This
+ * method can be overridden, though not required, to get the additional play direction info
+ * when an animation ends. Skipping calling super when overriding this method results in
+ * {@link #onAnimationEnd(Animator)} not getting called.
+ *
+ * @param animation The animation which reached its end.
+ * @param isReverse Whether the animation is playing in reverse.
+ */
+ default void onAnimationEnd(Animator animation, boolean isReverse) {
+ onAnimationEnd(animation);
+ }
+
/**
* <p>Notifies the start of the animation.</p>
*
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index a904f9d..8aba405 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -19,11 +19,15 @@
import android.app.ActivityThread;
import android.app.Application;
import android.os.Build;
+import android.os.Looper;
+import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.animation.Animation;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
import java.util.List;
/**
@@ -52,7 +56,7 @@
* Animation</a> developer guide.</p>
* </div>
*/
-public final class AnimatorSet extends Animator {
+public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback {
private static final String TAG = "AnimatorSet";
/**
@@ -66,7 +70,7 @@
* Tracks animations currently being played, so that we know what to
* cancel or end when cancel() or end() is called on this AnimatorSet
*/
- private ArrayList<Animator> mPlayingSet = new ArrayList<Animator>();
+ private ArrayList<Node> mPlayingSet = new ArrayList<Node>();
/**
* Contains all nodes, mapped to their respective Animators. When new
@@ -77,6 +81,11 @@
private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>();
/**
+ * Contains the start and end events of all the nodes. All these events are sorted in this list.
+ */
+ private ArrayList<AnimationEvent> mEvents = new ArrayList<>();
+
+ /**
* Set of all nodes created for this AnimatorSet. This list is used upon
* starting the set, and the nodes are placed in sorted order into the
* sortedNodes collection.
@@ -84,21 +93,6 @@
private ArrayList<Node> mNodes = new ArrayList<Node>();
/**
- * Animator Listener that tracks the lifecycle of each Animator in the set. It will be added
- * to each Animator before they start and removed after they end.
- */
- private AnimatorSetListener mSetListener = new AnimatorSetListener(this);
-
- /**
- * Flag indicating that the AnimatorSet has been manually
- * terminated (by calling cancel() or end()).
- * This flag is used to avoid starting other animations when currently-playing
- * child animations of this AnimatorSet end. It also determines whether cancel/end
- * notifications are sent out via the normal AnimatorSetListener mechanism.
- */
- private boolean mTerminated = false;
-
- /**
* Tracks whether any change has been made to the AnimatorSet, which is then used to
* determine whether the dependency graph should be re-constructed.
*/
@@ -131,8 +125,6 @@
// was set on this AnimatorSet, so it should not be passed down to the children.
private TimeInterpolator mInterpolator = null;
- // Whether the AnimatorSet can be reversed.
- private boolean mReversible = true;
// The total duration of finishing all the Animators in the set.
private long mTotalDuration = 0;
@@ -142,6 +134,59 @@
// the animator set and immediately end it for N and forward.
private final boolean mShouldIgnoreEndWithoutStart;
+ // In pre-O releases, calling start() doesn't reset all the animators values to start values.
+ // As a result, the start of the animation is inconsistent with what setCurrentPlayTime(0) would
+ // look like on O. Also it is inconsistent with what reverse() does on O, as reverse would
+ // advance all the animations to the right beginning values for before starting to reverse.
+ // From O and forward, we will add an additional step of resetting the animation values (unless
+ // the animation was previously seeked and therefore doesn't start from the beginning).
+ private final boolean mShouldResetValuesAtStart;
+
+ // The time, in milliseconds, when last frame of the animation came in. -1 when the animation is
+ // not running.
+ private long mLastFrameTime = -1;
+
+ // The time, in milliseconds, when the first frame of the animation came in.
+ // -1 when the animation is not running.
+ private long mFirstFrame = -1;
+
+ // The time, in milliseconds, when the first frame of the animation came in.
+ // -1 when the animation is not running.
+ private int mLastEventId = -1;
+
+ // Indicates whether the animation is reversing.
+ private boolean mReversing = false;
+
+ // Indicates whether the animation should register frame callbacks. If false, the animation will
+ // passively wait for an AnimatorSet to pulse it.
+ private boolean mSelfPulse = true;
+
+ // SeekState stores the last seeked play time as well as seek direction.
+ private SeekState mSeekState = new SeekState();
+
+ // Indicates where children animators are all initialized with their start values captured.
+ private boolean mChildrenInitialized = false;
+
+ /**
+ * Set on the next frame after pause() is called, used to calculate a new startTime
+ * or delayStartTime which allows the animator set to continue from the point at which
+ * it was paused. If negative, has not yet been set.
+ */
+ private long mPauseTime = -1;
+
+ // This is to work around a bug in b/34736819. This needs to be removed once app team
+ // fixes their side.
+ private AnimatorListenerAdapter mDummyListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mNodeMap.get(animation) == null) {
+ throw new AndroidRuntimeException("Error: animation ended is not in the node map");
+ }
+ mNodeMap.get(animation).mEnded = true;
+
+ }
+ };
+
public AnimatorSet() {
super();
mNodeMap.put(mDelayAnim, mRootNode);
@@ -150,10 +195,19 @@
Application app = ActivityThread.currentApplication();
if (app == null || app.getApplicationInfo() == null) {
mShouldIgnoreEndWithoutStart = true;
- } else if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
- mShouldIgnoreEndWithoutStart = true;
+ mShouldResetValuesAtStart = false;
} else {
- mShouldIgnoreEndWithoutStart = false;
+ if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+ mShouldIgnoreEndWithoutStart = true;
+ } else {
+ mShouldIgnoreEndWithoutStart = false;
+ }
+
+ if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O) {
+ mShouldResetValuesAtStart = false;
+ } else {
+ mShouldResetValuesAtStart = true;
+ }
}
}
@@ -206,7 +260,6 @@
if (items.length == 1) {
play(items[0]);
} else {
- mReversible = false;
for (int i = 0; i < items.length - 1; ++i) {
play(items[i]).before(items[i + 1]);
}
@@ -225,7 +278,6 @@
if (items.size() == 1) {
play(items.get(0));
} else {
- mReversible = false;
for (int i = 0; i < items.size() - 1; ++i) {
play(items.get(i)).before(items.get(i + 1));
}
@@ -350,7 +402,9 @@
@SuppressWarnings("unchecked")
@Override
public void cancel() {
- mTerminated = true;
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
if (isStarted()) {
ArrayList<AnimatorListener> tmpListeners = null;
if (mListeners != null) {
@@ -360,18 +414,13 @@
tmpListeners.get(i).onAnimationCancel(this);
}
}
- ArrayList<Animator> playingSet = new ArrayList<>(mPlayingSet);
+ ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet);
int setSize = playingSet.size();
for (int i = 0; i < setSize; i++) {
- playingSet.get(i).cancel();
+ playingSet.get(i).mAnimation.cancel();
}
- if (tmpListeners != null) {
- int size = tmpListeners.size();
- for (int i = 0; i < size; i++) {
- tmpListeners.get(i).onAnimationEnd(this);
- }
- }
- mStarted = false;
+ mPlayingSet.clear();
+ endAnimation();
}
}
@@ -383,50 +432,40 @@
*/
@Override
public void end() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
if (mShouldIgnoreEndWithoutStart && !isStarted()) {
return;
}
- mTerminated = true;
if (isStarted()) {
- endRemainingAnimations();
- }
- if (mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- for (int i = 0; i < tmpListeners.size(); i++) {
- tmpListeners.get(i).onAnimationEnd(this);
- }
- }
- mStarted = false;
- }
-
- /**
- * Iterate the animations that haven't finished or haven't started, and end them.
- */
- private void endRemainingAnimations() {
- ArrayList<Animator> remainingList = new ArrayList<Animator>(mNodes.size());
- remainingList.addAll(mPlayingSet);
-
- int index = 0;
- while (index < remainingList.size()) {
- Animator anim = remainingList.get(index);
- anim.end();
- index++;
- Node node = mNodeMap.get(anim);
- if (node.mChildNodes != null) {
- int childSize = node.mChildNodes.size();
- for (int i = 0; i < childSize; i++) {
- Node child = node.mChildNodes.get(i);
- if (child.mLatestParent != node) {
- continue;
+ // Iterate the animations that haven't finished or haven't started, and end them.
+ if (mReversing) {
+ // Between start() and first frame, mLastEventId would be unset (i.e. -1)
+ mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
+ for (int j = mLastEventId - 1; j >= 0; j--) {
+ AnimationEvent event = mEvents.get(j);
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ event.mNode.mAnimation.reverse();
+ } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ event.mNode.mAnimation.end();
}
- remainingList.add(child.mAnimation);
+ }
+ } else {
+ for (int j = mLastEventId + 1; j < mEvents.size(); j++) {
+ AnimationEvent event = mEvents.get(j);
+ if (event.mEvent == AnimationEvent.ANIMATION_START) {
+ event.mNode.mAnimation.start();
+ } else if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ event.mNode.mAnimation.end();
+ }
}
}
+ mPlayingSet.clear();
}
+ endAnimation();
}
-
/**
* Returns true if any of the child animations of this AnimatorSet have been started and have
* not yet ended. Child animations will not be started until the AnimatorSet has gone past
@@ -437,14 +476,12 @@
*/
@Override
public boolean isRunning() {
- int size = mNodes.size();
- for (int i = 0; i < size; i++) {
- Node node = mNodes.get(i);
- if (node != mRootNode && node.mAnimation.isStarted()) {
- return true;
- }
+ if (mStartDelay > 0) {
+ return mStarted && !mDelayAnim.isRunning();
+ } else {
+ // No start delay, animation should start right away
+ return mStarted;
}
- return false;
}
@Override
@@ -482,9 +519,6 @@
return;
}
mStartDelay = startDelay;
- if (mStartDelay > 0) {
- mReversible = false;
- }
if (!mDependencyDirty) {
// Dependency graph already constructed, update all the nodes' start/end time
int size = mNodes.size();
@@ -562,40 +596,26 @@
@Override
public void pause() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
boolean previouslyPaused = mPaused;
super.pause();
if (!previouslyPaused && mPaused) {
- if (mDelayAnim.isStarted()) {
- // If delay hasn't passed, pause the start delay animator.
- mDelayAnim.pause();
- } else {
- int size = mNodes.size();
- for (int i = 0; i < size; i++) {
- Node node = mNodes.get(i);
- if (node != mRootNode) {
- node.mAnimation.pause();
- }
- }
- }
+ mPauseTime = -1;
}
}
@Override
public void resume() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
boolean previouslyPaused = mPaused;
super.resume();
if (previouslyPaused && !mPaused) {
- if (mDelayAnim.isStarted()) {
- // If start delay hasn't passed, resume the previously paused start delay animator
- mDelayAnim.resume();
- } else {
- int size = mNodes.size();
- for (int i = 0; i < size; i++) {
- Node node = mNodes.get(i);
- if (node != mRootNode) {
- node.mAnimation.resume();
- }
- }
+ if (mPauseTime >= 0) {
+ addAnimationCallback(0);
}
}
}
@@ -610,9 +630,33 @@
@SuppressWarnings("unchecked")
@Override
public void start() {
- mTerminated = false;
+ start(false, true);
+ }
+
+ @Override
+ void startWithoutPulsing(boolean inReverse) {
+ start(inReverse, false);
+ }
+
+ private void initAnimation() {
+ if (mInterpolator != null) {
+ for (int i = 0; i < mNodes.size(); i++) {
+ Node node = mNodes.get(i);
+ node.mAnimation.setInterpolator(mInterpolator);
+ }
+ }
+ updateAnimatorsDuration();
+ createDependencyGraph();
+ }
+
+ private void start(boolean inReverse, boolean selfPulse) {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
mStarted = true;
+ mSelfPulse = selfPulse;
mPaused = false;
+ mPauseTime = -1;
int size = mNodes.size();
for (int i = 0; i < size; i++) {
@@ -621,26 +665,17 @@
node.mAnimation.setAllowRunningAsynchronously(false);
}
- if (mInterpolator != null) {
- for (int i = 0; i < size; i++) {
- Node node = mNodes.get(i);
- node.mAnimation.setInterpolator(mInterpolator);
- }
+ initAnimation();
+ if (inReverse && !canReverse()) {
+ throw new UnsupportedOperationException("Cannot reverse infinite AnimatorSet");
}
- updateAnimatorsDuration();
- createDependencyGraph();
+ mReversing = inReverse;
// Now that all dependencies are set up, start the animations that should be started.
- boolean setIsEmpty = false;
- if (mStartDelay > 0) {
- start(mRootNode);
- } else if (isEmptySet(this)) {
- // Set is empty or contains only empty animator sets. Skip to end in this case.
- setIsEmpty = true;
- } else {
- // No delay, but there are other animators in the set
- onChildAnimatorEnded(mDelayAnim);
+ boolean setIsEmpty = isEmptySet(this);
+ if (!setIsEmpty) {
+ startAnimation();
}
if (mListeners != null) {
@@ -648,12 +683,12 @@
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this);
+ tmpListeners.get(i).onAnimationStart(this, inReverse);
}
}
if (setIsEmpty) {
// In the case of empty AnimatorSet, we will trigger the onAnimationEnd() right away.
- onChildAnimatorEnded(mDelayAnim);
+ end();
}
}
@@ -690,11 +725,436 @@
mDelayAnim.setDuration(mStartDelay);
}
- void start(final Node node) {
- final Animator anim = node.mAnimation;
- mPlayingSet.add(anim);
- anim.addListener(mSetListener);
- anim.start();
+ @Override
+ void skipToEndValue(boolean inReverse) {
+ if (!isInitialized()) {
+ throw new UnsupportedOperationException("Children must be initialized.");
+ }
+
+ // This makes sure the animation events are sorted an up to date.
+ initAnimation();
+
+ // Calling skip to the end in the sequence that they would be called in a forward/reverse
+ // run, such that the sequential animations modifying the same property would have
+ // the right value in the end.
+ if (inReverse) {
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
+ }
+ }
+ } else {
+ for (int i = 0; i < mEvents.size(); i++) {
+ if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
+ mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Internal only.
+ *
+ * This method sets the animation values based on the play time. It also fast forward or
+ * backward all the child animations progress accordingly.
+ *
+ * This method is also responsible for calling
+ * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
+ * as needed, based on the last play time and current play time.
+ */
+ @Override
+ void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+ if (currentPlayTime < 0 || lastPlayTime < 0) {
+ throw new UnsupportedOperationException("Error: Play time should never be negative.");
+ }
+ // TODO: take into account repeat counts and repeat callback when repeat is implemented.
+ // Clamp currentPlayTime and lastPlayTime
+
+ // TODO: Make this more efficient
+
+ // Convert the play times to the forward direction.
+ if (inReverse) {
+ if (getTotalDuration() == DURATION_INFINITE) {
+ throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
+ + " duration");
+ }
+ long duration = getTotalDuration() - mStartDelay;
+ currentPlayTime = Math.min(currentPlayTime, duration);
+ currentPlayTime = duration - currentPlayTime;
+ lastPlayTime = duration - lastPlayTime;
+ inReverse = false;
+ }
+ // Skip all values to start, and iterate mEvents to get animations to the right fraction.
+ skipToStartValue(false);
+
+ ArrayList<Node> unfinishedNodes = new ArrayList<>();
+ // Assumes forward playing from here on.
+ for (int i = 0; i < mEvents.size(); i++) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.getTime() > currentPlayTime) {
+ break;
+ }
+
+ // This animation started prior to the current play time, and won't finish before the
+ // play time, add to the unfinished list.
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ if (event.mNode.mEndTime == DURATION_INFINITE
+ || event.mNode.mEndTime > currentPlayTime) {
+ unfinishedNodes.add(event.mNode);
+ }
+ }
+ // For animations that do finish before the play time, end them in the sequence that
+ // they would in a normal run.
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ // Skip to the end of the animation.
+ event.mNode.mAnimation.skipToEndValue(false);
+ }
+ }
+
+ // Seek unfinished animation to the right time.
+ for (int i = 0; i < unfinishedNodes.size(); i++) {
+ Node node = unfinishedNodes.get(i);
+ long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
+ node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
+ }
+ }
+
+ @Override
+ boolean isInitialized() {
+ if (mChildrenInitialized) {
+ return true;
+ }
+
+ boolean allInitialized = true;
+ for (int i = 0; i < mNodes.size(); i++) {
+ if (!mNodes.get(i).mAnimation.isInitialized()) {
+ allInitialized = false;
+ break;
+ }
+ }
+ mChildrenInitialized = allInitialized;
+ return mChildrenInitialized;
+ }
+
+ private void skipToStartValue(boolean inReverse) {
+ skipToEndValue(!inReverse);
+ }
+
+ /**
+ * Sets the position of the animation to the specified point in time. This time should
+ * be between 0 and the total duration of the animation, including any repetition. If
+ * the animation has not yet been started, then it will not advance forward after it is
+ * set to this time; it will simply set the time to this value and perform any appropriate
+ * actions based on that time. If the animation is already running, then setCurrentPlayTime()
+ * will set the current playing time to this value and continue playing from that point.
+ *
+ * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
+ * Unless the animation is reversing, the playtime is considered the time since
+ * the end of the start delay of the AnimatorSet in a forward playing direction.
+ *
+ */
+ public void setCurrentPlayTime(long playTime) {
+ if (mReversing && getTotalDuration() == DURATION_INFINITE) {
+ // Should never get here
+ throw new UnsupportedOperationException("Error: Cannot seek in reverse in an infinite"
+ + " AnimatorSet");
+ }
+
+ if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
+ || playTime < 0) {
+ throw new UnsupportedOperationException("Error: Play time should always be in between"
+ + "0 and duration.");
+ }
+
+ initAnimation();
+
+ if (!isStarted()) {
+ if (mReversing) {
+ throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
+ + " should not be set when AnimatorSet is not started.");
+ }
+ if (!mSeekState.isActive()) {
+ findLatestEventIdForTime(0);
+ // Set all the values to start values.
+ initChildren();
+ skipToStartValue(mReversing);
+ mSeekState.setPlayTime(0, mReversing);
+ }
+ animateBasedOnPlayTime(playTime, 0, mReversing);
+ mSeekState.setPlayTime(playTime, mReversing);
+ } else {
+ // If the animation is running, just set the seek time and wait until the next frame
+ // (i.e. doAnimationFrame(...)) to advance the animation.
+ mSeekState.setPlayTime(playTime, mReversing);
+ }
+ }
+
+ private void initChildren() {
+ if (!isInitialized()) {
+ mChildrenInitialized = true;
+ // Forcefully initialize all children based on their end time, so that if the start
+ // value of a child is dependent on a previous animation, the animation will be
+ // initialized after the the previous animations have been advanced to the end.
+ skipToEndValue(false);
+ }
+ }
+
+ /**
+ * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
+ * base.
+ * @return
+ * @hide
+ */
+ @Override
+ public boolean doAnimationFrame(long frameTime) {
+ if (mLastFrameTime < 0) {
+ mFirstFrame = mLastFrameTime = frameTime;
+ }
+
+ // Handle pause/resume
+ if (mPaused) {
+ // Note: Child animations don't receive pause events. Since it's never a contract that
+ // the child animators will be paused when set is paused, this is unlikely to be an
+ // issue.
+ mPauseTime = frameTime;
+ removeAnimationCallback();
+ return false;
+ } else if (mPauseTime > 0) {
+ // Offset by the duration that the animation was paused
+ mFirstFrame += (frameTime - mPauseTime);
+ mPauseTime = -1;
+ }
+
+ // Continue at seeked position
+ if (mSeekState.isActive()) {
+ mSeekState.updateSeekDirection(mReversing);
+ mFirstFrame = frameTime - mSeekState.getPlayTime() - mStartDelay;
+ mSeekState.reset();
+ }
+
+ // This playTime includes the start delay.
+ long playTime = frameTime - mFirstFrame;
+
+ // 1. Pulse the animators that will start or end in this frame
+ // 2. Pulse the animators that will finish in a later frame
+ int latestId = findLatestEventIdForTime(playTime);
+ int startId = mLastEventId;
+
+ handleAnimationEvents(startId, latestId, playTime);
+
+ mLastEventId = latestId;
+
+ // Pump a frame to the on-going animators
+ for (int i = 0; i < mPlayingSet.size(); i++) {
+ Node node = mPlayingSet.get(i);
+ if (!node.mEnded) {
+ node.mEnded = node.mAnimation.doAnimationFrame(getPlayTimeForNode(playTime, node));
+ }
+ }
+
+ // Remove all the finished anims
+ for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
+ if (mPlayingSet.get(i).mEnded) {
+ mPlayingSet.remove(i);
+ }
+ }
+
+ mLastFrameTime = frameTime;
+ if (mPlayingSet.isEmpty()) {
+ boolean finished;
+ if (mReversing) {
+ // Make sure there's no more END event before current event id and after start delay
+ finished = mLastEventId <= 3;
+ } else {
+ // Make sure there's no more START event before current event id:
+ finished = (mLastEventId == mEvents.size() - 1);
+ }
+ if (finished) {
+ endAnimation();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * When playing forward, we call start() at the animation's scheduled start time, and make sure
+ * to pump a frame at the animation's scheduled end time.
+ *
+ * When playing in reverse, we should reverse the animation when we hit animation's end event,
+ * and expect the animation to end at the its delay ended event, rather than start event.
+ */
+ private void handleAnimationEvents(int startId, int latestId, long playTime) {
+ if (mReversing) {
+ startId = startId == -1 ? mEvents.size() : startId;
+ for (int i = startId - 1; i >= latestId; i--) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ mPlayingSet.add(event.mNode);
+ node.mAnimation.startWithoutPulsing(true);
+ node.mAnimation.doAnimationFrame(0);
+ } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
+ // end event:
+ node.mEnded =
+ node.mAnimation.doAnimationFrame(getPlayTimeForNode(playTime, node));
+ }
+ }
+ } else {
+ for (int i = startId + 1; i <= latestId; i++) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_START) {
+ mPlayingSet.add(event.mNode);
+ node.mAnimation.startWithoutPulsing(false);
+ node.mAnimation.doAnimationFrame(0);
+ } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
+ // start event:
+ node.mEnded =
+ node.mAnimation.doAnimationFrame(getPlayTimeForNode(playTime, node));
+ }
+ }
+ }
+ }
+
+ private long getPlayTimeForNode(long overallPlayTime, Node node) {
+ return getPlayTimeForNode(overallPlayTime, node, mReversing);
+ }
+
+ private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
+ if (inReverse) {
+ overallPlayTime = getTotalDuration() - overallPlayTime;
+ return node.mEndTime - overallPlayTime;
+ } else {
+ return overallPlayTime - node.mStartTime;
+ }
+ }
+
+ private void startAnimation() {
+ addDummyListener();
+
+ // Register animation callback
+ addAnimationCallback(mStartDelay);
+
+ if (mSeekState.getPlayTimeNormalized() == 0 && mReversing) {
+ // Maintain old behavior, if seeked to 0 then call reverse, we'll treat the case
+ // the same as no seeking at all.
+ mSeekState.reset();
+ }
+ // Set the child animators to the right end:
+ if (mShouldResetValuesAtStart) {
+ if (mReversing || isInitialized()) {
+ skipToEndValue(!mReversing);
+ } else {
+ // If not all children are initialized and play direction is forward
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ Animator anim = mEvents.get(i).mNode.mAnimation;
+ // Only reset the animations that have been initialized to start value,
+ // so that if they are defined without a start value, they will get the
+ // values set at the right time (i.e. the next animation run)
+ if (anim.isInitialized()) {
+ anim.skipToEndValue(true);
+ }
+ }
+ }
+ }
+ }
+
+ if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
+ long playTime;
+ // If no delay, we need to call start on the first animations to be consistent with old
+ // behavior.
+ if (mSeekState.isActive()) {
+ mSeekState.updateSeekDirection(mReversing);
+ playTime = mSeekState.getPlayTime();
+ } else {
+ playTime = 0;
+ }
+ int toId = findLatestEventIdForTime(playTime);
+ handleAnimationEvents(-1, toId, playTime);
+ mLastEventId = toId;
+ }
+ }
+
+ // This is to work around the issue in b/34736819, as the old behavior in AnimatorSet had
+ // masked a real bug in play movies. TODO: remove this and below once the root cause is fixed.
+ private void addDummyListener() {
+ for (int i = 1; i < mNodes.size(); i++) {
+ mNodes.get(i).mAnimation.addListener(mDummyListener);
+ }
+ }
+
+ private void removeDummyListener() {
+ for (int i = 1; i < mNodes.size(); i++) {
+ mNodes.get(i).mAnimation.removeListener(mDummyListener);
+ }
+ }
+
+ private int findLatestEventIdForTime(long currentPlayTime) {
+ int size = mEvents.size();
+ int latestId = mLastEventId;
+ // Call start on the first animations now to be consistent with the old behavior
+ if (mReversing) {
+ currentPlayTime = getTotalDuration() - currentPlayTime;
+ mLastEventId = mLastEventId == -1 ? size : mLastEventId;
+ for (int j = mLastEventId - 1; j >= 0; j--) {
+ AnimationEvent event = mEvents.get(j);
+ if (event.getTime() >= currentPlayTime) {
+ latestId = j;
+ }
+ }
+ } else {
+ for (int i = mLastEventId + 1; i < size; i++) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.getTime() <= currentPlayTime) {
+ latestId = i;
+ }
+ }
+ }
+ return latestId;
+ }
+
+ private void endAnimation() {
+ mStarted = false;
+ mLastFrameTime = -1;
+ mFirstFrame = -1;
+ mLastEventId = -1;
+ mPaused = false;
+ mPauseTime = -1;
+ mSeekState.reset();
+ mPlayingSet.clear();
+
+ // No longer receive callbacks
+ removeAnimationCallback();
+ // Call end listener
+ if (mListeners != null) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onAnimationEnd(this, mReversing);
+ }
+ }
+ removeDummyListener();
+ mSelfPulse = true;
+ mReversing = false;
+ }
+
+ private void removeAnimationCallback() {
+ if (!mSelfPulse) {
+ return;
+ }
+ AnimationHandler handler = AnimationHandler.getInstance();
+ handler.removeCallback(this);
+ }
+
+ private void addAnimationCallback(long delay) {
+ if (!mSelfPulse) {
+ return;
+ }
+ AnimationHandler handler = AnimationHandler.getInstance();
+ handler.addAnimationFrameCallback(this, delay);
}
@Override
@@ -709,13 +1169,31 @@
* and will populate any appropriate lists, when it is started.
*/
final int nodeCount = mNodes.size();
- anim.mTerminated = false;
anim.mStarted = false;
- anim.mPlayingSet = new ArrayList<Animator>();
+ anim.mLastFrameTime = -1;
+ anim.mFirstFrame = -1;
+ anim.mLastEventId = -1;
+ anim.mPaused = false;
+ anim.mPauseTime = -1;
+ anim.mSeekState = new SeekState();
+ anim.mSelfPulse = true;
+ anim.mPlayingSet = new ArrayList<Node>();
anim.mNodeMap = new ArrayMap<Animator, Node>();
anim.mNodes = new ArrayList<Node>(nodeCount);
- anim.mReversible = mReversible;
- anim.mSetListener = new AnimatorSetListener(anim);
+ anim.mEvents = new ArrayList<AnimationEvent>();
+ anim.mDummyListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (anim.mNodeMap.get(animation) == null) {
+ throw new AndroidRuntimeException("Error: animation ended is not in the node"
+ + " map");
+ }
+ anim.mNodeMap.get(animation).mEnded = true;
+
+ }
+ };
+ anim.mReversing = false;
+ anim.mDependencyDirty = true;
// Walk through the old nodes list, cloning each node and adding it to the new nodemap.
// One problem is that the old node dependencies point to nodes in the old AnimatorSet.
@@ -727,17 +1205,6 @@
node.mTmpClone = nodeClone;
anim.mNodes.add(nodeClone);
anim.mNodeMap.put(nodeClone.mAnimation, nodeClone);
-
- // clear out any listeners that were set up by the AnimatorSet
- final ArrayList<AnimatorListener> cloneListeners = nodeClone.mAnimation.getListeners();
- if (cloneListeners != null) {
- for (int i = cloneListeners.size() - 1; i >= 0; i--) {
- final AnimatorListener listener = cloneListeners.get(i);
- if (listener instanceof AnimatorSetListener) {
- cloneListeners.remove(i);
- }
- }
- }
}
anim.mRootNode = mRootNode.mTmpClone;
@@ -771,89 +1238,6 @@
}
- private static class AnimatorSetListener implements AnimatorListener {
-
- private AnimatorSet mAnimatorSet;
-
- AnimatorSetListener(AnimatorSet animatorSet) {
- mAnimatorSet = animatorSet;
- }
-
- public void onAnimationCancel(Animator animation) {
-
- if (!mAnimatorSet.mTerminated) {
- // Listeners are already notified of the AnimatorSet canceling in cancel().
- // The logic below only kicks in when animations end normally
- if (mAnimatorSet.mPlayingSet.size() == 0) {
- ArrayList<AnimatorListener> listeners = mAnimatorSet.mListeners;
- if (listeners != null) {
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; ++i) {
- listeners.get(i).onAnimationCancel(mAnimatorSet);
- }
- }
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- public void onAnimationEnd(Animator animation) {
- animation.removeListener(this);
- mAnimatorSet.mPlayingSet.remove(animation);
- mAnimatorSet.onChildAnimatorEnded(animation);
- }
-
- // Nothing to do
- public void onAnimationRepeat(Animator animation) {
- }
-
- // Nothing to do
- public void onAnimationStart(Animator animation) {
- }
-
- }
-
- private void onChildAnimatorEnded(Animator animation) {
- Node animNode = mNodeMap.get(animation);
- animNode.mEnded = true;
-
- if (!mTerminated) {
- List<Node> children = animNode.mChildNodes;
- // Start children animations, if any.
- int childrenSize = children == null ? 0 : children.size();
- for (int i = 0; i < childrenSize; i++) {
- if (children.get(i).mLatestParent == animNode) {
- start(children.get(i));
- }
- }
- // Listeners are already notified of the AnimatorSet ending in cancel() or
- // end(); the logic below only kicks in when animations end normally
- boolean allDone = true;
- // Traverse the tree and find if there's any unfinished node
- int size = mNodes.size();
- for (int i = 0; i < size; i++) {
- if (!mNodes.get(i).mEnded) {
- allDone = false;
- break;
- }
- }
- if (allDone) {
- mStarted = false;
- mPaused = false;
- // If this was the last child animation to end, then notify listeners that this
- // AnimatorSet has ended
- if (mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(this);
- }
- }
- }
- }
- }
-
/**
* AnimatorSet is only reversible when the set contains no sequential animation, and no child
* animators have a start delay.
@@ -861,32 +1245,21 @@
*/
@Override
public boolean canReverse() {
- if (!mReversible) {
- return false;
- }
- // Loop to make sure all the Nodes can reverse.
- int size = mNodes.size();
- for (int i = 0; i < size; i++) {
- Node node = mNodes.get(i);
- if (!node.mAnimation.canReverse() || node.mAnimation.getStartDelay() > 0) {
- return false;
- }
- }
- return true;
+ return getTotalDuration() != DURATION_INFINITE;
}
/**
- * @hide
+ * Plays the AnimatorSet in reverse. If the animation has been seeked to a specific play time
+ * using {@link #setCurrentPlayTime(long)}, it will play backwards from the point seeked when
+ * reverse was called. Otherwise, then it will start from the end and play backwards. This
+ * behavior is only set for the current animation; future playing of the animation will use the
+ * default behavior of playing forward.
+ * <p>
+ * Note: reverse is not supported for infinite AnimatorSet.
*/
@Override
public void reverse() {
- if (canReverse()) {
- int size = mNodes.size();
- for (int i = 0; i < size; i++) {
- Node node = mNodes.get(i);
- node.mAnimation.reverse();
- }
- }
+ start(true, true);
}
@Override
@@ -993,18 +1366,124 @@
mRootNode.mEndTime = mDelayAnim.getDuration();
updatePlayTime(mRootNode, visited);
- long maxEndTime = 0;
- for (int i = 0; i < size; i++) {
+ sortAnimationEvents();
+ mTotalDuration = mEvents.get(mEvents.size() - 1).getTime();
+ }
+
+ private void sortAnimationEvents() {
+ // Sort the list of events in ascending order of their time
+ // Create the list including the delay animation.
+ mEvents.clear();
+ for (int i = 1; i < mNodes.size(); i++) {
Node node = mNodes.get(i);
- node.mTotalDuration = node.mAnimation.getTotalDuration();
- if (node.mEndTime == DURATION_INFINITE) {
- maxEndTime = DURATION_INFINITE;
- break;
+ mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_START));
+ mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_DELAY_ENDED));
+ mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_END));
+ }
+ mEvents.sort(new Comparator<AnimationEvent>() {
+ @Override
+ public int compare(AnimationEvent e1, AnimationEvent e2) {
+ long t1 = e1.getTime();
+ long t2 = e2.getTime();
+ if (t1 == t2) {
+ // For events that happen at the same time, we need them to be in the sequence
+ // (end, start, start delay ended)
+ if (e2.mEvent + e1.mEvent == AnimationEvent.ANIMATION_START
+ + AnimationEvent.ANIMATION_DELAY_ENDED) {
+ // Ensure start delay happens after start
+ return e1.mEvent - e2.mEvent;
+ } else {
+ return e2.mEvent - e1.mEvent;
+ }
+ }
+ if (t2 == DURATION_INFINITE) {
+ return -1;
+ }
+ if (t1 == DURATION_INFINITE) {
+ return 1;
+ }
+ // When neither event happens at INFINITE time:
+ return (int) (t1 - t2);
+ }
+ });
+
+ int eventSize = mEvents.size();
+ // For the same animation, start event has to happen before end.
+ for (int i = 0; i < eventSize;) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ boolean needToSwapStart;
+ if (event.mNode.mStartTime == event.mNode.mEndTime) {
+ needToSwapStart = true;
+ } else if (event.mNode.mEndTime == event.mNode.mStartTime
+ + event.mNode.mAnimation.getStartDelay()) {
+ // Swapping start delay
+ needToSwapStart = false;
+ } else {
+ i++;
+ continue;
+ }
+
+ int startEventId = eventSize;
+ int startDelayEndId = eventSize;
+ for (int j = i + 1; j < eventSize; j++) {
+ if (startEventId < eventSize && startDelayEndId < eventSize) {
+ break;
+ }
+ if (mEvents.get(j).mNode == event.mNode) {
+ if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_START) {
+ // Found start event
+ startEventId = j;
+ } else if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ startDelayEndId = j;
+ }
+ }
+
+ }
+ if (needToSwapStart && startEventId == mEvents.size()) {
+ throw new UnsupportedOperationException("Something went wrong, no start is"
+ + "found after stop for an animation that has the same start and end"
+ + "time.");
+
+ }
+ if (startDelayEndId == mEvents.size()) {
+ throw new UnsupportedOperationException("Something went wrong, no start"
+ + "delay end is found after stop for an animation");
+
+ }
+
+ // We need to make sure start is inserted before start delay ended event,
+ // because otherwise inserting start delay ended events first would change
+ // the start event index.
+ if (needToSwapStart) {
+ AnimationEvent startEvent = mEvents.remove(startEventId);
+ mEvents.add(i, startEvent);
+ i++;
+ }
+
+ AnimationEvent startDelayEndEvent = mEvents.remove(startDelayEndId);
+ mEvents.add(i, startDelayEndEvent);
+ i += 2;
} else {
- maxEndTime = node.mEndTime > maxEndTime ? node.mEndTime : maxEndTime;
+ i++;
}
}
- mTotalDuration = maxEndTime;
+
+ if (!mEvents.isEmpty() && mEvents.get(0).mEvent != AnimationEvent.ANIMATION_START) {
+ throw new UnsupportedOperationException(
+ "Sorting went bad, the start event should always be at index 0");
+ }
+
+ // Add AnimatorSet's start delay node to the beginning
+ mEvents.add(0, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_START));
+ mEvents.add(1, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_DELAY_ENDED));
+ mEvents.add(2, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_END));
+
+ if (mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_START
+ || mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ throw new UnsupportedOperationException(
+ "Something went wrong, the last event is not an end event");
+ }
}
/**
@@ -1235,6 +1714,95 @@
}
/**
+ * This class is a wrapper around a node and an event for the animation corresponding to the
+ * node. The 3 types of events represent the start of an animation, the end of a start delay of
+ * an animation, and the end of an animation. When playing forward (i.e. in the non-reverse
+ * direction), start event marks when start() should be called, and end event corresponds to
+ * when the animation should finish. When playing in reverse, start delay will not be a part
+ * of the animation. Therefore, reverse() is called at the end event, and animation should end
+ * at the delay ended event.
+ */
+ private static class AnimationEvent {
+ static final int ANIMATION_START = 0;
+ static final int ANIMATION_DELAY_ENDED = 1;
+ static final int ANIMATION_END = 2;
+ final Node mNode;
+ final int mEvent;
+
+ AnimationEvent(Node node, int event) {
+ mNode = node;
+ mEvent = event;
+ }
+
+ long getTime() {
+ if (mEvent == ANIMATION_START) {
+ return mNode.mStartTime;
+ } else if (mEvent == ANIMATION_DELAY_ENDED) {
+ return mNode.mStartTime + mNode.mAnimation.getStartDelay();
+ } else {
+ return mNode.mEndTime;
+ }
+ }
+
+ public String toString() {
+ String eventStr = mEvent == ANIMATION_START ? "start" : (
+ mEvent == ANIMATION_DELAY_ENDED ? "delay ended" : "end");
+ return eventStr + " " + mNode.mAnimation.toString();
+ }
+ }
+
+ private class SeekState {
+ private long mPlayTime = -1;
+ private boolean mSeekingInReverse = false;
+ void reset() {
+ mPlayTime = -1;
+ mSeekingInReverse = false;
+ }
+
+ void setPlayTime(long playTime, boolean inReverse) {
+ // TODO: This can be simplified.
+
+ // Clamp the play time
+ if (getTotalDuration() != DURATION_INFINITE) {
+ mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+ }
+ mPlayTime = Math.max(0, mPlayTime);
+ mSeekingInReverse = inReverse;
+ }
+
+ void updateSeekDirection(boolean inReverse) {
+ // Change seek direction without changing the overall fraction
+ if (inReverse && getTotalDuration() == DURATION_INFINITE) {
+ throw new UnsupportedOperationException("Error: Cannot reverse infinite animator"
+ + " set");
+ }
+ if (mPlayTime >= 0) {
+ if (inReverse != mSeekingInReverse) {
+ mPlayTime = getTotalDuration() - mStartDelay - mPlayTime;
+ }
+ }
+ }
+
+ long getPlayTime() {
+ return mPlayTime;
+ }
+
+ /**
+ * Returns the playtime assuming the animation is forward playing
+ */
+ long getPlayTimeNormalized() {
+ if (mReversing) {
+ return getTotalDuration() - mStartDelay - mPlayTime;
+ }
+ return mPlayTime;
+ }
+
+ boolean isActive() {
+ return mPlayTime != -1;
+ }
+ }
+
+ /**
* The <code>Builder</code> object is a utility class to facilitate adding animations to a
* <code>AnimatorSet</code> along with the relationships between the various animations. The
* intention of the <code>Builder</code> methods, along with the {@link
@@ -1328,7 +1896,6 @@
* {@link AnimatorSet#play(Animator)} method ends.
*/
public Builder before(Animator anim) {
- mReversible = false;
Node node = getNodeForAnimation(anim);
mCurrentNode.addChild(node);
return this;
@@ -1343,7 +1910,6 @@
* {@link AnimatorSet#play(Animator)} method to play.
*/
public Builder after(Animator anim) {
- mReversible = false;
Node node = getNodeForAnimation(anim);
mCurrentNode.addParent(node);
return this;
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 4707bed..1e1f155 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -992,6 +992,11 @@
}
@Override
+ boolean isInitialized() {
+ return mInitialized;
+ }
+
+ @Override
public ObjectAnimator clone() {
final ObjectAnimator anim = (ObjectAnimator) super.clone();
return anim;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index f0fc8af..470523f 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -24,6 +24,7 @@
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
@@ -67,7 +68,7 @@
* </div>
*/
@SuppressWarnings("unchecked")
-public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
+public class ValueAnimator extends Animator {
private static final String TAG = "ValueAnimator";
private static final boolean DEBUG = false;
@@ -90,7 +91,7 @@
*
* Whenever mStartTime is set, you must also update mStartTimeCommitted.
*/
- long mStartTime;
+ long mStartTime = -1;
/**
* When true, the start time has been firmly committed as a chosen reference point in
@@ -152,7 +153,13 @@
/**
* Tracks the time (in milliseconds) when the last frame arrived.
*/
- private long mLastFrameTime = 0;
+ private long mLastFrameTime = -1;
+
+ /**
+ * Tracks the time (in milliseconds) when the first frame arrived. Note the frame may arrive
+ * during the start delay.
+ */
+ private long mFirstFrameTime = -1;
/**
* Additional playing state to indicate whether an animator has been start()'d. There is
@@ -212,6 +219,12 @@
private int mRepeatMode = RESTART;
/**
+ * Whether or not the animator should register for its own animation callback to receive
+ * animation pulse.
+ */
+ private boolean mSelfPulse = true;
+
+ /**
* The time interpolator to be used. The elapsed fraction of the animation will be passed
* through this interpolator to calculate the interpolated fraction, which is then used to
* calculate the animated values.
@@ -628,7 +641,7 @@
mSeekFraction = fraction;
}
mOverallFraction = fraction;
- final float currentIterationFraction = getCurrentIterationFraction(fraction);
+ final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
animateValue(currentIterationFraction);
}
@@ -654,11 +667,11 @@
* should be played backwards. E.g. When the animation is played backwards in an iteration,
* the fraction for that iteration will go from 1f to 0f.
*/
- private float getCurrentIterationFraction(float fraction) {
+ private float getCurrentIterationFraction(float fraction, boolean inReverse) {
fraction = clampFraction(fraction);
int iteration = getCurrentIteration(fraction);
float currentFraction = fraction - iteration;
- return shouldPlayBackward(iteration) ? 1f - currentFraction : currentFraction;
+ return shouldPlayBackward(iteration, inReverse) ? 1f - currentFraction : currentFraction;
}
/**
@@ -682,18 +695,18 @@
* whether the entire animation is being reversed, 2) repeat mode applied to the current
* iteration.
*/
- private boolean shouldPlayBackward(int iteration) {
+ private boolean shouldPlayBackward(int iteration, boolean inReverse) {
if (iteration > 0 && mRepeatMode == REVERSE &&
(iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
// if we were seeked to some other iteration in a reversing animator,
// figure out the correct direction to start playing based on the iteration
- if (mReversing) {
+ if (inReverse) {
return (iteration % 2) == 0;
} else {
return (iteration % 2) != 0;
}
} else {
- return mReversing;
+ return inReverse;
}
}
@@ -965,7 +978,7 @@
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this);
+ tmpListeners.get(i).onAnimationStart(this, mReversing);
}
}
mStartListenersCalled = true;
@@ -984,11 +997,12 @@
*
* @param playBackwards Whether the ValueAnimator should start playing in reverse.
*/
- private void start(boolean playBackwards) {
+ private void start(boolean playBackwards, boolean selfPulse) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
+ mSelfPulse = selfPulse;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
@@ -1006,11 +1020,11 @@
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
- mLastFrameTime = 0;
- AnimationHandler animationHandler = AnimationHandler.getInstance();
- animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
+ mLastFrameTime = -1;
+ mFirstFrameTime = -1;
+ addAnimationCallback((long) (mStartDelay * sDurationScale));
- if (mStartDelay == 0 || mSeekFraction >= 0) {
+ if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
@@ -1026,9 +1040,13 @@
}
}
+ void startWithoutPulsing(boolean inReverse) {
+ start(inReverse, false);
+ }
+
@Override
public void start() {
- start(false);
+ start(false, true);
}
@Override
@@ -1073,7 +1091,7 @@
} else if (!mInitialized) {
initAnimation();
}
- animateValue(shouldPlayBackward(mRepeatCount) ? 0f : 1f);
+ animateValue(shouldPlayBackward(mRepeatCount, mReversing) ? 0f : 1f);
endAnimation();
}
@@ -1086,8 +1104,7 @@
if (mPaused && !mResumed) {
mResumed = true;
if (mPauseTime > 0) {
- AnimationHandler handler = AnimationHandler.getInstance();
- handler.addAnimationFrameCallback(this, 0);
+ addAnimationCallback(0);
}
}
super.resume();
@@ -1133,7 +1150,7 @@
mReversing = !mReversing;
end();
} else {
- start(true);
+ start(true, true);
}
}
@@ -1153,8 +1170,7 @@
if (mAnimationEndRequested) {
return;
}
- AnimationHandler handler = AnimationHandler.getInstance();
- handler.removeCallback(this);
+ removeAnimationCallback();
mAnimationEndRequested = true;
mPaused = false;
@@ -1166,16 +1182,18 @@
mRunning = false;
mStarted = false;
mStartListenersCalled = false;
+ mLastFrameTime = -1;
+ mFirstFrameTime = -1;
mReversing = false;
- mLastFrameTime = 0;
if (notify && mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(this);
+ tmpListeners.get(i).onAnimationEnd(this, mReversing);
}
}
+ mReversing = false;
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
@@ -1211,7 +1229,7 @@
* is called (or after start delay if any), which may be before the animation loop starts.
*/
private boolean isPulsingInternal() {
- return mLastFrameTime > 0;
+ return mLastFrameTime >= 0;
}
/**
@@ -1276,24 +1294,116 @@
done = true;
}
mOverallFraction = clampFraction(fraction);
- float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
+ float currentIterationFraction = getCurrentIterationFraction(
+ mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
/**
+ * Internal use only.
+ *
+ * This method does not modify any fields of the animation. It should be called when seeking
+ * in an AnimatorSet. When the last play time and current play time are of different repeat
+ * iterations,
+ * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)}
+ * will be called.
+ */
+ @Override
+ void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+ if (currentPlayTime < 0 || lastPlayTime < 0) {
+ throw new UnsupportedOperationException("Error: Play time should never be negative.");
+ }
+
+ initAnimation();
+ // Check whether repeat callback is needed only when repeat count is non-zero
+ if (mRepeatCount > 0) {
+ int iteration = (int) (currentPlayTime / mDuration);
+ int lastIteration = (int) (lastPlayTime / mDuration);
+
+ // Clamp iteration to [0, mRepeatCount]
+ iteration = Math.min(iteration, mRepeatCount);
+ lastIteration = Math.min(lastIteration, mRepeatCount);
+
+ if (iteration != lastIteration) {
+ if (mListeners != null) {
+ int numListeners = mListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ mListeners.get(i).onAnimationRepeat(this);
+ }
+ }
+ }
+ }
+
+ if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
+ skipToEndValue(inReverse);
+ } else {
+ // Find the current fraction:
+ float fraction = currentPlayTime / (float) mDuration;
+ fraction = getCurrentIterationFraction(fraction, inReverse);
+ animateValue(fraction);
+ }
+ }
+
+ /**
+ * Internal use only.
+ * Skips the animation value to end/start, depending on whether the play direction is forward
+ * or backward.
+ *
+ * @param inReverse whether the end value is based on a reverse direction. If yes, this is
+ * equivalent to skip to start value in a forward playing direction.
+ */
+ void skipToEndValue(boolean inReverse) {
+ initAnimation();
+ float endFraction = inReverse ? 0f : 1f;
+ if (mRepeatCount % 2 == 1 && mRepeatMode == REVERSE) {
+ // This would end on fraction = 0
+ endFraction = 0f;
+ }
+ animateValue(endFraction);
+ }
+
+ /**
* Processes a frame of the animation, adjusting the start time if needed.
*
* @param frameTime The frame time.
* @return true if the animation has ended.
* @hide
*/
- public final void doAnimationFrame(long frameTime) {
- AnimationHandler handler = AnimationHandler.getInstance();
- if (mLastFrameTime == 0) {
+ public final boolean doAnimationFrame(long frameTime) {
+ if (!mRunning && mStartTime < 0) {
+ // First frame during delay
+ mStartTime = frameTime + mStartDelay;
+ }
+
+ // Handle pause/resume
+ if (mPaused) {
+ mPauseTime = frameTime;
+ removeAnimationCallback();
+ return false;
+ } else if (mResumed) {
+ mResumed = false;
+ if (mPauseTime > 0) {
+ // Offset by the duration that the animation was paused
+ mStartTime += (frameTime - mPauseTime);
+ }
+ }
+
+ if (!mRunning) {
+ // If not running, that means the animation is in the start delay phase. In the case of
+ // reversing, we want to run start delay in the end.
+ if (mStartTime > frameTime) {
+ // During start delay
+ return false;
+ } else {
+ // Start delay has passed.
+ mRunning = true;
+ }
+ }
+
+ if (mLastFrameTime < 0) {
// First frame
- handler.addOneShotCommitCallback(this);
if (mStartDelay > 0) {
startAnimation();
}
@@ -1307,19 +1417,6 @@
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
mLastFrameTime = frameTime;
- if (mPaused) {
- mPauseTime = frameTime;
- handler.removeCallback(this);
- return;
- } else if (mResumed) {
- mResumed = false;
- if (mPauseTime > 0) {
- // Offset by the duration that the animation was paused
- mStartTime += (frameTime - mPauseTime);
- mStartTimeCommitted = false; // allow start time to be compensated for jank
- }
- handler.addOneShotCommitCallback(this);
- }
// The frame time might be before the start time during the first frame of
// an animation. The "current time" must always be on or after the start
// time to avoid animating frames at negative time intervals. In practice, this
@@ -1330,6 +1427,31 @@
if (finished) {
endAnimation();
}
+ return finished;
+ }
+
+ private void addOneShotCommitCallback() {
+ if (!mSelfPulse) {
+ return;
+ }
+ AnimationHandler handler = AnimationHandler.getInstance();
+ handler.addOneShotCommitCallback(this);
+ }
+
+ private void removeAnimationCallback() {
+ if (!mSelfPulse) {
+ return;
+ }
+ AnimationHandler handler = AnimationHandler.getInstance();
+ handler.removeCallback(this);
+ }
+
+ private void addAnimationCallback(long delay) {
+ if (!mSelfPulse) {
+ return;
+ }
+ AnimationHandler handler = AnimationHandler.getInstance();
+ handler.addAnimationFrameCallback(this, delay);
}
/**
@@ -1384,11 +1506,12 @@
anim.mPaused = false;
anim.mResumed = false;
anim.mStartListenersCalled = false;
- anim.mStartTime = 0;
+ anim.mStartTime = -1;
anim.mStartTimeCommitted = false;
anim.mAnimationEndRequested = false;
- anim.mPauseTime = 0;
- anim.mLastFrameTime = 0;
+ anim.mPauseTime = -1;
+ anim.mLastFrameTime = -1;
+ anim.mFirstFrameTime = -1;
anim.mOverallFraction = 0;
anim.mCurrentFraction = 0;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f6771857..1d84ff5 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;
@@ -119,9 +117,8 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.autofill.AutoFillId;
-import android.view.autofill.Dataset;
-import android.view.autofill.DatasetField;
-import android.view.autofill.VirtualViewDelegate;
+import android.view.autofill.AutoFillManager;
+import android.view.autofill.AutoFillSession;
import android.widget.AdapterView;
import android.widget.Toast;
import android.widget.Toolbar;
@@ -792,8 +789,6 @@
private VoiceInteractor mVoiceInteractor;
- private TextAssistant mTextAssistant;
-
private CharSequence mTitle;
private int mTitleColor = 0;
@@ -852,10 +847,7 @@
private boolean mHasCurrentPermissionsRequest;
@GuardedBy("this")
- private WeakReference<IAutoFillAppCallback> mAutoFillCallback;
-
- @GuardedBy("this")
- private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+ private AutoFillSession mAutoFillSession;
private static native String getDlWarning();
@@ -1398,24 +1390,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
@@ -1726,76 +1700,17 @@
}
/**
- * Lazily sets the {@link #mAutoFillDelegateCallback}.
- */
- private void setAutoFillDelegateCallback() {
- synchronized (this) {
- if (mAutoFillDelegateCallback == null) {
- mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
- // TODO(b/33197203): implement
- };
- }
- }
- }
-
- /**
* Lazily gets the {@link IAutoFillAppCallback} for this activitity.
*
* <p>This callback is used by the {@link AutoFillService} app to auto-fill the activity fields.
*/
- WeakReference<IAutoFillAppCallback> getAutoFillCallback() {
+ IAutoFillAppCallback getAutoFillCallback() {
synchronized (this) {
- if (mAutoFillCallback == null) {
- final IAutoFillAppCallback cb = new IAutoFillAppCallback.Stub() {
- @Override
- public void autoFill(Dataset dataset) throws RemoteException {
- // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
- // dataset.extras to service
- runOnUiThread(() -> {
- final View root = getWindow().getDecorView().getRootView();
- for (DatasetField field : dataset.getFields()) {
- final AutoFillId id = field.getId();
- if (id == null) {
- Log.w(TAG, "autoFill(): null id on " + field);
- continue;
- }
- final int viewId = id.getViewId();
- final View view = root.findViewByAccessibilityIdTraversal(viewId);
- if (view == null) {
- Log.w(TAG, "autoFill(): no View with id " + viewId);
- continue;
- }
-
- // TODO(b/33197203): handle protected value (like credit card)
- if (id.isVirtual()) {
- // Delegate virtual fields to provider.
- setAutoFillDelegateCallback();
- final VirtualViewDelegate mgr = view
- .getAutoFillVirtualViewDelegate(
- mAutoFillDelegateCallback);
- if (mgr == null) {
- Log.w(TAG, "autoFill(): cannot fill virtual " + id
- + "; no auto-fill provider for view "
- + view.getClass());
- continue;
- }
- if (DEBUG_AUTO_FILL) {
- Log.d(TAG, "autoFill(): delegating " + id
- + " to virtual manager " + mgr);
- }
- mgr.autoFill(id.getVirtualChildId(), field.getValue());
- } else {
- // Handle non-virtual fields itself.
- view.autoFill(field.getValue());
- }
- }
- });
- }
- };
- mAutoFillCallback = new WeakReference<IAutoFillAppCallback>(cb);
+ if (mAutoFillSession == null) {
+ mAutoFillSession = new AutoFillSession(this);
}
+ return mAutoFillSession.getCallback();
}
- return mAutoFillCallback;
}
/**
@@ -2029,78 +1944,53 @@
}
/**
- * Puts the activity in picture-in-picture mode.
+ * Puts the activity in picture-in-picture mode if possible in the current system state. Any
+ * prior calls to {@link #setPictureInPictureArgs(PictureInPictureArgs)} will still apply when
+ * entering picture-in-picture through this call.
+ *
+ * @see #enterPictureInPictureMode(PictureInPictureArgs)
* @see android.R.attr#supportsPictureInPicture
*/
public void enterPictureInPictureMode() {
- try {
- ActivityManager.getService().enterPictureInPictureMode(mToken);
- } catch (RemoteException e) {
- }
+ enterPictureInPictureMode(new PictureInPictureArgs());
}
/**
- * Puts the activity in picture-in-picture mode with a given aspect ratio.
+ * Puts the activity in picture-in-picture mode if possible in the current system state with
+ * explicit given arguments. Only the set parameters in {@param args} will override prior calls
+ * {@link #setPictureInPictureArgs(PictureInPictureArgs)}.
+ *
+ * The system may disallow entering picture-in-picture in various cases, including when the
+ * activity is not visible.
+ *
* @see android.R.attr#supportsPictureInPicture
*
- * @param aspectRatio the new aspect ratio of the picture-in-picture.
+ * @param args the explicit non-null arguments to use when entering picture-in-picture.
+ * @return whether the system successfully entered picture-in-picture.
*/
- public void enterPictureInPictureMode(float aspectRatio) {
+ public boolean enterPictureInPictureMode(@NonNull PictureInPictureArgs args) {
try {
- ActivityManagerNative.getDefault().enterPictureInPictureModeWithAspectRatio(mToken,
- aspectRatio);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Requests to the system that the activity can be automatically put into picture-in-picture
- * mode when the user leaves the activity causing it normally to be hidden. Generally, this
- * happens when another task is brought to the forground or the task containing this activity
- * is moved to the background. This is a *not* a guarantee that the activity will actually be
- * put in picture-in-picture mode, and depends on a number of factors, including whether there
- * is already something in picture-in-picture.
- *
- * @param enterPictureInPictureOnMoveToBg whether or not this activity can automatically enter
- * picture-in-picture
- */
- public void enterPictureInPictureModeOnMoveToBackground(
- boolean enterPictureInPictureOnMoveToBg) {
- try {
- ActivityManagerNative.getDefault().enterPictureInPictureModeOnMoveToBackground(mToken,
- enterPictureInPictureOnMoveToBg);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Updates the aspect ratio of the current picture-in-picture activity if this activity is
- * already in picture-in-picture mode, or sets it to be used later if
- * {@link #enterPictureInPictureModeOnMoveToBackground(boolean)} is requested.
- *
- * @param aspectRatio the new aspect ratio of the picture-in-picture.
- */
- public void setPictureInPictureAspectRatio(float aspectRatio) {
- try {
- ActivityManagerNative.getDefault().setPictureInPictureAspectRatio(mToken, aspectRatio);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Updates the set of user actions associated with the picture-in-picture activity.
- *
- * @param actions the new actions for picture-in-picture (can be null to reset the set of
- * actions). The maximum number of actions that will be displayed on this device
- * is defined by {@link ActivityManager#getMaxNumPictureInPictureActions()}.
- */
- public void setPictureInPictureActions(List<RemoteAction> actions) {
- try {
- if (actions == null) {
- actions = new ArrayList<>();
+ if (args == null) {
+ throw new IllegalArgumentException("Expected non-null picture-in-picture args");
}
- ActivityManagerNative.getDefault().setPictureInPictureActions(mToken,
- new ParceledListSlice<RemoteAction>(actions));
+ return ActivityManagerNative.getDefault().enterPictureInPictureMode(mToken, args);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Updates the properties of the picture-in-picture activity, or sets it to be used later when
+ * {@link #enterPictureInPictureMode()} is called.
+ *
+ * @param args the new properties of the picture-in-picture.
+ */
+ public void setPictureInPictureArgs(@NonNull PictureInPictureArgs args) {
+ try {
+ if (args == null) {
+ throw new IllegalArgumentException("Expected non-null picture-in-picture args");
+ }
+ ActivityManagerNative.getDefault().setPictureInPictureArgs(mToken, args);
} catch (RemoteException e) {
}
}
@@ -3155,7 +3045,7 @@
*/
@Override
public void enterPictureInPictureModeIfPossible() {
- if (mActivityInfo.resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE) {
+ if (mActivityInfo.supportsPictureInPicture()) {
enterPictureInPictureMode();
}
}
@@ -4377,7 +4267,7 @@
* @param requestCode If >= 0, this code will be returned in
* onActivityResult() when the activity exits.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
@@ -4586,7 +4476,7 @@
* <var>flagsMask</var>
* @param extraFlags Always set to 0.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details. If options
* have also been supplied by the IntentSender, options given here will
* override any that conflict with those given by the IntentSender.
@@ -4671,7 +4561,7 @@
*
* @param intent The intent to start.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
@@ -4720,7 +4610,7 @@
*
* @param intents The intents to start.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
@@ -4769,7 +4659,7 @@
* <var>flagsMask</var>
* @param extraFlags Always set to 0.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details. If options
* have also been supplied by the IntentSender, options given here will
* override any that conflict with those given by the IntentSender.
@@ -4829,7 +4719,7 @@
* onActivityResult() when the activity exits, as described in
* {@link #startActivityForResult}.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @return If a new activity was launched then true is returned; otherwise
@@ -4906,7 +4796,7 @@
* your own activity; the only changes you can make are to the extras
* inside of it.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @return Returns a boolean indicating whether there was another Activity
@@ -4961,7 +4851,7 @@
* @param intent The intent to start.
* @param requestCode Reply request code. < 0 if reply is not requested.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
@@ -5014,7 +4904,7 @@
* @param intent The intent to start.
* @param requestCode Reply request code. < 0 if reply is not requested.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
@@ -6114,9 +6004,9 @@
getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
}
- if (mAutoFillCallback != null) {
- writer.print(prefix); writer.print("mAutoFillCallback: " );
- writer.println(mAutoFillCallback.get());
+ if (mAutoFillSession!= null) {
+ writer.print(prefix); writer.print("mAutoFillSession: " );
+ writer.println(mAutoFillSession);
}
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
@@ -6839,6 +6729,8 @@
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
+
+ mWindow.setColorMode(info.colorMode);
}
/** @hide */
@@ -7206,7 +7098,7 @@
*
* @return True if caption is displayed on content, false if it pushes the content down.
*
- * @see {@link #setOverlayWithDecorCaptionEnabled(boolean)}
+ * @see #setOverlayWithDecorCaptionEnabled(boolean)
*/
public boolean isOverlayWithDecorCaptionEnabled() {
return mWindow.isOverlayWithDecorCaptionEnabled();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2e3c100..b367d0c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -133,7 +133,7 @@
public final static boolean ENABLE_TASK_SNAPSHOTS;
static {
- ENABLE_TASK_SNAPSHOTS = SystemProperties.getBoolean("persist.enable_task_snapshots", false);
+ ENABLE_TASK_SNAPSHOTS = SystemProperties.getBoolean("persist.enable_task_snapshots", true);
}
static final class UidObserver extends IUidObserver.Stub {
@@ -520,15 +520,19 @@
/** @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}: disable/cancel pending
- * launches. */
- public static final int APP_START_MODE_DISABLED = 2;
+ /** @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#isAppStartModeDisabled}: disable/cancel pending
+ * launches; this is the mode for ephemeral apps. */
+ public static final int APP_START_MODE_DISABLED = 3;
/**
* Lock task mode is not active.
@@ -694,6 +698,13 @@
}
/**
+ * Returns true if the input stack is affected by drag resizing.
+ */
+ public static boolean isStackAffectedByDragResizing(int stackId) {
+ return isStaticStack(stackId) && stackId != PINNED_STACK_ID;
+ }
+
+ /**
* Returns true if the windows of tasks being moved to the target stack from the source
* stack should be replaced, meaning that window manager will keep the old window around
* until the new is ready.
@@ -1478,7 +1489,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}.
@@ -1529,7 +1540,7 @@
} else {
dest.writeInt(0);
}
- dest.writeInt(isDockable ? 1 : 0);
+ dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
dest.writeInt(resizeMode);
}
@@ -1553,7 +1564,7 @@
numActivities = source.readInt();
bounds = source.readInt() > 0 ?
Rect.CREATOR.createFromParcel(source) : null;
- isDockable = source.readInt() == 1;
+ supportsSplitScreenMultiWindow = source.readInt() == 1;
resizeMode = source.readInt();
}
@@ -1741,7 +1752,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}.
@@ -1771,7 +1782,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);
}
@@ -1788,7 +1799,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();
}
@@ -2329,13 +2340,13 @@
public static final int FLAG_FOREGROUND = 1<<1;
/**
- * Bit for {@link #flags): set if the service is running in a
+ * Bit for {@link #flags}: set if the service is running in a
* core system process.
*/
public static final int FLAG_SYSTEM_PROCESS = 1<<2;
/**
- * Bit for {@link #flags): set if the service is running in a
+ * Bit for {@link #flags}: set if the service is running in a
* persistent process.
*/
public static final int FLAG_PERSISTENT_PROCESS = 1<<3;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 87700dc..e848080 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -64,36 +64,6 @@
public static final int APP_TRANSITION_TIMEOUT = 3;
/**
- * Class to hold deferred properties to apply for picture-in-picture for a given activity.
- */
- public static class PictureInPictureArguments {
- /**
- * The expected aspect ratio of the picture-in-picture.
- */
- public float aspectRatio;
-
- /**
- * The set of actions that are associated with this activity when in picture in picture.
- */
- public List<RemoteAction> userActions = new ArrayList<>();
-
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "aspectRatio=" + aspectRatio);
- if (userActions.isEmpty()) {
- pw.println(prefix + " userActions=[]");
- } else {
- pw.println(prefix + " userActions=[");
- for (int i = 0; i < userActions.size(); i++) {
- RemoteAction action = userActions.get(i);
- pw.print(prefix + " Action[" + i + "]: ");
- action.dump("", pw);
- }
- pw.println(prefix + " ]");
- }
- }
- }
-
- /**
* Grant Uri permissions from one app to another. This method only extends
* permission grants if {@code callingUid} has permission to them.
*/
@@ -184,6 +154,12 @@
public abstract List<IBinder> getTopVisibleActivities();
/**
+ * Returns the top, focused activity of the currently visible stack, but only if it belongs to
+ * the given UID.
+ */
+ public abstract IBinder getTopVisibleActivity(int uid);
+
+ /**
* Callback for window manager to let activity manager know that docked stack changes its
* minimized state.
*/
@@ -201,6 +177,17 @@
public abstract void setPendingIntentWhitelistDuration(IIntentSender target, long duration);
/**
+ * Allow DeviceIdleController to tell us about what apps are whitelisted.
+ */
+ public abstract void setDeviceIdleWhitelist(int[] appids);
+
+ /**
+ * Update information about which app IDs are on the temp whitelist.
+ */
+ public abstract void updateDeviceIdleTempWhitelist(int[] appids, int changingAppId,
+ boolean adding);
+
+ /**
* Updates and persists the {@link Configuration} for a given user.
*
* @param values the configuration to update
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0b3ae3a..cf20b68 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -245,7 +245,7 @@
boolean mSomeActivitiesChanged = false;
boolean mUpdatingSystemConfig = false;
- // These can be accessed by multiple threads; mPackages is the lock.
+ // These can be accessed by multiple threads; mResourcesManager is the lock.
// XXX For now we keep around information about all packages we have
// seen, not removing entries from this map.
// NOTE: The activity and window managers need to call in to
@@ -254,12 +254,13 @@
// holds their own lock. Thus you MUST NEVER call back into the activity manager
// or window manager or anything that depends on them while holding this lock.
// These LoadedApk are only valid for the userId that we're running as.
- final ArrayMap<String, WeakReference<LoadedApk>> mPackages
- = new ArrayMap<String, WeakReference<LoadedApk>>();
- final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages
- = new ArrayMap<String, WeakReference<LoadedApk>>();
- final ArrayList<ActivityClientRecord> mRelaunchingActivities
- = new ArrayList<ActivityClientRecord>();
+ @GuardedBy("mResourcesManager")
+ final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
+ @GuardedBy("mResourcesManager")
+ final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages = new ArrayMap<>();
+ @GuardedBy("mResourcesManager")
+ final ArrayList<ActivityClientRecord> mRelaunchingActivities = new ArrayList<>();
+ @GuardedBy("mResourcesManager")
Configuration mPendingConfiguration = null;
// Because we merge activity relaunch operations we can't depend on the ordering provided by
// the handler messages. We need to introduce secondary ordering mechanism, which will allow
@@ -540,6 +541,7 @@
ParcelFileDescriptor profileFd;
int samplingInterval;
boolean autoStopProfiler;
+ boolean streamingOutput;
boolean profiling;
boolean handlingProfiling;
public void setProfiler(ProfilerInfo profilerInfo) {
@@ -565,6 +567,7 @@
profileFd = fd;
samplingInterval = profilerInfo.samplingInterval;
autoStopProfiler = profilerInfo.autoStopProfiler;
+ streamingOutput = profilerInfo.streamingOutput;
}
public void startProfiling() {
if (profileFd == null || profiling) {
@@ -573,7 +576,8 @@
try {
int bufferSize = SystemProperties.getInt("debug.traceview-buffer-size-mb", 8);
VMDebug.startMethodTracing(profileFile, profileFd.getFileDescriptor(),
- bufferSize * 1024 * 1024, 0, samplingInterval != 0, samplingInterval);
+ bufferSize * 1024 * 1024, 0, samplingInterval != 0, samplingInterval,
+ streamingOutput);
profiling = true;
} catch (RuntimeException e) {
Slog.w(TAG, "Profiling failed on path " + profileFile);
@@ -904,6 +908,10 @@
sendMessage(H.CONFIGURATION_CHANGED, config);
}
+ public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
+ sendMessage(H.APPLICATION_INFO_CHANGED, ai);
+ }
+
public void updateTimeZone() {
TimeZone.setDefault(null);
}
@@ -1448,6 +1456,7 @@
public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
public static final int ATTACH_AGENT = 155;
+ public static final int APPLICATION_INFO_CHANGED = 156;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
@@ -1505,6 +1514,7 @@
case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
case ATTACH_AGENT: return "ATTACH_AGENT";
+ case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED";
}
}
return Integer.toString(code);
@@ -1636,8 +1646,11 @@
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
mUpdatingSystemConfig = true;
- handleConfigurationChanged((Configuration)msg.obj, null);
- mUpdatingSystemConfig = false;
+ try {
+ handleConfigurationChanged((Configuration) msg.obj, null);
+ } finally {
+ mUpdatingSystemConfig = false;
+ }
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CLEAN_UP_CONTEXT:
@@ -1763,6 +1776,14 @@
case ATTACH_AGENT:
handleAttachAgent((String) msg.obj);
break;
+ case APPLICATION_INFO_CHANGED:
+ mUpdatingSystemConfig = true;
+ try {
+ handleApplicationInfoChanged((ApplicationInfo) msg.obj);
+ } finally {
+ mUpdatingSystemConfig = false;
+ }
+ break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
@@ -2598,9 +2619,10 @@
r.activityInfo.targetActivity);
}
+ ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
- java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
+ java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
@@ -2629,7 +2651,6 @@
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
- Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (r.overrideConfig != null) {
@@ -2643,6 +2664,7 @@
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
+ appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
@@ -2718,8 +2740,8 @@
return activity;
}
- private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
- int displayId = Display.DEFAULT_DISPLAY;
+ private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
+ final int displayId;
try {
displayId = ActivityManager.getService().getActivityDisplayId(r.token);
} catch (RemoteException e) {
@@ -2727,9 +2749,7 @@
}
ContextImpl appContext = ContextImpl.createActivityContext(
- this, r.packageInfo, r.token, displayId, r.overrideConfig);
- appContext.setOuterContext(activity);
- Context baseContext = appContext;
+ this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
@@ -2742,12 +2762,12 @@
if (id != Display.DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getDisplayAdjustments(id));
- baseContext = appContext.createDisplayContext(display);
+ appContext = (ContextImpl) appContext.createDisplayContext(display);
break;
}
}
}
- return baseContext;
+ return appContext;
}
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
@@ -2947,7 +2967,7 @@
if (!forAutoFill) {
r.activity.onProvideAssistContent(content);
} else if (addAutoFillCallback) {
- IAutoFillAppCallback cb = r.activity.getAutoFillCallback().get();
+ IAutoFillAppCallback cb = r.activity.getAutoFillCallback();
if (cb != null) {
data.putBinder(AutoFillService.KEY_CALLBACK, cb.asBinder());
} else {
@@ -3101,9 +3121,16 @@
IActivityManager mgr = ActivityManager.getService();
+ Application app;
BroadcastReceiver receiver;
+ ContextImpl context;
try {
- java.lang.ClassLoader cl = packageInfo.getClassLoader();
+ app = packageInfo.makeApplication(false, mInstrumentation);
+ context = (ContextImpl) app.getBaseContext();
+ if (data.info.splitName != null) {
+ context = (ContextImpl) context.createContextForSplit(data.info.splitName);
+ }
+ java.lang.ClassLoader cl = context.getClassLoader();
data.intent.setExtrasClassLoader(cl);
data.intent.prepareToEnterProcess();
data.setExtrasClassLoader(cl);
@@ -3118,8 +3145,6 @@
}
try {
- Application app = packageInfo.makeApplication(false, mInstrumentation);
-
if (localLOGV) Slog.v(
TAG, "Performing receive of " + data.intent
+ ": app=" + app
@@ -3128,7 +3153,6 @@
+ ", comp=" + data.intent.getComponent().toShortString()
+ ", dir=" + packageInfo.getAppDir());
- ContextImpl context = (ContextImpl)app.getBaseContext();
sCurrentBroadcastIntent.set(data.intent);
receiver.setPendingResult(data);
receiver.onReceive(context.getReceiverRestrictedContext(),
@@ -3500,15 +3524,17 @@
}
r.activity.performResume();
- // If there is a pending local relaunch that was requested when the activity was
- // paused, it will put the activity into paused state when it finally happens.
- // Since the activity resumed before being relaunched, we don't want that to happen,
- // so we need to clear the request to relaunch paused.
- for (int i = mRelaunchingActivities.size() - 1; i >= 0; i--) {
- final ActivityClientRecord relaunching = mRelaunchingActivities.get(i);
- if (relaunching.token == r.token
- && relaunching.onlyLocalRequest && relaunching.startsNotResumed) {
- relaunching.startsNotResumed = false;
+ synchronized (mResourcesManager) {
+ // If there is a pending local relaunch that was requested when the activity was
+ // paused, it will put the activity into paused state when it finally happens.
+ // Since the activity resumed before being relaunched, we don't want that to
+ // happen, so we need to clear the request to relaunch paused.
+ for (int i = mRelaunchingActivities.size() - 1; i >= 0; i--) {
+ final ActivityClientRecord relaunching = mRelaunchingActivities.get(i);
+ if (relaunching.token == r.token
+ && relaunching.onlyLocalRequest && relaunching.startsNotResumed) {
+ relaunching.startsNotResumed = false;
+ }
}
}
@@ -4898,6 +4924,53 @@
}
}
+ void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) {
+ // Updates triggered by package installation go through a package update
+ // receiver. Here we try to capture ApplicationInfo changes that are
+ // caused by other sources, such as overlays. That means we want to be as conservative
+ // about code changes as possible. Take the diff of the old ApplicationInfo and the new
+ // to see if anything needs to change.
+ synchronized (mResourcesManager) {
+ // Update all affected loaded packages with new package information
+ WeakReference<LoadedApk> ref = mPackages.get(ai.packageName);
+ LoadedApk apk = ref != null ? ref.get() : null;
+ if (apk != null) {
+ final ArrayList<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths, null /*outLibPaths*/);
+ apk.updateApplicationInfo(ai, oldPaths);
+ }
+
+ ref = mResourcePackages.get(ai.packageName);
+ apk = ref != null ? ref.get() : null;
+ if (apk != null) {
+ final ArrayList<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths, null /*outLibPaths*/);
+ apk.updateApplicationInfo(ai, oldPaths);
+ }
+
+ // Update all affected Resources objects to use new ResourcesImpl
+ mResourcesManager.applyNewResourceDirsLocked(ai.sourceDir, ai.resourceDirs);
+ }
+
+ ApplicationPackageManager.configurationChanged();
+
+ // Trigger a regular Configuration change event, only with a different assetsSeq number
+ // so that we actually call through to all components.
+ Configuration newConfig = new Configuration();
+ newConfig.unset();
+ newConfig.assetsSeq = mConfiguration.assetsSeq + 1;
+ handleConfigurationChanged(newConfig, null);
+
+ // Schedule all activities to reload
+ for (final Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
+ final Activity activity = entry.getValue().activity;
+ if (!activity.mFinished) {
+ requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, null, false,
+ false);
+ }
+ }
+ }
+
static void freeTextLayoutCachesIfNeeded(int configDiff) {
if (configDiff != 0) {
// Ask text layout engine to free its caches if there is a locale change
@@ -4959,7 +5032,9 @@
* @hide
*/
public void stopProfiling() {
- mProfiler.stopProfiling();
+ if (mProfiler != null) {
+ mProfiler.stopProfiling();
+ }
}
static final void handleDumpHeap(boolean managed, DumpHeapData dhd) {
@@ -5205,6 +5280,7 @@
mProfiler.profileFd = data.initProfilerInfo.profileFd;
mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
+ mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
}
// send up app name; do this *before* waiting for debugger
@@ -5964,6 +6040,15 @@
info.name);
return null;
}
+
+ if (info.splitName != null) {
+ try {
+ c = c.createContextForSplit(info.splitName);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
try {
final java.lang.ClassLoader cl = c.getClassLoader();
localProvider = (ContentProvider)cl.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 67fbc5a..603126b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -243,8 +243,12 @@
public static final int OP_AUDIO_ACCESSIBILITY_VOLUME = 64;
/** @hide Read the phone number. */
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 = 66;
+ public static final int _NUM_OP = 68;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -461,6 +465,8 @@
OP_RUN_IN_BACKGROUND,
OP_AUDIO_ACCESSIBILITY_VOLUME,
OP_READ_PHONE_NUMBER,
+ OP_REQUEST_INSTALL_PACKAGES,
+ OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
};
/**
@@ -534,6 +540,8 @@
null,
null, // OP_AUDIO_ACCESSIBILITY_VOLUME
OPSTR_READ_PHONE_NUMBER,
+ null, // OP_REQUEST_INSTALL_PACKAGES
+ null,
};
/**
@@ -607,6 +615,8 @@
"RUN_IN_BACKGROUND",
"AUDIO_ACCESSIBILITY_VOLUME",
"READ_PHONE_NUMBER",
+ "REQUEST_INSTALL_PACKAGES",
+ "OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE",
};
/**
@@ -680,6 +690,8 @@
null, // no permission for running in background
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
};
/**
@@ -754,6 +766,8 @@
null, // RUN_IN_BACKGROUND
UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
null, // READ_PHONE_NUMBER
+ null, // REQUEST_INSTALL_PACKAGES
+ null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
};
/**
@@ -827,6 +841,8 @@
false, // RUN_IN_BACKGROUND
false, // AUDIO_ACCESSIBILITY_VOLUME
false, // READ_PHONE_NUMBER
+ false, // REQUEST_INSTALL_PACKAGES
+ false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
};
/**
@@ -899,6 +915,8 @@
AppOpsManager.MODE_ALLOWED, // OP_RUN_IN_BACKGROUND
AppOpsManager.MODE_ALLOWED, // OP_AUDIO_ACCESSIBILITY_VOLUME
AppOpsManager.MODE_ALLOWED,
+ AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES
+ AppOpsManager.MODE_ALLOWED, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
};
/**
@@ -975,6 +993,8 @@
false,
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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f3185a8..9f2fffd 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -17,13 +17,14 @@
package android.app;
import android.annotation.DrawableRes;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.XmlRes;
-import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -52,13 +53,13 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -136,6 +137,21 @@
}
@Override
+ public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags)
+ throws NameNotFoundException {
+ try {
+ PackageInfo pi = mPM.getPackageInfoVersioned(versionedPackage, flags,
+ mContext.getUserId());
+ if (pi != null) {
+ return pi;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ throw new NameNotFoundException(versionedPackage.toString());
+ }
+
+ @Override
public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
throws NameNotFoundException {
try {
@@ -146,7 +162,6 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
-
throw new NameNotFoundException(packageName);
}
@@ -444,6 +459,28 @@
/** @hide */
@Override
+ public @NonNull List<SharedLibraryInfo> getSharedLibraries(int flags) {
+ return getSharedLibrariesAsUser(flags, mContext.getUserId());
+ }
+
+ /** @hide */
+ @Override
+ @SuppressWarnings("unchecked")
+ public @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) {
+ try {
+ ParceledListSlice<SharedLibraryInfo> sharedLibs = mPM.getSharedLibraries(
+ flags, userId);
+ if (sharedLibs == null) {
+ return Collections.emptyList();
+ }
+ return sharedLibs.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ @Override
public @NonNull String getServicesSystemSharedLibraryPackageName() {
try {
return mPM.getServicesSystemSharedLibraryPackageName();
@@ -1386,7 +1423,7 @@
}
}
- ApplicationPackageManager(ContextImpl context,
+ protected ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;
mPM = pm;
@@ -1820,6 +1857,12 @@
@Override
public @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ return getPackageCurrentVolume(app, storage);
+ }
+
+ @VisibleForTesting
+ protected @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app,
+ StorageManager storage) {
if (app.isInternal()) {
return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
} else if (app.isExternalAsec()) {
@@ -1831,25 +1874,43 @@
@Override
public @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) {
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- final VolumeInfo currentVol = getPackageCurrentVolume(app);
- final List<VolumeInfo> vols = storage.getVolumes();
+ final StorageManager storageManager = mContext.getSystemService(StorageManager.class);
+ return getPackageCandidateVolumes(app, storageManager, mPM);
+ }
+
+ @VisibleForTesting
+ protected @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app,
+ StorageManager storageManager, IPackageManager pm) {
+ final VolumeInfo currentVol = getPackageCurrentVolume(app, storageManager);
+ final List<VolumeInfo> vols = storageManager.getVolumes();
final List<VolumeInfo> candidates = new ArrayList<>();
for (VolumeInfo vol : vols) {
- if (Objects.equals(vol, currentVol) || isPackageCandidateVolume(mContext, app, vol)) {
+ if (Objects.equals(vol, currentVol)
+ || isPackageCandidateVolume(mContext, app, vol, pm)) {
candidates.add(vol);
}
}
return candidates;
}
- private boolean isPackageCandidateVolume(
- ContextImpl context, ApplicationInfo app, VolumeInfo vol) {
- final boolean forceAllowOnExternal = Settings.Global.getInt(
+ @VisibleForTesting
+ protected boolean isForceAllowOnExternal(Context context) {
+ return Settings.Global.getInt(
context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
- // Private internal is always an option
+ }
+
+ @VisibleForTesting
+ protected boolean isAllow3rdPartyOnInternal(Context context) {
+ return context.getResources().getBoolean(
+ com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
+ }
+
+ private boolean isPackageCandidateVolume(
+ ContextImpl context, ApplicationInfo app, VolumeInfo vol, IPackageManager pm) {
+ final boolean forceAllowOnExternal = isForceAllowOnExternal(context);
+
if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
- return true;
+ return app.isSystemApp() || isAllow3rdPartyOnInternal(context);
}
// System apps and apps demanding internal storage can't be moved
@@ -1875,7 +1936,7 @@
// Some apps can't be moved. (e.g. device admins)
try {
- if (mPM.isPackageDeviceAdminOnAnyUser(app.packageName)) {
+ if (pm.isPackageDeviceAdminOnAnyUser(app.packageName)) {
return false;
}
} catch (RemoteException e) {
@@ -1952,10 +2013,11 @@
}
@Override
- public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer, int flags,
- int userId) {
+ public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer,
+ int flags, int userId) {
try {
- mPM.deletePackageAsUser(packageName, observer, userId, flags);
+ mPM.deletePackageAsUser(packageName, PackageManager.VERSION_CODE_HIGHEST,
+ observer, userId, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2278,7 +2340,7 @@
synchronized (mLock) {
if (mInstaller == null) {
try {
- mInstaller = new PackageInstaller(mContext, this, mPM.getPackageInstaller(),
+ mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
mContext.getPackageName(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -2518,4 +2580,13 @@
return false;
}
}
+
+ @Override
+ public boolean canRequestPackageInstalls() {
+ try {
+ return mPM.canRequestPackageInstalls(mContext.getPackageName(), mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d08bee5..9db2b92 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -33,6 +32,7 @@
import android.content.ReceiverCallNotAllowedException;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -59,21 +59,27 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.IStorageManager;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.DisplayAdjustments;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import dalvik.system.PathClassLoader;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -81,6 +87,8 @@
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
class ReceiverRestrictedContext extends ContextWrapper {
@@ -148,6 +156,7 @@
final ActivityThread mMainThread;
final LoadedApk mPackageInfo;
+ private ClassLoader mClassLoader;
private final IBinder mActivityToken;
@@ -273,8 +282,7 @@
@Override
public ClassLoader getClassLoader() {
- return mPackageInfo != null ?
- mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
+ return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
}
@Override
@@ -1342,8 +1350,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();
@@ -1385,7 +1393,14 @@
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
- return startServiceCommon(service, mUser);
+ return startServiceCommon(service, -1, null, mUser);
+ }
+
+ @Override
+ public ComponentName startServiceInForeground(Intent service,
+ int id, Notification notification) {
+ warnIfCallingFromSystemProcess();
+ return startServiceCommon(service, id, notification, mUser);
}
@Override
@@ -1396,16 +1411,24 @@
@Override
public ComponentName startServiceAsUser(Intent service, UserHandle user) {
- return startServiceCommon(service, user);
+ return startServiceCommon(service, -1, null, user);
}
- private ComponentName startServiceCommon(Intent service, UserHandle user) {
+ @Override
+ public ComponentName startServiceInForegroundAsUser(Intent service,
+ int id, Notification notification, UserHandle user) {
+ return startServiceCommon(service, id, notification, user);
+ }
+
+ private ComponentName startServiceCommon(Intent service, int id, Notification notification,
+ UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
- getContentResolver()), getOpPackageName(), user.getIdentifier());
+ getContentResolver()), id, notification, getOpPackageName(),
+ user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException(
@@ -1415,6 +1438,9 @@
throw new SecurityException(
"Unable to start service " + service
+ ": " + cn.getClassName());
+ } else if (cn.getPackageName().equals("?")) {
+ throw new IllegalStateException(
+ "Not allowed to start service " + service + ": " + cn.getClassName());
}
}
return cn;
@@ -1573,9 +1599,20 @@
throw new IllegalArgumentException("permission is null");
}
+ final IActivityManager am = ActivityManager.getService();
+ if (am == null) {
+ // Well this is super awkward; we somehow don't have an active
+ // 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 {
- return ActivityManager.getService().checkPermission(
- permission, pid, uid);
+ return am.checkPermission(permission, pid, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1859,7 +1896,7 @@
pi.getResDir(),
pi.getSplitResDirs(),
pi.getOverlayDirs(),
- pi.getApplicationInfo().sharedLibraryFiles,
+ pi.getSharedLibraries(),
displayId,
overrideConfig,
compatInfo,
@@ -1873,7 +1910,8 @@
flags | CONTEXT_REGISTER_PACKAGE);
if (pi != null) {
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
- new UserHandle(UserHandle.getUserId(application.uid)), flags);
+ new UserHandle(UserHandle.getUserId(application.uid)), flags,
+ null);
final int displayId = mDisplay != null
? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
@@ -1902,13 +1940,15 @@
if (packageName.equals("system") || packageName.equals("android")) {
// The system resources are loaded in every application, so we can safely copy
// the context without reloading Resources.
- return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, user, flags);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, user, flags,
+ null);
}
LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
if (pi != null) {
- ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, user, flags);
+ ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, user, flags,
+ null);
final int displayId = mDisplay != null
? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
@@ -1926,13 +1966,42 @@
}
@Override
+ public Context createContextForSplit(String splitName) throws NameNotFoundException {
+ if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
+ // All Splits are always loaded.
+ return this;
+ }
+
+ final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName);
+ final String[] paths = mPackageInfo.getSplitPaths(splitName);
+
+ final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo,
+ mActivityToken, mUser, mFlags, classLoader);
+
+ final int displayId = mDisplay != null
+ ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
+
+ context.mResources = ResourcesManager.getInstance().getResources(
+ mActivityToken,
+ mPackageInfo.getResDir(),
+ paths,
+ mPackageInfo.getOverlayDirs(),
+ mPackageInfo.getApplicationInfo().sharedLibraryFiles,
+ displayId,
+ null,
+ mPackageInfo.getCompatibilityInfo(),
+ classLoader);
+ return context;
+ }
+
+ @Override
public Context createConfigurationContext(Configuration overrideConfiguration) {
if (overrideConfiguration == null) {
throw new IllegalArgumentException("overrideConfiguration must not be null");
}
ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- mUser, mFlags);
+ mUser, mFlags, mClassLoader);
final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
context.mResources = createResources(mActivityToken, mPackageInfo, displayId,
@@ -1947,7 +2016,7 @@
}
ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- mUser, mFlags);
+ mUser, mFlags, mClassLoader);
final int displayId = display.getDisplayId();
context.mResources = createResources(mActivityToken, mPackageInfo, displayId, null,
@@ -1960,14 +2029,16 @@
public Context createDeviceProtectedStorageContext() {
final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
| Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
- return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags,
+ mClassLoader);
}
@Override
public Context createCredentialProtectedStorageContext() {
final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
| Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
- return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags,
+ mClassLoader);
}
@Override
@@ -2054,8 +2125,9 @@
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
- ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
- context.mResources = packageInfo.getResources(mainThread);
+ ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+ null);
+ context.mResources = packageInfo.getResources();
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetrics());
return context;
@@ -2063,18 +2135,35 @@
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
- ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
- context.mResources = packageInfo.getResources(mainThread);
+ ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+ null);
+ context.mResources = packageInfo.getResources();
return context;
}
static ContextImpl createActivityContext(ActivityThread mainThread,
- LoadedApk packageInfo, IBinder activityToken, int displayId,
+ LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
+ String[] splitDirs = packageInfo.getSplitResDirs();
+ ClassLoader classLoader = packageInfo.getClassLoader();
+
+ if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
+ try {
+ classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
+ splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
+ } catch (NameNotFoundException e) {
+ // Nothing above us can handle a NameNotFoundException, better crash.
+ throw new RuntimeException(e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityToken, null,
- 0);
+ 0, classLoader);
// Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
@@ -2089,20 +2178,21 @@
// will be rebased upon.
context.mResources = resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
- packageInfo.getSplitResDirs(),
+ splitDirs,
packageInfo.getOverlayDirs(),
- packageInfo.getApplicationInfo().sharedLibraryFiles,
+ packageInfo.getSharedLibraries(),
displayId,
overrideConfiguration,
compatInfo,
- packageInfo.getClassLoader());
+ classLoader);
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.mResources.getDisplayAdjustments());
return context;
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
- LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags) {
+ LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
+ ClassLoader classLoader) {
mOuterContext = this;
// If creator didn't specify which storage to use, use the default
@@ -2127,6 +2217,7 @@
mUser = user;
mPackageInfo = packageInfo;
+ mClassLoader = classLoader;
mResourcesManager = ResourcesManager.getInstance();
if (container != null) {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 62d6898..612998d 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -762,6 +762,24 @@
* are going to call back with {@link #onActivityResult(int, int, Intent)}.
*/
public void setTargetFragment(Fragment fragment, int requestCode) {
+ // Don't allow a caller to set a target fragment in another FragmentManager,
+ // but there's a snag: people do set target fragments before fragments get added.
+ // We'll have the FragmentManager check that for validity when we move
+ // the fragments to a valid state.
+ final FragmentManager mine = getFragmentManager();
+ final FragmentManager theirs = fragment.getFragmentManager();
+ if (mine != null && theirs != null && mine != theirs) {
+ throw new IllegalArgumentException("Fragment " + fragment
+ + " must share the same FragmentManager to be set as a target fragment");
+ }
+
+ // Don't let someone create a cycle.
+ for (Fragment check = fragment; check != null; check = check.getTargetFragment()) {
+ if (check == this) {
+ throw new IllegalArgumentException("Setting " + fragment + " as the target of "
+ + this + " would create a target cycle");
+ }
+ }
mTarget = fragment;
mTargetRequestCode = requestCode;
}
@@ -1114,7 +1132,7 @@
*
* @param intent The intent to start.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*/
public void startActivity(Intent intent, Bundle options) {
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 44f1322..32cf1c3 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1110,10 +1110,25 @@
}
}
}
+
f.mHost = mHost;
f.mParentFragment = mParent;
f.mFragmentManager = mParent != null
? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
+
+ // If we have a target fragment, push it along to at least CREATED
+ // so that this one can rely on it as an initialized dependency.
+ if (f.mTarget != null) {
+ if (!mActive.contains(f.mTarget)) {
+ throw new IllegalStateException("Fragment " + f
+ + " declared target fragment " + f.mTarget
+ + " that does not belong to this FragmentManager!");
+ }
+ if (f.mTarget.mState < Fragment.CREATED) {
+ moveToState(f.mTarget, Fragment.CREATED, 0, 0, true);
+ }
+ }
+
dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
f.mCalled = false;
f.onAttach(mHost.getContext());
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 5824c32..0a2f804 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -34,6 +34,7 @@
import android.app.IUserSwitchObserver;
import android.app.Notification;
import android.app.PendingIntent;
+import android.app.PictureInPictureArgs;
import android.app.ProfilerInfo;
import android.app.WaitResult;
import android.app.assist.AssistContent;
@@ -128,7 +129,8 @@
void finishSubActivity(in IBinder token, in String resultWho, int requestCode);
PendingIntent getRunningServiceControlPanel(in ComponentName service);
ComponentName startService(in IApplicationThread caller, in Intent service,
- in String resolvedType, in String callingPackage, int userId);
+ in String resolvedType, int id, in Notification notification,
+ in String callingPackage, int userId);
int stopService(in IApplicationThread caller, in Intent service,
in String resolvedType, int userId);
int bindService(in IApplicationThread caller, in IBinder token, in Intent service,
@@ -262,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,
@@ -473,18 +475,14 @@
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);
boolean isInPictureInPictureMode(in IBinder token);
void killPackageDependents(in String packageName, int userId);
- void enterPictureInPictureMode(in IBinder token);
- void enterPictureInPictureModeWithAspectRatio(in IBinder token, float aspectRatio);
- void enterPictureInPictureModeOnMoveToBackground(in IBinder token,
- boolean enterPictureInPictureOnMoveToBg);
- void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio);
- void setPictureInPictureActions(in IBinder token, in ParceledListSlice actions);
+ boolean enterPictureInPictureMode(in IBinder token, in PictureInPictureArgs args);
+ void setPictureInPictureArgs(in IBinder token, in PictureInPictureArgs args);
void activityRelaunched(in IBinder token);
IBinder getUriPermissionOwnerForActivity(in IBinder activityToken);
/**
@@ -541,7 +539,7 @@
*/
void swapDockedAndFullscreenStack();
void notifyLockedProfile(int userId);
- void startConfirmDeviceCredentialIntent(in Intent intent);
+ void startConfirmDeviceCredentialIntent(in Intent intent, in Bundle options);
void sendIdleJobTrigger();
int sendIntentSender(in IIntentSender target, int code, in Intent intent,
in String resolvedType, in IIntentReceiver finishedReceiver,
@@ -601,6 +599,8 @@
*/
ActivityManager.TaskSnapshot getTaskSnapshot(int taskId);
+ void scheduleApplicationInfoChanged(in List<String> packageNames, int userId);
+
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
// If a transaction which will also be used on the native side is being inserted, add it
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 7f168c9..41d1255 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -152,4 +152,5 @@
IVoiceInteractor voiceInteractor);
void handleTrustStorageUpdate();
void attachAgent(String path);
+ void scheduleApplicationInfoChanged(in ApplicationInfo ai);
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 07c21a5..58cd310 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -40,24 +40,29 @@
{
void cancelAllNotifications(String pkg, int userId);
+ void clearData(String pkg, int uid);
void enqueueToast(String pkg, ITransientNotification callback, int duration);
void cancelToast(String pkg, ITransientNotification callback);
void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
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);
int getPackageImportance(String pkg);
+ void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
void createNotificationChannels(String pkg, in ParceledListSlice channelsList);
+ ParceledListSlice getNotificationChannelGroupsForPackage(String pkg, int uid, boolean includeDeleted);
void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
NotificationChannel getNotificationChannel(String pkg, String channelId);
- NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId);
+ NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, String channelId);
ParceledListSlice getNotificationChannels(String pkg);
- ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid);
+ ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
@@ -72,8 +77,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);
@@ -83,6 +86,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);
@@ -97,6 +101,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/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 6deedb6..5e420c0 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -31,8 +31,10 @@
* Called whenever IActivityManager.startActivity is called on an activity that is already
* running in the pinned stack and the activity is not actually started, but the task is either
* brought to the front or a new Intent is delivered to it.
+ *
+ * @param sourceComponent the component name of the activity that initiated the restart attempt
*/
- void onPinnedActivityRestartAttempt();
+ void onPinnedActivityRestartAttempt(in ComponentName sourceComponent);
/**
* Called whenever the pinned stack is done animating a resize.
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 848464c..7f0b6fb 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -58,11 +58,16 @@
void setTheme(String theme);
/**
- * Gets whith theme overlays to use within /vendor/overlay.
+ * Gets which theme overlays to use within /vendor/overlay.
*/
String getTheme();
/**
+ * Gets the themes available in /vendor/overlay.
+ */
+ String[] getAvailableThemes();
+
+ /**
* Tells if UI mode is locked or not.
*/
boolean isUiModeLocked();
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index cc7981c..b1bdea1 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -443,6 +443,7 @@
private final String mClass;
private final ActivityResult mResult;
private final boolean mBlock;
+ private final boolean mIgnoreMatchingSpecificIntents;
// This is protected by 'Instrumentation.this.mSync'.
@@ -454,7 +455,7 @@
/**
* Create a new ActivityMonitor that looks for a particular kind of
* intent to be started.
- *
+ *
* @param which The set of intents this monitor is responsible for.
* @param result A canned result to return if the monitor is hit; can
* be null.
@@ -470,6 +471,7 @@
mClass = null;
mResult = result;
mBlock = block;
+ mIgnoreMatchingSpecificIntents = false;
}
/**
@@ -491,6 +493,34 @@
mClass = cls;
mResult = result;
mBlock = block;
+ mIgnoreMatchingSpecificIntents = false;
+ }
+
+ /**
+ * Create a new ActivityMonitor that can be used for intercepting any activity to be
+ * started.
+ *
+ * <p> When an activity is started, {@link #onMatchIntent(Intent)} will be called on
+ * instances created using this constructor to see if it is a hit.
+ *
+ * @see #onMatchIntent(Intent)
+ */
+ public ActivityMonitor() {
+ mWhich = null;
+ mClass = null;
+ mResult = null;
+ mBlock = false;
+ mIgnoreMatchingSpecificIntents = true;
+ }
+
+ /**
+ * @return true if this monitor is used for intercepting any started activity by calling
+ * into {@link #onMatchIntent(Intent)}, false if this monitor is only used
+ * for specific intents corresponding to the intent filter or activity class
+ * passed in the constructor.
+ */
+ final boolean ignoreMatchingSpecificIntents() {
+ return mIgnoreMatchingSpecificIntents;
}
/**
@@ -577,10 +607,31 @@
}
}
}
-
+
+ /**
+ * Used for intercepting any started activity.
+ *
+ * <p> A non-null return value here will be considered a hit for this monitor.
+ * By default this will return {@code null} and subclasses can override this to return
+ * a non-null value if the intent needs to be intercepted.
+ *
+ * <p> Whenever a new activity is started, this method will be called on instances created
+ * using {@link #Instrumentation.ActivityMonitor()} to check if there is a match. In case
+ * of a match, the activity start will be blocked and the returned result will be used.
+ *
+ * @param intent The intent used for starting the activity.
+ * @return The {@link ActivityResult} that needs to be used in case of a match.
+ */
+ public ActivityResult onMatchIntent(Intent intent) {
+ return null;
+ }
+
final boolean match(Context who,
Activity activity,
Intent intent) {
+ if (mIgnoreMatchingSpecificIntents) {
+ return false;
+ }
synchronized (this) {
if (mWhich != null
&& mWhich.match(who.getContentResolver(), intent,
@@ -1492,7 +1543,14 @@
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
- if (am.match(who, null, intent)) {
+ ActivityResult result = null;
+ if (am.ignoreMatchingSpecificIntents()) {
+ result = am.onMatchIntent(intent);
+ }
+ if (result != null) {
+ am.mHits++;
+ return result;
+ } else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
@@ -1548,7 +1606,14 @@
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
- if (am.match(who, null, intents[0])) {
+ ActivityResult result = null;
+ if (am.ignoreMatchingSpecificIntents()) {
+ result = am.onMatchIntent(intents[0]);
+ }
+ if (result != null) {
+ am.mHits++;
+ return;
+ } else if (am.match(who, null, intents[0])) {
am.mHits++;
if (am.isBlocking()) {
return;
@@ -1611,7 +1676,14 @@
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
- if (am.match(who, null, intent)) {
+ ActivityResult result = null;
+ if (am.ignoreMatchingSpecificIntents()) {
+ result = am.onMatchIntent(intent);
+ }
+ if (result != null) {
+ am.mHits++;
+ return result;
+ } else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
@@ -1671,7 +1743,14 @@
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
- if (am.match(who, null, intent)) {
+ ActivityResult result = null;
+ if (am.ignoreMatchingSpecificIntents()) {
+ result = am.onMatchIntent(intent);
+ }
+ if (result != null) {
+ am.mHits++;
+ return result;
+ } else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
@@ -1710,7 +1789,14 @@
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
- if (am.match(who, null, intent)) {
+ ActivityResult result = null;
+ if (am.ignoreMatchingSpecificIntents()) {
+ result = am.onMatchIntent(intent);
+ }
+ if (result != null) {
+ am.mHits++;
+ return result;
+ } else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
@@ -1748,7 +1834,14 @@
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
- if (am.match(who, null, intent)) {
+ ActivityResult result = null;
+ if (am.ignoreMatchingSpecificIntents()) {
+ result = am.onMatchIntent(intent);
+ }
+ if (result != null) {
+ am.mHits++;
+ return;
+ } else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return;
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/LoadedApk.java b/core/java/android/app/LoadedApk.java
index db1162a..17f5edd 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -16,6 +16,8 @@
package android.app;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -25,9 +27,11 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.split.SplitDependencyLoaderHelper;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Resources;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
@@ -39,23 +43,22 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.system.Os;
-import android.system.OsConstants;
-import android.system.ErrnoException;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.Display;
import android.view.DisplayAdjustments;
+import com.android.internal.util.ArrayUtils;
+
import dalvik.system.BaseDexClassLoader;
import dalvik.system.VMRuntime;
import java.io.File;
-import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
@@ -63,13 +66,12 @@
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
-import libcore.io.IoUtils;
-
final class IntentReceiverLeaked extends AndroidRuntimeException {
public IntentReceiverLeaked(String msg) {
super(msg);
@@ -95,8 +97,6 @@
private ApplicationInfo mApplicationInfo;
private String mAppDir;
private String mResDir;
- private String[] mSplitAppDirs;
- private String[] mSplitResDirs;
private String[] mOverlayDirs;
private String[] mSharedLibraries;
private String mDataDir;
@@ -114,14 +114,18 @@
private ClassLoader mClassLoader;
private Application mApplication;
+ private String[] mSplitNames;
+ private String[] mSplitAppDirs;
+ private String[] mSplitResDirs;
+
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
- = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
+ = new ArrayMap<>();
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers
- = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
+ = new ArrayMap<>();
private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
- = new ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
+ = new ArrayMap<>();
private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices
- = new ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
+ = new ArrayMap<>();
int mClientCount = 0;
@@ -262,7 +266,15 @@
return ai.sharedLibraryFiles;
}
- public void updateApplicationInfo(ApplicationInfo aInfo, List<String> oldPaths) {
+ /**
+ * Update the ApplicationInfo for an app. If oldPaths is null, all the paths are considered
+ * new.
+ * @param aInfo The new ApplicationInfo to use for this LoadedApk
+ * @param oldPaths The code paths for the old ApplicationInfo object. null means no paths can
+ * be reused.
+ */
+ public void updateApplicationInfo(@NonNull ApplicationInfo aInfo,
+ @Nullable List<String> oldPaths) {
setApplicationInfo(aInfo);
final List<String> newPaths = new ArrayList<>();
@@ -290,9 +302,18 @@
synchronized (this) {
createOrUpdateClassLoaderLocked(addedPaths);
if (mResources != null) {
- mResources = mActivityThread.getTopLevelResources(mResDir, mSplitResDirs,
- mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
- this);
+ final String[] splitPaths;
+ try {
+ splitPaths = getSplitPaths(null);
+ } catch (PackageManager.NameNotFoundException e) {
+ // This should NEVER fail.
+ throw new AssertionError("null split not found");
+ }
+
+ mResources = ResourcesManager.getInstance().getResources(null, mResDir,
+ splitPaths, mOverlayDirs, mSharedLibraries,
+ Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+ getClassLoader());
}
}
}
@@ -303,8 +324,6 @@
mApplicationInfo = aInfo;
mAppDir = aInfo.sourceDir;
mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
- mSplitAppDirs = aInfo.splitSourceDirs;
- mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
mOverlayDirs = aInfo.resourceDirs;
mSharedLibraries = aInfo.sharedLibraryFiles;
mDataDir = aInfo.dataDir;
@@ -312,19 +331,28 @@
mDataDirFile = FileUtils.newFileOrNull(aInfo.dataDir);
mDeviceProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.deviceProtectedDataDir);
mCredentialProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.credentialProtectedDataDir);
+
+ mSplitNames = aInfo.splitNames;
+ mSplitAppDirs = aInfo.splitSourceDirs;
+ mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
+
+ if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
+ mSplitLoader = new SplitDependencyLoader(aInfo.splitDependencies);
+ }
}
public static void makePaths(ActivityThread activityThread, ApplicationInfo aInfo,
List<String> outZipPaths, List<String> outLibPaths) {
final String appDir = aInfo.sourceDir;
- final String[] splitAppDirs = aInfo.splitSourceDirs;
final String libDir = aInfo.nativeLibraryDir;
final String[] sharedLibraries = aInfo.sharedLibraryFiles;
outZipPaths.clear();
outZipPaths.add(appDir);
- if (splitAppDirs != null) {
- Collections.addAll(outZipPaths, splitAppDirs);
+
+ // Do not load all available splits if the app requested isolated split loading.
+ if (aInfo.splitSourceDirs != null && !aInfo.requestsIsolatedSplitLoading()) {
+ Collections.addAll(outZipPaths, aInfo.splitSourceDirs);
}
if (outLibPaths != null) {
@@ -357,13 +385,18 @@
|| appDir.equals(instrumentedAppDir)) {
outZipPaths.clear();
outZipPaths.add(instrumentationAppDir);
- if (instrumentationSplitAppDirs != null) {
- Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
- }
- if (!instrumentationAppDir.equals(instrumentedAppDir)) {
- outZipPaths.add(instrumentedAppDir);
- if (instrumentedSplitAppDirs != null) {
- Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
+
+ // Only add splits if the app did not request isolated split loading.
+ if (!aInfo.requestsIsolatedSplitLoading()) {
+ if (instrumentationSplitAppDirs != null) {
+ Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
+ }
+
+ if (!instrumentationAppDir.equals(instrumentedAppDir)) {
+ outZipPaths.add(instrumentedAppDir);
+ if (instrumentedSplitAppDirs != null) {
+ Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
+ }
}
}
@@ -389,7 +422,7 @@
// will be added to zipPaths that shouldn't be part of the library path.
if (aInfo.primaryCpuAbi != null) {
// Add fake libs into the library search path if we target prior to N.
- if (aInfo.targetSdkVersion <= 23) {
+ if (aInfo.targetSdkVersion < Build.VERSION_CODES.N) {
outLibPaths.add("/system/fake-libs" +
(VMRuntime.is64BitAbi(aInfo.primaryCpuAbi) ? "64" : ""));
}
@@ -424,6 +457,116 @@
}
}
+ private class SplitDependencyLoader
+ extends SplitDependencyLoaderHelper<PackageManager.NameNotFoundException> {
+ private String[] mCachedBaseResourcePath;
+ private final String[][] mCachedResourcePaths;
+ private final ClassLoader[] mCachedSplitClassLoaders;
+
+ SplitDependencyLoader(SparseIntArray dependencies) {
+ super(dependencies);
+ mCachedResourcePaths = new String[mSplitNames.length][];
+ mCachedSplitClassLoaders = new ClassLoader[mSplitNames.length];
+ }
+
+ @Override
+ protected boolean isSplitCached(int splitIdx) {
+ if (splitIdx != -1) {
+ return mCachedSplitClassLoaders[splitIdx] != null;
+ }
+ return mClassLoader != null && mCachedBaseResourcePath != null;
+ }
+
+ private void addAllConfigSplits(String splitName, ArrayList<String> outAssetPaths) {
+ for (int i = 0; i < mSplitNames.length; i++) {
+ if (isConfigurationSplitOf(mSplitNames[i], splitName)) {
+ outAssetPaths.add(mSplitResDirs[i]);
+ }
+ }
+ }
+
+ @Override
+ protected void constructSplit(int splitIdx, int parentSplitIdx) throws
+ PackageManager.NameNotFoundException {
+ final ArrayList<String> splitPaths = new ArrayList<>();
+ if (splitIdx == -1) {
+ createOrUpdateClassLoaderLocked(null);
+ addAllConfigSplits(null, splitPaths);
+ mCachedBaseResourcePath = splitPaths.toArray(new String[splitPaths.size()]);
+ return;
+ }
+
+ final ClassLoader parent;
+ if (parentSplitIdx == -1) {
+ // The parent is the base APK, so use its ClassLoader as parent
+ // and its configuration splits as part of our own too.
+ parent = mClassLoader;
+ Collections.addAll(splitPaths, mCachedBaseResourcePath);
+ } else {
+ parent = mCachedSplitClassLoaders[parentSplitIdx];
+ Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
+ }
+
+ mCachedSplitClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
+ mSplitAppDirs[splitIdx], getTargetSdkVersion(), false, null, null, parent);
+
+ splitPaths.add(mSplitResDirs[splitIdx]);
+ addAllConfigSplits(mSplitNames[splitIdx], splitPaths);
+ mCachedResourcePaths[splitIdx] = splitPaths.toArray(new String[splitPaths.size()]);
+ }
+
+ private int ensureSplitLoaded(String splitName)
+ throws PackageManager.NameNotFoundException {
+ final int idx;
+ if (splitName == null) {
+ idx = -1;
+ } else {
+ idx = Arrays.binarySearch(mSplitNames, splitName);
+ if (idx < 0) {
+ throw new PackageManager.NameNotFoundException(
+ "Split name '" + splitName + "' is not installed");
+ }
+ }
+
+ loadDependenciesForSplit(idx);
+ return idx;
+ }
+
+ ClassLoader getClassLoaderForSplit(String splitName)
+ throws PackageManager.NameNotFoundException {
+ final int idx = ensureSplitLoaded(splitName);
+ if (idx < 0) {
+ return mClassLoader;
+ }
+ return mCachedSplitClassLoaders[idx];
+ }
+
+ String[] getSplitPathsForSplit(String splitName)
+ throws PackageManager.NameNotFoundException {
+ final int idx = ensureSplitLoaded(splitName);
+ if (idx < 0) {
+ return mCachedBaseResourcePath;
+ }
+ return mCachedResourcePaths[idx];
+ }
+ }
+
+ private SplitDependencyLoader mSplitLoader;
+
+ ClassLoader getSplitClassLoader(String splitName) throws PackageManager.NameNotFoundException {
+ if (mSplitLoader == null) {
+ return mClassLoader;
+ }
+ return mSplitLoader.getClassLoaderForSplit(splitName);
+ }
+
+ String[] getSplitPaths(String splitName) throws PackageManager.NameNotFoundException {
+ if (mSplitLoader == null) {
+ return mSplitResDirs;
+ }
+ return mSplitLoader.getSplitPathsForSplit(splitName);
+ }
+
private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
if (mPackageName.equals("android")) {
// Note: This branch is taken for system server and we don't need to setup
@@ -780,6 +923,10 @@
return mOverlayDirs;
}
+ public String[] getSharedLibraries() {
+ return mSharedLibraries;
+ }
+
public String getDataDir() {
return mDataDir;
}
@@ -796,14 +943,24 @@
return mCredentialProtectedDataDirFile;
}
- public AssetManager getAssets(ActivityThread mainThread) {
- return getResources(mainThread).getAssets();
+ public AssetManager getAssets() {
+ return getResources().getAssets();
}
- public Resources getResources(ActivityThread mainThread) {
+ public Resources getResources() {
if (mResources == null) {
- mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
- mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
+ final String[] splitPaths;
+ try {
+ splitPaths = getSplitPaths(null);
+ } catch (PackageManager.NameNotFoundException e) {
+ // This should never fail.
+ throw new AssertionError("null split not found");
+ }
+
+ mResources = ResourcesManager.getInstance().getResources(null, mResDir,
+ splitPaths, mOverlayDirs, mSharedLibraries,
+ Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+ getClassLoader());
}
return mResources;
}
@@ -860,8 +1017,7 @@
}
// Rewrite the R 'constants' for all library apks.
- SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
- .getAssignedPackageIdentifiers();
+ SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
final int N = packageIdentifiers.size();
for (int i = 0; i < N; i++) {
final int id = packageIdentifiers.keyAt(i);
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index 09e95df..d2d7b6d 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -174,7 +174,7 @@
@Override
public void setContentDescription(CharSequence contentDescription) {
super.setContentDescription(contentDescription);
- setTooltip(contentDescription);
+ setTooltipText(contentDescription);
}
@Override
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5b74e23..7bdf4cc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -46,6 +46,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.BidiFormatter;
import android.text.SpannableStringBuilder;
@@ -70,7 +71,6 @@
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.NotificationColorUtil;
-import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -972,6 +972,21 @@
public static final String EXTRA_MESSAGES = "android.messages";
/**
+ * {@link #extras} key: an array of
+ * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
+ * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
+ * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
+ * array of bundles.
+ */
+ public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
+
+ /**
+ * {@link #extras} key: whether the notification should be colorized as
+ * supplied to {@link Builder#setColorized(boolean)}}.
+ */
+ public static final String EXTRA_COLORIZED = "android.colorized";
+
+ /**
* {@link #extras} key: the user that built the notification.
*
* @hide
@@ -1022,6 +1037,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 +1067,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 +1109,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 +1182,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 +1204,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 +1276,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 +1782,7 @@
if (parcel.readInt() != 0) {
mChannelId = parcel.readString();
}
+ mTimeout = parcel.readLong();
}
@Override
@@ -1872,6 +1889,7 @@
that.color = this.color;
that.mChannelId = this.mChannelId;
+ that.mTimeout = this.mTimeout;
if (!heavy) {
that.lightenPayload(); // will clean out extras
@@ -2128,6 +2146,7 @@
} else {
parcel.writeInt(0);
}
+ parcel.writeLong(mTimeout);
}
/**
@@ -2325,6 +2344,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.
@@ -2407,6 +2433,9 @@
private static final int MAX_ACTION_BUTTONS = 3;
+ private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
+ SystemProperties.getBoolean("notifications.only_title", true);
+
private Context mContext;
private Notification mN;
private Bundle mUserExtras = new Bundle();
@@ -2414,6 +2443,8 @@
private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
private ArrayList<String> mPersonList = new ArrayList<String>();
private NotificationColorUtil mColorUtil;
+ private boolean mIsLegacy;
+ private boolean mIsLegacyInitialized;
private boolean mColorUtilInited = false;
/**
@@ -2432,6 +2463,10 @@
* so make sure to call {@link StandardTemplateParams#reset()} before using it.
*/
StandardTemplateParams mParams = new StandardTemplateParams();
+ private int mTextColorsAreForBackground = COLOR_INVALID;
+ private int mPrimaryTextColor = COLOR_INVALID;
+ private int mSecondaryTextColor = COLOR_INVALID;
+ private int mActionBarColor = COLOR_INVALID;
/**
* Constructs a new Builder with the defaults:
@@ -2451,9 +2486,23 @@
* A {@link Context} that will be used by the Builder to construct the
* RemoteViews. The Context will not be held past the lifetime of this Builder
* object.
+ * @param channelId
+ * The constructed Notification will be posted on this
+ * {@link NotificationChannel}. To use a NotificationChannel, it must first be
+ * created using {@link NotificationManager#createNotificationChannel}.
*/
+ public Builder(Context context, String channelId) {
+ this(context, (Notification) null);
+ mN.mChannelId = channelId;
+ }
+
+ /**
+ * @deprecated use {@link Notification.Builder#Notification.Builder(Context, String)}
+ * instead. All posted Notifications must specify a NotificationChannel Id.
+ */
+ @Deprecated
public Builder(Context context) {
- this(context, null);
+ this(context, (Notification) null);
}
/**
@@ -2516,7 +2565,7 @@
private NotificationColorUtil getColorUtil() {
if (!mColorUtilInited) {
mColorUtilInited = true;
- if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
+ if (isLegacy() || isColorized()) {
mColorUtil = NotificationColorUtil.getInstance(mContext);
}
}
@@ -2532,6 +2581,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
@@ -2992,6 +3050,22 @@
}
/**
+ * Set whether this notification should be colorized. When set, the color set with
+ * {@link #setColor(int)} will be used as the background color of this notification.
+ * <p>
+ * The coloring will only be applied if the notification is ongoing.
+ * This should only be used for high priority ongoing tasks like navigation, an ongoing
+ * call, or other similarly high-priority events for the user.
+ *
+ * @see Builder#setOngoing(boolean)
+ * @see Builder#setColor(int)
+ */
+ public Builder setColorized(boolean colorize) {
+ mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
+ return this;
+ }
+
+ /**
* Set this flag if you would only like the sound, vibrate
* and ticker to be played if the notification is not already showing.
*
@@ -3356,6 +3430,10 @@
if (profileBadge != null) {
contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
+ if (isColorized()) {
+ contentView.setDrawableParameters(R.id.profile_badge, false, -1,
+ getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP, -1);
+ }
}
}
@@ -3413,13 +3491,14 @@
resetStandardTemplate(contentView);
final Bundle ex = mN.extras;
-
+ updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient);
bindLargeIcon(contentView);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
contentView.setTextViewText(R.id.title, p.title);
+ setTextViewColorPrimary(contentView, R.id.title);
contentView.setViewLayoutWidth(R.id.title, showProgress
? ViewGroup.LayoutParams.WRAP_CONTENT
: ViewGroup.LayoutParams.MATCH_PARENT);
@@ -3428,6 +3507,7 @@
int textId = showProgress ? com.android.internal.R.id.text_line_1
: com.android.internal.R.id.text;
contentView.setTextViewText(textId, p.text);
+ setTextViewColorSecondary(contentView, textId);
contentView.setViewVisibility(textId, View.VISIBLE);
}
@@ -3436,6 +3516,52 @@
return contentView;
}
+ private void setTextViewColorPrimary(RemoteViews contentView, int id) {
+ ensureColors();
+ contentView.setTextColor(id, mPrimaryTextColor);
+ }
+
+ private int getPrimaryTextColor() {
+ ensureColors();
+ return mPrimaryTextColor;
+ }
+
+ private int getActionBarColor() {
+ ensureColors();
+ return mActionBarColor;
+ }
+
+ private void setTextViewColorSecondary(RemoteViews contentView, int id) {
+ ensureColors();
+ contentView.setTextColor(id, mSecondaryTextColor);
+ }
+
+ private void ensureColors() {
+ int backgroundColor = getBackgroundColor();
+ if (mPrimaryTextColor == COLOR_INVALID
+ || mSecondaryTextColor == COLOR_INVALID
+ || mActionBarColor == COLOR_INVALID
+ || mTextColorsAreForBackground != backgroundColor) {
+ mTextColorsAreForBackground = backgroundColor;
+ mPrimaryTextColor = NotificationColorUtil.resolvePrimaryColor(
+ mContext, backgroundColor);
+ mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(
+ mContext, backgroundColor);
+ mActionBarColor = NotificationColorUtil.resolveActionBarColor(backgroundColor);
+ }
+ }
+
+ private void updateBackgroundColor(RemoteViews contentView) {
+ if (isColorized()) {
+ contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
+ getBackgroundColor());
+ } else {
+ // Clear it!
+ contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
+ 0);
+ }
+ }
+
/**
* @param remoteView the remote view to update the minheight in
* @param hasMinHeight does it have a mimHeight
@@ -3502,15 +3628,17 @@
}
private void bindExpandButton(RemoteViews contentView) {
- contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveContrastColor(),
+ int color = isColorized() ? getPrimaryTextColor() : resolveContrastColor();
+ contentView.setDrawableParameters(R.id.expand_button, false, -1, color,
PorterDuff.Mode.SRC_ATOP, -1);
contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
- resolveContrastColor());
+ color);
}
private void bindHeaderChronometerAndTime(RemoteViews contentView) {
if (showsTimeOrChronometer()) {
contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
+ setTextViewColorSecondary(contentView, R.id.time_divider);
if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
contentView.setLong(R.id.chronometer, "setBase",
@@ -3518,9 +3646,11 @@
contentView.setBoolean(R.id.chronometer, "setStarted", true);
boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
contentView.setChronometerCountDown(R.id.chronometer, countsDown);
+ setTextViewColorSecondary(contentView, R.id.chronometer);
} else {
contentView.setViewVisibility(R.id.time, View.VISIBLE);
contentView.setLong(R.id.time, "setTime", mN.when);
+ setTextViewColorSecondary(contentView, R.id.time);
}
} else {
// We still want a time to be set but gone, such that we can show and hide it
@@ -3543,8 +3673,10 @@
if (headerText != null) {
// TODO: Remove the span entirely to only have the string with propper formating.
contentView.setTextViewText(R.id.header_text, processLegacyText(headerText));
+ setTextViewColorSecondary(contentView, R.id.header_text);
contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
+ setTextViewColorSecondary(contentView, R.id.header_text_divider);
}
}
@@ -3583,8 +3715,12 @@
}
private void bindHeaderAppName(RemoteViews contentView, boolean ambient) {
contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
- contentView.setTextColor(R.id.app_name_text,
- ambient ? resolveAmbientColor() : resolveContrastColor());
+ if (isColorized()) {
+ setTextViewColorPrimary(contentView, R.id.app_name_text);
+ } else {
+ contentView.setTextColor(R.id.app_name_text,
+ ambient ? resolveAmbientColor() : resolveContrastColor());
+ }
}
private void bindSmallIcon(RemoteViews contentView, boolean ambient) {
@@ -3642,6 +3778,11 @@
big.setViewVisibility(R.id.actions, View.VISIBLE);
if (p.ambient) {
big.setInt(R.id.actions, "setBackgroundColor", Color.TRANSPARENT);
+ } else if (isColorized()) {
+ big.setInt(R.id.actions, "setBackgroundColor", getActionBarColor());
+ } else {
+ big.setInt(R.id.actions, "setBackgroundColor", mContext.getColor(
+ R.color.notification_action_list));
}
big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
R.dimen.notification_action_list_height);
@@ -3663,15 +3804,18 @@
&& replyText.length > 0 && !TextUtils.isEmpty(replyText[0])) {
big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_1, replyText[0]);
+ setTextViewColorSecondary(big, R.id.notification_material_reply_text_1);
if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])) {
big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_2, replyText[1]);
+ setTextViewColorSecondary(big, R.id.notification_material_reply_text_2);
if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])) {
big.setViewVisibility(
R.id.notification_material_reply_text_3, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_3, replyText[2]);
+ setTextViewColorSecondary(big, R.id.notification_material_reply_text_3);
}
}
}
@@ -3731,7 +3875,7 @@
} else if (mActions.size() != 0) {
result = applyStandardTemplateWithActions(getBigBaseLayoutResource());
}
- adaptNotificationHeaderForBigContentView(result);
+ makeHeaderExpanded(result);
return result;
}
@@ -3766,7 +3910,12 @@
}
}
- private void adaptNotificationHeaderForBigContentView(RemoteViews result) {
+ /**
+ * Adapt the Notification header if this view is used as an expanded view.
+ *
+ * @hide
+ */
+ public static void makeHeaderExpanded(RemoteViews result) {
if (result != null) {
result.setBoolean(R.id.notification_header, "setExpanded", true);
}
@@ -3826,7 +3975,57 @@
return publicView;
}
+ /**
+ * Construct a content view for the display when low - priority
+ *
+ * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
+ * a new subtext is created consisting of the content of the
+ * notification.
+ * @hide
+ */
+ public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
+ int color = mN.color;
+ mN.color = COLOR_DEFAULT;
+ CharSequence summary = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+ if (!useRegularSubtext || TextUtils.isEmpty(summary)) {
+ CharSequence newSummary = createSummaryText();
+ if (!TextUtils.isEmpty(newSummary)) {
+ mN.extras.putCharSequence(EXTRA_SUB_TEXT, newSummary);
+ }
+ }
+ RemoteViews header = makeNotificationHeader();
+ if (summary != null) {
+ mN.extras.putCharSequence(EXTRA_SUB_TEXT, summary);
+ } else {
+ mN.extras.remove(EXTRA_SUB_TEXT);
+ }
+ mN.color = color;
+ return header;
+ }
+ private CharSequence createSummaryText() {
+ CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
+ if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
+ return titleText;
+ }
+ SpannableStringBuilder summary = new SpannableStringBuilder();
+ if (titleText == null) {
+ titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
+ }
+ BidiFormatter bidi = BidiFormatter.getInstance();
+ if (titleText != null) {
+ summary.append(bidi.unicodeWrap(titleText));
+ }
+ CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
+ if (titleText != null && contentText != null) {
+ summary.append(bidi.unicodeWrap(mContext.getText(
+ R.string.notification_header_divider_symbol_with_spaces)));
+ }
+ if (contentText != null) {
+ summary.append(bidi.unicodeWrap(contentText));
+ }
+ return summary;
+ }
private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
boolean oddAction, boolean ambient) {
@@ -3842,6 +4041,7 @@
if (action.mRemoteInputs != null) {
button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
}
+ // TODO: handle emphasized mode / actions right
if (emphazisedMode) {
// change the background bgColor
int bgColor = mContext.getColor(oddAction ? R.color.notification_action_list
@@ -3857,16 +4057,19 @@
title = ensureColorSpanContrast(title, bgColor, outResultColor);
}
button.setTextViewText(R.id.action0, title);
+ setTextViewColorPrimary(button, R.id.action0);
if (outResultColor != null && outResultColor[0] != null) {
// We need to set the text color as well since changing a text to uppercase
// clears its spans.
button.setTextColor(R.id.action0, outResultColor[0]);
- } else if (mN.color != COLOR_DEFAULT) {
+ } else if (mN.color != COLOR_DEFAULT && !isColorized()) {
button.setTextColor(R.id.action0,resolveContrastColor());
}
} else {
button.setTextViewText(R.id.action0, processLegacyText(action.title));
- if (mN.color != COLOR_DEFAULT) {
+ if (isColorized()) {
+ setTextViewColorPrimary(button, R.id.action0);
+ } else if (mN.color != COLOR_DEFAULT) {
button.setTextColor(R.id.action0,
ambient ? resolveAmbientColor() : resolveContrastColor());
}
@@ -3985,11 +4188,16 @@
* doesn't create material notifications by itself) app.
*/
private boolean isLegacy() {
- return getColorUtil() != null;
+ if (!mIsLegacyInitialized) {
+ mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.LOLLIPOP;
+ mIsLegacyInitialized = true;
+ }
+ return mIsLegacy;
}
private CharSequence processLegacyText(CharSequence charSequence) {
- if (isLegacy()) {
+ if (isLegacy() || isColorized()) {
return getColorUtil().invertCharSequenceColors(charSequence);
} else {
return charSequence;
@@ -4251,6 +4459,37 @@
private int getActionTombstoneLayoutResource() {
return R.layout.notification_material_action_tombstone;
}
+
+ private int getBackgroundColor() {
+ if (isColorized()) {
+ return mN.color;
+ } else {
+ return COLOR_DEFAULT;
+ }
+ }
+
+ private boolean isColorized() {
+ return mN.isColorized();
+ }
+ }
+
+ /**
+ * @return whether this notification is ongoing and can't be dismissed by the user.
+ */
+ private boolean isOngoing() {
+ final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE
+ | Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
+ return (flags & ongoingFlags) != 0;
+ }
+
+ /**
+ * @return true if this notification is colorized. This also factors in wheather the
+ * notification is ongoing.
+ *
+ * @hide
+ */
+ public boolean isColorized() {
+ return extras.getBoolean(EXTRA_COLORIZED) && isOngoing();
}
private boolean hasLargeIcon() {
@@ -4584,6 +4823,7 @@
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
if (mSummaryTextSet) {
contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
+ mBuilder.setTextViewColorSecondary(contentView, R.id.text);
contentView.setViewVisibility(R.id.text, View.VISIBLE);
}
mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon());
@@ -4739,6 +4979,7 @@
static void applyBigTextContentView(Builder builder,
RemoteViews contentView, CharSequence bigTextText) {
contentView.setTextViewText(R.id.big_text, bigTextText);
+ builder.setTextViewColorSecondary(contentView, R.id.big_text);
contentView.setViewVisibility(R.id.big_text,
TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines(builder));
@@ -4789,6 +5030,7 @@
CharSequence mUserDisplayName;
CharSequence mConversationTitle;
List<Message> mMessages = new ArrayList<>();
+ List<Message> mHistoricMessages = new ArrayList<>();
MessagingStyle() {
}
@@ -4845,15 +5087,15 @@
* @return this object for method chaining
*/
public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
- mMessages.add(new Message(text, timestamp, sender));
- if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
- mMessages.remove(0);
- }
- return this;
+ return addMessage(new Message(text, timestamp, sender));
}
/**
* Adds a {@link Message} for display in this notification.
+ *
+ * <p>The messages should be added in chronologic order, i.e. the oldest first,
+ * the newest last.
+ *
* @param message The {@link Message} to be displayed
* @return this object for method chaining
*/
@@ -4866,6 +5108,27 @@
}
/**
+ * Adds a {@link Message} for historic context in this notification.
+ *
+ * <p>Messages should be added as historic if they are not the main subject of the
+ * notification but may give context to a conversation. The system may choose to present
+ * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
+ *
+ * <p>The messages should be added in chronologic order, i.e. the oldest first,
+ * the newest last.
+ *
+ * @param message The historic {@link Message} to be added
+ * @return this object for method chaining
+ */
+ public MessagingStyle addHistoricMessage(Message message) {
+ mHistoricMessages.add(message);
+ if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
+ mHistoricMessages.remove(0);
+ }
+ return this;
+ }
+
+ /**
* Gets the list of {@code Message} objects that represent the notification
*/
public List<Message> getMessages() {
@@ -4873,6 +5136,13 @@
}
/**
+ * Gets the list of historic {@code Message}s in the notification.
+ */
+ public List<Message> getHistoricMessages() {
+ return mHistoricMessages;
+ }
+
+ /**
* @hide
*/
@Override
@@ -4887,6 +5157,9 @@
if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
Message.getBundleArrayForMessages(mMessages));
}
+ if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES,
+ Message.getBundleArrayForMessages(mHistoricMessages));
+ }
fixTitleAndTextExtras(extras);
}
@@ -4926,11 +5199,16 @@
super.restoreFromExtras(extras);
mMessages.clear();
+ mHistoricMessages.clear();
mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
- Parcelable[] parcelables = extras.getParcelableArray(EXTRA_MESSAGES);
- if (parcelables != null && parcelables instanceof Parcelable[]) {
- mMessages = Message.getMessagesFromBundleArray(parcelables);
+ Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
+ if (messages != null && messages instanceof Parcelable[]) {
+ mMessages = Message.getMessagesFromBundleArray(messages);
+ }
+ Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
+ if (histMessages != null && histMessages instanceof Parcelable[]) {
+ mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
}
}
@@ -4945,7 +5223,7 @@
: (m == null) ? null : m.mSender;
CharSequence text = (m == null)
? null
- : mConversationTitle != null ? makeMessageLine(m) : m.mText;
+ : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
return mBuilder.applyStandardTemplateWithActions(mBuilder.getBaseLayoutResource(),
mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
@@ -4983,7 +5261,7 @@
CharSequence text;
if (hasTitle) {
bigTitle = title;
- text = makeMessageLine(mMessages.get(0));
+ text = makeMessageLine(mMessages.get(0), mBuilder);
} else {
bigTitle = mMessages.get(0).mSender;
text = mMessages.get(0).mText;
@@ -5015,13 +5293,13 @@
int contractedChildId = View.NO_ID;
Message contractedMessage = findLatestIncomingMessage();
- int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
- while (firstMessage + i < mMessages.size() && i < rowIds.length) {
- Message m = mMessages.get(firstMessage + i);
+ int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
+ - (rowIds.length - mMessages.size()));
+ while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
+ Message m = mHistoricMessages.get(firstHistoricMessage + i);
int rowId = rowIds[i];
- contentView.setViewVisibility(rowId, View.VISIBLE);
- contentView.setTextViewText(rowId, makeMessageLine(m));
+ contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
if (contractedMessage == m) {
contractedChildId = rowId;
@@ -5029,23 +5307,52 @@
i++;
}
+
+ int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
+ while (firstMessage + i < mMessages.size() && i < rowIds.length) {
+ Message m = mMessages.get(firstMessage + i);
+ int rowId = rowIds[i];
+
+ contentView.setViewVisibility(rowId, View.VISIBLE);
+ contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
+ mBuilder.setTextViewColorSecondary(contentView, rowId);
+
+ if (contractedMessage == m) {
+ contractedChildId = rowId;
+ }
+
+ i++;
+ }
+ // Clear the remaining views for reapply. Ensures that historic message views can
+ // reliably be identified as being GONE and having non-null text.
+ while (i < rowIds.length) {
+ int rowId = rowIds[i];
+ contentView.setTextViewText(rowId, null);
+ i++;
+ }
+
// Record this here to allow transformation between the contracted and expanded views.
contentView.setInt(R.id.notification_messaging, "setContractedChildId",
contractedChildId);
return contentView;
}
- private CharSequence makeMessageLine(Message m) {
+ private CharSequence makeMessageLine(Message m, Builder builder) {
BidiFormatter bidi = BidiFormatter.getInstance();
SpannableStringBuilder sb = new SpannableStringBuilder();
+ boolean colorize = builder.isColorized();
if (TextUtils.isEmpty(m.mSender)) {
CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
sb.append(bidi.unicodeWrap(replyName),
- makeFontColorSpan(mBuilder.resolveContrastColor()),
+ makeFontColorSpan(colorize
+ ? builder.getPrimaryTextColor()
+ : mBuilder.resolveContrastColor()),
0 /* flags */);
} else {
sb.append(bidi.unicodeWrap(m.mSender),
- makeFontColorSpan(Color.BLACK),
+ makeFontColorSpan(colorize
+ ? builder.getPrimaryTextColor()
+ : Color.BLACK),
0 /* flags */);
}
CharSequence text = m.mText == null ? "" : m.mText;
@@ -5064,7 +5371,7 @@
: (m == null) ? null : m.mSender;
CharSequence text = (m == null)
? null
- : mConversationTitle != null ? makeMessageLine(m) : m.mText;
+ : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
@@ -5355,6 +5662,7 @@
if (!TextUtils.isEmpty(str)) {
contentView.setViewVisibility(rowIds[i], View.VISIBLE);
contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str));
+ mBuilder.setTextViewColorSecondary(contentView, rowIds[i]);
contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
handleInboxImageMargin(contentView, rowIds[i], first);
if (first) {
@@ -7088,11 +7396,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 +7423,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 +7439,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 +7461,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 bdccb8a..26ec418 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -21,6 +21,7 @@
import org.xmlpull.v1.XmlSerializer;
import android.annotation.SystemApi;
+import android.app.NotificationManager;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -44,10 +45,13 @@
private static final String TAG_CHANNEL = "channel";
private static final String ATT_NAME = "name";
private static final String ATT_ID = "id";
+ private static final String ATT_DELETED = "deleted";
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_LIGHTS = "lights";
+ //TODO: add support for light colors
+ private static final String ATT_LIGHT_COLOR = "light_color";
private static final String ATT_VIBRATION = "vibration";
private static final String ATT_VIBRATION_ENABLED = "vibration_enabled";
private static final String ATT_SOUND = "sound";
@@ -55,6 +59,7 @@
private static final String ATT_AUDIO_ATTRIBUTES = "audio_attributes";
private static final String ATT_SHOW_BADGE = "show_badge";
private static final String ATT_USER_LOCKED = "locked";
+ private static final String ATT_GROUP = "group";
private static final String DELIMITER = ",";
/**
@@ -120,7 +125,8 @@
NotificationManager.VISIBILITY_NO_OVERRIDE;
private static final int DEFAULT_IMPORTANCE =
NotificationManager.IMPORTANCE_UNSPECIFIED;
- private static final boolean DEFAULT_ALLOWED = true;
+ private static final boolean DEFAULT_DELETED = false;
+ private static final boolean DEFAULT_SHOW_BADGE = true;
private final String mId;
private CharSequence mName;
@@ -132,8 +138,9 @@
private long[] mVibration;
private int mUserLockedFields;
private boolean mVibrationEnabled;
- private boolean mShowBadge;
- private boolean mAllowed = DEFAULT_ALLOWED;
+ private boolean mShowBadge = DEFAULT_SHOW_BADGE;
+ private boolean mDeleted = DEFAULT_DELETED;
+ private String mGroup;
/**
* Creates a notification channel.
@@ -170,7 +177,12 @@
mUserLockedFields = in.readInt();
mVibrationEnabled = in.readByte() != 0;
mShowBadge = in.readByte() != 0;
- mAllowed = in.readByte() != 0;
+ mDeleted = in.readByte() != 0;
+ if (in.readByte() != 0) {
+ mGroup = in.readString();
+ } else {
+ mGroup = null;
+ }
}
@Override
@@ -196,7 +208,13 @@
dest.writeInt(mUserLockedFields);
dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
dest.writeByte(mShowBadge ? (byte) 1 : (byte) 0);
- dest.writeByte(mAllowed ? (byte) 1 : (byte) 0);
+ dest.writeByte(mDeleted ? (byte) 1 : (byte) 0);
+ if (mGroup != null) {
+ dest.writeByte((byte) 1);
+ dest.writeString(mGroup);
+ } else {
+ dest.writeByte((byte) 0);
+ }
}
/**
@@ -207,6 +225,14 @@
mUserLockedFields |= field;
}
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void setDeleted(boolean deleted) {
+ mDeleted = deleted;
+ }
+
// Modifiable by a notification ranker.
/**
@@ -245,6 +271,18 @@
// Modifiable by apps on channel creation.
/**
+ * Sets what group this channel belongs to.
+ *
+ * Group information is only used for presentation, not for behavior.
+ *
+ * @param groupId the id of a group created by
+ * {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}.
+ */
+ public void setGroup(String groupId) {
+ this.mGroup = groupId;
+ }
+
+ /**
* Sets whether notifications posted to this channel can appear as application icon badges
* in a Launcher.
*
@@ -359,16 +397,28 @@
/**
* 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;
}
/**
- * Returns whether notifications are allowed to post to this channel.
+ * Returns what group this channel belongs to.
+ *
+ * This is used only for visually grouping channels in the UI.
*/
- public boolean isAllowed() {
- return mAllowed;
+ public String getGroup() {
+ return mGroup;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public boolean isDeleted() {
+ return mDeleted;
}
/**
@@ -393,6 +443,8 @@
enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
+ setDeleted(safeBool(parser, ATT_DELETED, false));
+ setGroup(parser.getAttributeValue(null, ATT_GROUP));
lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
}
@@ -434,6 +486,12 @@
if (canShowBadge()) {
out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
}
+ if (isDeleted()) {
+ out.attribute(null, ATT_DELETED, Boolean.toString(isDeleted()));
+ }
+ if (getGroup() != null) {
+ out.attribute(null, ATT_GROUP, getGroup());
+ }
out.endTag(null, TAG_CHANNEL);
}
@@ -464,6 +522,8 @@
record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
+ record.put(ATT_DELETED, Boolean.toString(isDeleted()));
+ record.put(ATT_GROUP, getGroup());
return record;
}
@@ -509,10 +569,12 @@
private static String longArrayToString(long[] values) {
StringBuffer sb = new StringBuffer();
- for (int i = 0; i < values.length - 1; i++) {
- sb.append(values[i]).append(DELIMITER);
+ if (values != null) {
+ for (int i = 0; i < values.length - 1; i++) {
+ sb.append(values[i]).append(DELIMITER);
+ }
+ sb.append(values[values.length - 1]);
}
- sb.append(values[values.length - 1]);
return sb.toString();
}
@@ -540,35 +602,41 @@
NotificationChannel that = (NotificationChannel) o;
- if (mImportance != that.mImportance) return false;
+ if (getImportance() != that.getImportance()) return false;
if (mBypassDnd != that.mBypassDnd) return false;
- if (mLockscreenVisibility != that.mLockscreenVisibility) return false;
+ if (getLockscreenVisibility() != that.getLockscreenVisibility()) return false;
if (mLights != that.mLights) return false;
- if (mUserLockedFields != that.mUserLockedFields) return false;
+ if (getUserLockedFields() != that.getUserLockedFields()) return false;
if (mVibrationEnabled != that.mVibrationEnabled) return false;
if (mShowBadge != that.mShowBadge) return false;
- if (mAllowed != that.mAllowed) return false;
- if (mId != null ? !mId.equals(that.mId) : that.mId != null) return false;
- if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;
- if (mSound != null ? !mSound.equals(that.mSound) : that.mSound != null) return false;
- return Arrays.equals(mVibration, that.mVibration);
+ if (isDeleted() != that.isDeleted()) return false;
+ if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
+ if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+ return false;
+ }
+ if (getSound() != null ? !getSound().equals(that.getSound()) : that.getSound() != null) {
+ return false;
+ }
+ if (!Arrays.equals(mVibration, that.mVibration)) return false;
+ return getGroup() != null ? getGroup().equals(that.getGroup()) : that.getGroup() == null;
}
@Override
public int hashCode() {
- int result = mId != null ? mId.hashCode() : 0;
- result = 31 * result + (mName != null ? mName.hashCode() : 0);
- result = 31 * result + mImportance;
+ int result = getId() != null ? getId().hashCode() : 0;
+ result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+ result = 31 * result + getImportance();
result = 31 * result + (mBypassDnd ? 1 : 0);
- result = 31 * result + mLockscreenVisibility;
- result = 31 * result + (mSound != null ? mSound.hashCode() : 0);
+ result = 31 * result + getLockscreenVisibility();
+ result = 31 * result + (getSound() != null ? getSound().hashCode() : 0);
result = 31 * result + (mLights ? 1 : 0);
result = 31 * result + Arrays.hashCode(mVibration);
- result = 31 * result + mUserLockedFields;
+ result = 31 * result + getUserLockedFields();
result = 31 * result + (mVibrationEnabled ? 1 : 0);
result = 31 * result + (mShowBadge ? 1 : 0);
- result = 31 * result + (mAllowed ? 1 : 0);
+ result = 31 * result + (isDeleted() ? 1 : 0);
+ result = 31 * result + (getGroup() != null ? getGroup().hashCode() : 0);
return result;
}
@@ -586,7 +654,8 @@
", mUserLockedFields=" + mUserLockedFields +
", mVibrationEnabled=" + mVibrationEnabled +
", mShowBadge=" + mShowBadge +
- ", mAllowed=" + mAllowed +
+ ", mDeleted=" + mDeleted +
+ ", mGroup='" + mGroup + '\'' +
'}';
}
}
diff --git a/core/java/android/app/NotificationChannelGroup.aidl b/core/java/android/app/NotificationChannelGroup.aidl
new file mode 100644
index 0000000..c0da037
--- /dev/null
+++ b/core/java/android/app/NotificationChannelGroup.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.app;
+
+parcelable NotificationChannelGroup;
\ No newline at end of file
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
new file mode 100644
index 0000000..3341b91
--- /dev/null
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -0,0 +1,187 @@
+/*
+ * 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.annotation.SystemApi;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.notification.NotificationListenerService;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A grouping of related notification channels. e.g., channels that all belong to a single account.
+ */
+public final class NotificationChannelGroup implements Parcelable {
+
+ private static final String TAG_GROUP = "channelGroup";
+ private static final String ATT_NAME = "name";
+ private static final String ATT_ID = "id";
+
+ private final String mId;
+ private CharSequence mName;
+ private List<NotificationChannel> mChannels = new ArrayList<>();
+
+ /**
+ * Creates a notification channel.
+ *
+ * @param id The id of the group. Must be unique per package.
+ * @param name The user visible name of the group.
+ */
+ public NotificationChannelGroup(String id, CharSequence name) {
+ this.mId = id;
+ this.mName = name;
+ }
+
+ protected NotificationChannelGroup(Parcel in) {
+ if (in.readByte() != 0) {
+ mId = in.readString();
+ } else {
+ mId = null;
+ }
+ mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ in.readList(mChannels, NotificationChannel.class.getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mId != null) {
+ dest.writeByte((byte) 1);
+ dest.writeString(mId);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ TextUtils.writeToParcel(mName, dest, flags);
+ dest.writeParcelableList(mChannels, flags);
+ }
+
+ /**
+ * Returns the id of this channel.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the user visible name of this channel.
+ */
+ public CharSequence getName() {
+ return mName;
+ }
+
+ /*
+ * Returns the list of channels that belong to this group
+ *
+ * @hide
+ */
+ @SystemApi
+ public List<NotificationChannel> getChannels() {
+ return mChannels;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void addChannel(NotificationChannel channel) {
+ mChannels.add(channel);
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void writeXml(XmlSerializer out) throws IOException {
+ out.startTag(null, TAG_GROUP);
+
+ out.attribute(null, ATT_ID, getId());
+ out.attribute(null, ATT_NAME, getName().toString());
+
+ out.endTag(null, TAG_GROUP);
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public JSONObject toJson() throws JSONException {
+ JSONObject record = new JSONObject();
+ record.put(ATT_ID, getId());
+ record.put(ATT_NAME, getName());
+ return record;
+ }
+
+ public static final Creator<NotificationChannelGroup> CREATOR =
+ new Creator<NotificationChannelGroup>() {
+ @Override
+ public NotificationChannelGroup createFromParcel(Parcel in) {
+ return new NotificationChannelGroup(in);
+ }
+
+ @Override
+ public NotificationChannelGroup[] newArray(int size) {
+ return new NotificationChannelGroup[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NotificationChannelGroup that = (NotificationChannelGroup) o;
+
+ if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
+ if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+ return false;
+ }
+ return getChannels() != null ? getChannels().equals(that.getChannels())
+ : that.getChannels() == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = getId() != null ? getId().hashCode() : 0;
+ result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+ result = 31 * result + (getChannels() != null ? getChannels().hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "NotificationChannelGroup{" +
+ "mId='" + mId + '\'' +
+ ", mName=" + mName +
+ '}';
+ }
+}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 3551691..5205959 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -24,6 +24,7 @@
import android.app.Notification.Builder;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -382,8 +383,46 @@
}
/**
+ * Creates a group container for {@link NotificationChannel} objects.
+ *
+ * This is a no-op for groups that already exist.
+ * <p>
+ * Group information is only used for presentation, not for behavior. Groups are optional
+ * for channels, and you can have a mix of channels that belong to groups and channels
+ * that do not.
+ * </p>
+ * <p>
+ * For example, if your application supports multiple accounts, and those accounts will
+ * have similar channels, you can create a group for each account with account specific
+ * labels instead of appending account information to each channel's label.
+ * </p>
+ *
+ * @param group The group to create
+ */
+ public void createNotificationChannelGroup(@NonNull NotificationChannelGroup group) {
+ createNotificationChannelGroups(Arrays.asList(group));
+ }
+
+ /**
+ * Creates multiple notification channel groups.
+ *
+ * @param groups The list of groups to create
+ */
+ public void createNotificationChannelGroups(@NonNull List<NotificationChannelGroup> groups) {
+ INotificationManager service = getService();
+ try {
+ service.createNotificationChannelGroups(mContext.getPackageName(),
+ new ParceledListSlice(groups));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Creates a notification channel that notifications can be posted to.
*
+ * This is a no-op for channels that already exist.
+ *
* @param channel the channel to create. Note that the created channel may differ from this
* value. If the channel already exists, it will not be modified.
*/
@@ -1085,4 +1124,38 @@
default: return defValue;
}
}
+
+ /**
+ * Start a service directly into the "foreground service" state. Unlike
+ * {@link android.content.Context#startService(Intent)}, this method
+ * can be used from within background operations like broadcast receivers
+ * or scheduled jobs.
+ *
+ * @param service Description of the service to be started. The Intent must be either
+ * fully explicit (supplying a component name) or specify a specific package
+ * name it is targeted to.
+ * @param id The identifier for this notification as per
+ * {@link #notify(int, Notification) NotificationManager.notify(int, Notification)};
+ * must not be 0.
+ * @param notification The Notification to be displayed.
+ * @return If the service is being started or is already running, the
+ * {@link ComponentName} of the actual service that was started is
+ * returned; else if the service does not exist null is returned.
+ */
+ @Nullable
+ public ComponentName startServiceInForeground(Intent service,
+ int id, Notification notification) {
+ return mContext.startServiceInForeground(service, id, notification);
+ }
+
+ /**
+ * @hide like {@link #startServiceInForeground(Intent, int, Notification)}
+ * but for a specific user.
+ */
+ @Nullable
+ public ComponentName startServiceInForegroundAsUser(Intent service,
+ int id, Notification notification, UserHandle user) {
+ return mContext.startServiceInForegroundAsUser(service, id, notification, user);
+ }
+
}
diff --git a/core/java/android/app/PictureInPictureArgs.aidl b/core/java/android/app/PictureInPictureArgs.aidl
new file mode 100644
index 0000000..49df39a
--- /dev/null
+++ b/core/java/android/app/PictureInPictureArgs.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.app;
+
+parcelable PictureInPictureArgs;
diff --git a/core/java/android/app/PictureInPictureArgs.java b/core/java/android/app/PictureInPictureArgs.java
new file mode 100644
index 0000000..fbdcbf4
--- /dev/null
+++ b/core/java/android/app/PictureInPictureArgs.java
@@ -0,0 +1,175 @@
+/*
+ * 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.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a set of arguments used to initialize the picture-in-picture mode.
+ */
+public final class PictureInPictureArgs implements Parcelable {
+
+ /**
+ * The expected aspect ratio of the picture-in-picture.
+ */
+ @Nullable
+ private Float mAspectRatio;
+
+ /**
+ * The set of actions that are associated with this activity when in picture in picture.
+ */
+ @Nullable
+ private List<RemoteAction> mUserActions;
+
+ PictureInPictureArgs(Parcel in) {
+ if (in.readInt() != 0) {
+ mAspectRatio = in.readFloat();
+ }
+ if (in.readInt() != 0) {
+ mUserActions = new ArrayList<>();
+ in.readParcelableList(mUserActions, RemoteAction.class.getClassLoader());
+ }
+ }
+
+ /**
+ * Creates a new set of picture-in-picture arguments.
+ */
+ public PictureInPictureArgs() {
+ // Empty constructor
+ }
+
+ /**
+ * Creates a new set of picture-in-picture arguments from the given {@param aspectRatio} and
+ * {@param actions}.
+ */
+ public PictureInPictureArgs(float aspectRatio, List<RemoteAction> actions) {
+ mAspectRatio = aspectRatio;
+ if (actions != null) {
+ mUserActions = new ArrayList<>(actions);
+ }
+ }
+
+ /**
+ * Copies the set parameters from the other picture-in-picture args.
+ * @hide
+ */
+ public void copyOnlySet(PictureInPictureArgs otherArgs) {
+ if (otherArgs.hasSetAspectRatio()) {
+ mAspectRatio = otherArgs.mAspectRatio;
+ }
+ if (otherArgs.hasSetActions()) {
+ mUserActions = otherArgs.mUserActions;
+ }
+ }
+
+ /**
+ * Sets the aspect ratio.
+ * @param aspectRatio the new aspect ratio for picture-in-picture.
+ */
+ public void setAspectRatio(float aspectRatio) {
+ mAspectRatio = aspectRatio;
+ }
+
+ /**
+ * @return the aspect ratio. If none is set, return 0.
+ * @hide
+ */
+ public float getAspectRatio() {
+ if (mAspectRatio != null) {
+ return mAspectRatio;
+ }
+ return 0f;
+ }
+
+ /**
+ * @return whether the aspect ratio is set.
+ * @hide
+ */
+ public boolean hasSetAspectRatio() {
+ return mAspectRatio != null;
+ }
+
+ /**
+ * Sets the user actions.
+ * @param actions the new actions to show in the picture-in-picture menu.
+ */
+ public void setActions(List<RemoteAction> actions) {
+ if (mUserActions != null) {
+ mUserActions = null;
+ }
+ if (actions != null) {
+ mUserActions = new ArrayList<>(actions);
+ }
+ }
+
+ /**
+ * @return the set of user actions.
+ * @hide
+ */
+ public List<RemoteAction> getActions() {
+ return mUserActions;
+ }
+
+ /**
+ * @return whether the user actions are set.
+ * @hide
+ */
+ public boolean hasSetActions() {
+ return mUserActions != null;
+ }
+
+ @Override
+ public PictureInPictureArgs clone() {
+ return new PictureInPictureArgs(mAspectRatio, mUserActions);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (mAspectRatio != null) {
+ out.writeInt(1);
+ out.writeFloat(mAspectRatio);
+ } else {
+ out.writeInt(0);
+ }
+ if (mUserActions != null) {
+ out.writeInt(1);
+ out.writeParcelableList(mUserActions, 0);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ public static final Creator<PictureInPictureArgs> CREATOR =
+ new Creator<PictureInPictureArgs>() {
+ public PictureInPictureArgs createFromParcel(Parcel in) {
+ return new PictureInPictureArgs(in);
+ }
+ public PictureInPictureArgs[] newArray(int size) {
+ return new PictureInPictureArgs[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index cea7c3c..f3fe677 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -39,11 +39,16 @@
/* Automatically stop the profiler when the app goes idle. */
public final boolean autoStopProfiler;
- public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop) {
+ /* Indicates whether to stream the profiling info to the out file continuously. */
+ public final boolean streamingOutput;
+
+ public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop,
+ boolean streaming) {
profileFile = filename;
profileFd = fd;
samplingInterval = interval;
autoStopProfiler = autoStop;
+ streamingOutput = streaming;
}
public int describeContents() {
@@ -64,6 +69,7 @@
}
out.writeInt(samplingInterval);
out.writeInt(autoStopProfiler ? 1 : 0);
+ out.writeInt(streamingOutput ? 1 : 0);
}
public static final Parcelable.Creator<ProfilerInfo> CREATOR =
@@ -82,5 +88,6 @@
profileFd = in.readInt() != 0 ? ParcelFileDescriptor.CREATOR.createFromParcel(in) : null;
samplingInterval = in.readInt();
autoStopProfiler = in.readInt() != 0;
+ streamingOutput = in.readInt() != 0;
}
}
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/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 45831a3..94a8990 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -916,44 +916,85 @@
}
}
- // Bail early if there is no work to do.
- if (updatedResourceKeys.isEmpty()) {
- return;
+ redirectResourcesToNewImplLocked(updatedResourceKeys);
+ }
+ }
+
+ // TODO(adamlesinski): Make this accept more than just overlay directories.
+ final void applyNewResourceDirsLocked(@NonNull final String baseCodePath,
+ @NonNull final String[] newResourceDirs) {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
+ "ResourcesManager#applyNewResourceDirsLocked");
+
+ final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
+ final int implCount = mResourceImpls.size();
+ for (int i = 0; i < implCount; i++) {
+ final ResourcesKey key = mResourceImpls.keyAt(i);
+ final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+ final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
+ if (impl != null && key.mResDir != null && key.mResDir.equals(baseCodePath)) {
+ updatedResourceKeys.put(impl, new ResourcesKey(
+ key.mResDir,
+ key.mSplitResDirs,
+ newResourceDirs,
+ key.mLibDirs,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo));
+ }
}
- // Update any references to ResourcesImpl that require reloading.
- final int resourcesCount = mResourceReferences.size();
- for (int i = 0; i < resourcesCount; i++) {
- final Resources r = mResourceReferences.get(i).get();
+ invalidatePath("/");
+
+ redirectResourcesToNewImplLocked(updatedResourceKeys);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+
+ private void redirectResourcesToNewImplLocked(
+ @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
+ // Bail early if there is no work to do.
+ if (updatedResourceKeys.isEmpty()) {
+ return;
+ }
+
+ // Update any references to ResourcesImpl that require reloading.
+ final int resourcesCount = mResourceReferences.size();
+ for (int i = 0; i < resourcesCount; i++) {
+ final WeakReference<Resources> ref = mResourceReferences.get(i);
+ final Resources r = ref != null ? ref.get() : null;
+ if (r != null) {
+ final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
+ if (key != null) {
+ final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
+ if (impl == null) {
+ throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
+ }
+ r.setImpl(impl);
+ }
+ }
+ }
+
+ // Update any references to ResourcesImpl that require reloading for each Activity.
+ for (ActivityResources activityResources : mActivityResourceReferences.values()) {
+ final int resCount = activityResources.activityResources.size();
+ for (int i = 0; i < resCount; i++) {
+ final WeakReference<Resources> ref = activityResources.activityResources.get(i);
+ final Resources r = ref != null ? ref.get() : null;
if (r != null) {
final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
if (key != null) {
final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
if (impl == null) {
- throw new Resources.NotFoundException("failed to load " + libAsset);
+ throw new Resources.NotFoundException(
+ "failed to redirect ResourcesImpl");
}
r.setImpl(impl);
}
}
}
-
- // Update any references to ResourcesImpl that require reloading for each Activity.
- for (ActivityResources activityResources : mActivityResourceReferences.values()) {
- final int resCount = activityResources.activityResources.size();
- for (int i = 0; i < resCount; i++) {
- final Resources r = activityResources.activityResources.get(i).get();
- if (r != null) {
- final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
- if (key != null) {
- final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
- if (impl == null) {
- throw new Resources.NotFoundException("failed to load " + libAsset);
- }
- r.setImpl(impl);
- }
- }
- }
- }
}
}
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9387019..5a75a67 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -112,13 +112,14 @@
import android.os.storage.StorageManager;
import android.print.IPrintManager;
import android.print.PrintManager;
+import android.service.autofill.IAutoFillManagerService;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
import android.telecom.TelecomManager;
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;
@@ -126,13 +127,16 @@
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
+import android.view.autofill.AutoFillManager;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
import android.view.textservice.TextServicesManager;
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 +230,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 +797,23 @@
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));
+ }});
+ registerService(Context.AUTO_FILL_MANAGER_SERVICE, AutoFillManager.class,
+ new CachedServiceFetcher<AutoFillManager>() {
+ @Override
+ public AutoFillManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.AUTO_FILL_MANAGER_SERVICE);
+ IAutoFillManagerService service = IAutoFillManagerService.Stub.asInterface(b);
+ return new AutoFillManager(ctx, service);
+ }});
}
/**
diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java
index 0077db1..bab993f 100644
--- a/core/java/android/app/TaskStackBuilder.java
+++ b/core/java/android/app/TaskStackBuilder.java
@@ -226,7 +226,7 @@
* Start the task stack constructed by this builder.
*
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*/
public void startActivities(Bundle options) {
@@ -259,7 +259,7 @@
* {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
* intent that can be supplied when the actual send happens.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @return The obtained PendingIntent
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index fd766bf..35c67d3 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -35,7 +35,7 @@
}
@Override
- public void onPinnedActivityRestartAttempt() throws RemoteException {
+ public void onPinnedActivityRestartAttempt(ComponentName sourceComponent) throws RemoteException {
}
@Override
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 2572a20..af41a7d 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -271,6 +271,21 @@
}
/**
+ * Gets the valid inputs to {@link #setTheme(String)}.
+ * @hide
+ */
+ public String[] getAvailableThemes() {
+ if (mService != null) {
+ try {
+ return mService.getAvailableThemes();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns the currently configured night mode.
* <p>
* May be one of:
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index a248bce..34a0c30 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -707,17 +707,24 @@
}
/**
- * Allows the receiver to be notified when information about a pending system update is
+ * Called when the information about a pending system update is available.
+ *
+ * <p>Allows the receiver to be notified when information about a pending system update is
* available from the system update service. The same pending system update can trigger multiple
* calls to this method, so it is necessary to examine the incoming parameters for details about
* the update.
- * <p>
- * This callback is only applicable to device owners.
+ *
+ * <p>This callback is only applicable to device owners and profile owners.
+ *
+ * <p>To get further information about a pending system update (for example, whether or not the
+ * update is a security patch), the device owner or profile owner can call
+ * {@link DevicePolicyManager#getPendingSystemUpdate}.
*
* @param context The running context as per {@link #onReceive}.
* @param intent The received intent as per {@link #onReceive}.
* @param receivedTime The time as given by {@link System#currentTimeMillis()} indicating when
* the current pending update was first available. -1 if no pending update is available.
+ * @see DevicePolicyManager#getPendingSystemUpdate
*/
public void onSystemUpdatePending(Context context, Intent intent, long receivedTime) {
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 2701698..0546708 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -34,6 +34,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
@@ -909,6 +910,16 @@
* or the organization name.
*
* <p> Use in Bundle {@link #EXTRA_PROVISIONING_DISCLAIMERS}
+ *
+ * <p> System app, i.e. application with {@link ApplicationInfo#FLAG_SYSTEM}, can also insert a
+ * disclaimer by declaring an application-level meta-data in {@code AndroidManifest.xml}.
+ * Must use it with {@link #EXTRA_PROVISIONING_DISCLAIMER_CONTENT}. Here is the example:
+ *
+ * <pre>
+ * <meta-data
+ * android:name="android.app.extra.PROVISIONING_DISCLAIMER_HEADER"
+ * android:resource="@string/disclaimer_header"
+ * /></pre>
*/
public static final String EXTRA_PROVISIONING_DISCLAIMER_HEADER =
"android.app.extra.PROVISIONING_DISCLAIMER_HEADER";
@@ -931,6 +942,16 @@
* {@link android.content.ClipData} of the intent too.
*
* <p> Use in Bundle {@link #EXTRA_PROVISIONING_DISCLAIMERS}
+ *
+ * <p> System app, i.e. application with {@link ApplicationInfo#FLAG_SYSTEM}, can also insert a
+ * disclaimer by declaring an application-level meta-data in {@code AndroidManifest.xml}.
+ * Must use it with {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER}. Here is the example:
+ *
+ * <pre>
+ * <meta-data
+ * android:name="android.app.extra.PROVISIONING_DISCLAIMER_CONTENT"
+ * android:resource="@string/disclaimer_content"
+ * /></pre>
*/
public static final String EXTRA_PROVISIONING_DISCLAIMER_CONTENT =
"android.app.extra.PROVISIONING_DISCLAIMER_CONTENT";
@@ -1091,6 +1112,30 @@
public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
/**
+ * Constant to indicate the feature of disabling the camera. Used as argument to
+ * {@link #createAdminSupportIntent(String)}.
+ * @see #setCameraDisabled(ComponentName, boolean)
+ */
+ public static final String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+
+ /**
+ * Constant to indicate the feature of disabling screen captures. Used as argument to
+ * {@link #createAdminSupportIntent(String)}.
+ * @see #setScreenCaptureDisabled(ComponentName, boolean)
+ */
+ public static final String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
+
+ /**
+ * A String indicating a specific restricted feature. Can be a user restriction from the
+ * {@link UserManager}, e.g. {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the values
+ * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @see #createAdminSupportIntent(String)
+ * @hide
+ */
+ @TestApi
+ public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
+
+ /**
* Activity action: have the user enter a new password. This activity should
* be launched after using {@link #setPasswordQuality(ComponentName, int)},
* or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user
@@ -1132,6 +1177,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 +1256,53 @@
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";
+
+ /**
+ * Delegation of application uninstall block. This scope grants access to the
+ * {@link #setUninstallBlocked} API.
+ */
+ public static final String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
+
+ /**
+ * Delegation of permission policy and permission grant state. This scope grants access to the
+ * {@link #setPermissionPolicy}, {@link #getPermissionGrantState},
+ * and {@link #setPermissionGrantState} APIs.
+ */
+ public static final String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
+
+ /**
+ * Delegation of package access state. This scope grants access to the
+ * {@link #isApplicationHidden}, {@link #setApplicationHidden}, {@link #isPackageSuspended}, and
+ * {@link #setPackagesSuspended} APIs.
+ */
+ public static final String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+
+ /**
+ * Delegation for enabling system apps. This scope grants access to the {@link #enableSystemApp}
+ * API.
+ */
+ public static final String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+
+ /**
+ * Delegation of management of uninstalled packages. This scope grants access to the
+ * {@code #setKeepUninstalledPackages} and {@code #getKeepUninstalledPackages} APIs.
+ * @hide
+ */
+ public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES =
+ "delegation-keep-uninstalled-packages";
+
+ /**
* No management for current user in-effect. This is the default.
* @hide
*/
@@ -1438,6 +1547,7 @@
}
return false;
}
+
/**
* Return true if the given administrator component is currently being removed
* for the user.
@@ -1454,7 +1564,6 @@
return false;
}
-
/**
* Return a list of all currently active device administrators' component
* names. If there are no administrators {@code null} may be
@@ -1485,6 +1594,7 @@
* or uninstalled.
* @hide
*/
+ @SystemApi
public boolean packageHasActiveAdmins(String packageName) {
return packageHasActiveAdmins(packageName, myUserId());
}
@@ -3245,6 +3355,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.
@@ -3253,12 +3367,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();
}
@@ -3269,18 +3385,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) {
@@ -3305,7 +3427,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 {
@@ -3334,8 +3456,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();
}
@@ -3355,7 +3477,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();
@@ -3387,6 +3509,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) {
@@ -3418,6 +3542,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) {
@@ -3430,8 +3556,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) {
@@ -3452,11 +3578,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();
}
@@ -3492,7 +3620,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");
@@ -3506,14 +3638,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");
@@ -3528,6 +3665,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.
*
@@ -3734,13 +3948,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.
@@ -4432,7 +4646,9 @@
}
/**
- * Called by device or profile owners to suspend packages for this user.
+ * Called by device or profile owners to suspend packages for this user. This function can be
+ * called by a device owner, profile owner, or by a delegate given the
+ * {@link #DELEGATION_PACKAGE_ACCESS} scope via {@link #setDelegatedScopes}.
* <p>
* A suspended package will not be able to start activities. Its notifications will be hidden,
* it will not show up in recents, will not be able to show toasts or dialogs or ring the
@@ -4442,20 +4658,24 @@
* package will no longer be suspended. The admin can block this by using
* {@link #setUninstallBlocked}.
*
- * @param admin The name of the admin component to check.
+ * @param admin The name of the admin component to check, or {@code null} if the caller is a
+ * package access delegate.
* @param packageNames The package names to suspend or unsuspend.
* @param suspended If set to {@code true} than the packages will be suspended, if set to
* {@code false} the packages will be unsuspended.
* @return an array of package names for which the suspended status is not set as requested in
* this method.
* @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_PACKAGE_ACCESS
*/
public @NonNull String[] setPackagesSuspended(@NonNull ComponentName admin,
@NonNull String[] packageNames, boolean suspended) {
throwIfParentInstance("setPackagesSuspended");
if (mService != null) {
try {
- return mService.setPackagesSuspended(admin, packageNames, suspended);
+ return mService.setPackagesSuspended(admin, mContext.getPackageName(), packageNames,
+ suspended);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -4464,21 +4684,26 @@
}
/**
- * Called by device or profile owners to determine if a package is suspended.
+ * Determine if a package is suspended. This function can be called by a device owner, profile
+ * owner, or by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
+ * {@link #setDelegatedScopes}.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is a package access delegate.
* @param packageName The name of the package to retrieve the suspended status of.
* @return {@code true} if the package is suspended or {@code false} if the package is not
* suspended, could not be found or an error occurred.
* @throws SecurityException if {@code admin} is not a device or profile owner.
* @throws NameNotFoundException if the package could not be found.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_PACKAGE_ACCESS
*/
public boolean isPackageSuspended(@NonNull ComponentName admin, String packageName)
throws NameNotFoundException {
throwIfParentInstance("isPackageSuspended");
if (mService != null) {
try {
- return mService.isPackageSuspended(admin, packageName);
+ return mService.isPackageSuspended(admin, mContext.getPackageName(), packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (IllegalArgumentException ex) {
@@ -4685,7 +4910,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");
@@ -4702,14 +4931,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) {
@@ -4729,12 +4964,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();
}
@@ -4746,8 +4986,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>
@@ -4774,7 +5014,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
@@ -4783,7 +5024,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();
}
@@ -5331,19 +5573,24 @@
}
/**
- * Called by a device owner to get the list of apps to keep around as APKs even if no user has
- * currently installed it.
+ * Get the list of apps to keep around as APKs even if no user has currently installed it. This
+ * function can be called by a device owner or by a delegate given the
+ * {@link #DELEGATION_KEEP_UNINSTALLED_PACKAGES} scope via {@link #setDelegatedScopes}.
+ * <p>
+ * Please note that packages returned in this method are not automatically pre-cached.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is a keep uninstalled packages delegate.
* @return List of package names to keep cached.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_KEEP_UNINSTALLED_PACKAGES
* @hide
*/
- public @Nullable List<String> getKeepUninstalledPackages(@NonNull ComponentName admin) {
+ public @Nullable List<String> getKeepUninstalledPackages(@Nullable ComponentName admin) {
throwIfParentInstance("getKeepUninstalledPackages");
if (mService != null) {
try {
- return mService.getKeepUninstalledPackages(admin);
+ return mService.getKeepUninstalledPackages(admin, mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5352,23 +5599,27 @@
}
/**
- * Called by a device owner to set a list of apps to keep around as APKs even if no user has
- * currently installed it.
+ * Set a list of apps to keep around as APKs even if no user has currently installed it. This
+ * function can be called by a device owner or by a delegate given the
+ * {@link #DELEGATION_KEEP_UNINSTALLED_PACKAGES} scope via {@link #setDelegatedScopes}.
*
* <p>Please note that setting this policy does not imply that specified apps will be
* automatically pre-cached.</p>
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is a keep uninstalled packages delegate.
* @param packageNames List of package names to keep cached.
* @throws SecurityException if {@code admin} is not a device owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_KEEP_UNINSTALLED_PACKAGES
* @hide
*/
- public void setKeepUninstalledPackages(@NonNull ComponentName admin,
+ public void setKeepUninstalledPackages(@Nullable ComponentName admin,
@NonNull List<String> packageNames) {
throwIfParentInstance("setKeepUninstalledPackages");
if (mService != null) {
try {
- mService.setKeepUninstalledPackages(admin, packageNames);
+ mService.setKeepUninstalledPackages(admin, mContext.getPackageName(), packageNames);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5522,8 +5773,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
*
@@ -5534,7 +5785,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(
@@ -5542,7 +5794,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();
}
@@ -5619,22 +5872,55 @@
}
/**
- * Called by profile or device owners to hide or unhide packages. When a package is hidden it is
- * unavailable for use, but the data and actual package file remain.
+ * Called by any app to display a support dialog when a feature was disabled by an admin.
+ * This returns an intent that can be used with {@link Context#startActivity(Intent)} to
+ * display the dialog. It will tell the user that the feature indicated by {@code restriction}
+ * was disabled by an admin, and include a link for more information. The default content of
+ * the dialog can be changed by the restricting admin via
+ * {@link #setShortSupportMessage(ComponentName, CharSequence)}. If the restriction is not
+ * set (i.e. the feature is available), then the return value will be {@code null}.
+ * @param restriction Indicates for which feature the dialog should be displayed. Can be a
+ * user restriction from {@link UserManager}, e.g.
+ * {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the constants
+ * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @return Intent An intent to be used to start the dialog-activity if the restriction is
+ * set by an admin, or null if the restriction does not exist or no admin set it.
+ */
+ public Intent createAdminSupportIntent(@NonNull String restriction) {
+ throwIfParentInstance("createAdminSupportIntent");
+ if (mService != null) {
+ try {
+ return mService.createAdminSupportIntent(restriction);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and
+ * actual package file remain. This function can be called by a device owner, profile owner, or
+ * by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
+ * {@link #setDelegatedScopes}.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is a package access delegate.
* @param packageName The name of the package to hide or unhide.
* @param hidden {@code true} if the package should be hidden, {@code false} if it should be
* unhidden.
* @return boolean Whether the hidden setting of the package was successfully updated.
* @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_PACKAGE_ACCESS
*/
public boolean setApplicationHidden(@NonNull ComponentName admin, String packageName,
boolean hidden) {
throwIfParentInstance("setApplicationHidden");
if (mService != null) {
try {
- return mService.setApplicationHidden(admin, packageName, hidden);
+ return mService.setApplicationHidden(admin, mContext.getPackageName(), packageName,
+ hidden);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5643,18 +5929,23 @@
}
/**
- * Called by profile or device owners to determine if a package is hidden.
+ * Determine if a package is hidden. This function can be called by a device owner, profile
+ * owner, or by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
+ * {@link #setDelegatedScopes}.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is a package access delegate.
* @param packageName The name of the package to retrieve the hidden status of.
* @return boolean {@code true} if the package is hidden, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_PACKAGE_ACCESS
*/
public boolean isApplicationHidden(@NonNull ComponentName admin, String packageName) {
throwIfParentInstance("isApplicationHidden");
if (mService != null) {
try {
- return mService.isApplicationHidden(admin, packageName);
+ return mService.isApplicationHidden(admin, mContext.getPackageName(), packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5663,18 +5954,22 @@
}
/**
- * Called by profile or device owners to re-enable a system app that was disabled by default
- * when the user was initialized.
+ * Re-enable a system app that was disabled by default when the user was initialized. This
+ * function can be called by a device owner, profile owner, or by a delegate given the
+ * {@link #DELEGATION_ENABLE_SYSTEM_APP} scope via {@link #setDelegatedScopes}.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is an enable system app delegate.
* @param packageName The package to be re-enabled in the calling profile.
* @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_PACKAGE_ACCESS
*/
public void enableSystemApp(@NonNull ComponentName admin, String packageName) {
throwIfParentInstance("enableSystemApp");
if (mService != null) {
try {
- mService.enableSystemApp(admin, packageName);
+ mService.enableSystemApp(admin, mContext.getPackageName(), packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5682,20 +5977,24 @@
}
/**
- * Called by profile or device owners to re-enable system apps by intent that were disabled by
- * default when the user was initialized.
+ * Re-enable system apps by intent that were disabled by default when the user was initialized.
+ * This function can be called by a device owner, profile owner, or by a delegate given the
+ * {@link #DELEGATION_ENABLE_SYSTEM_APP} scope via {@link #setDelegatedScopes}.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is an enable system app delegate.
* @param intent An intent matching the app(s) to be installed. All apps that resolve for this
* intent will be re-enabled in the calling profile.
* @return int The number of activities that matched the intent and were installed.
* @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_PACKAGE_ACCESS
*/
public int enableSystemApp(@NonNull ComponentName admin, Intent intent) {
throwIfParentInstance("enableSystemApp");
if (mService != null) {
try {
- return mService.enableSystemAppWithIntent(admin, intent);
+ return mService.enableSystemAppWithIntent(admin, mContext.getPackageName(), intent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5973,19 +6272,25 @@
}
/**
- * Called by profile or device owners to change whether a user can uninstall a package.
+ * Change whether a user can uninstall a package. This function can be called by a device owner,
+ * profile owner, or by a delegate given the {@link #DELEGATION_BLOCK_UNINSTALL} scope via
+ * {@link #setDelegatedScopes}.
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is a block uninstall delegate.
* @param packageName package to change.
* @param uninstallBlocked true if the user shouldn't be able to uninstall the package.
* @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_BLOCK_UNINSTALL
*/
- public void setUninstallBlocked(@NonNull ComponentName admin, String packageName,
+ public void setUninstallBlocked(@Nullable ComponentName admin, String packageName,
boolean uninstallBlocked) {
throwIfParentInstance("setUninstallBlocked");
if (mService != null) {
try {
- mService.setUninstallBlocked(admin, packageName, uninstallBlocked);
+ mService.setUninstallBlocked(admin, mContext.getPackageName(), packageName,
+ uninstallBlocked);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -6198,12 +6503,18 @@
}
/**
- * Callable by the system update service to notify device owners about pending updates.
- * The caller must hold {@link android.Manifest.permission#NOTIFY_PENDING_SYSTEM_UPDATE}
- * permission.
+ * Called by the system update service to notify device and profile owners of pending system
+ * updates.
*
- * @param updateReceivedTime The time as given by {@link System#currentTimeMillis()} indicating
- * when the current pending update was first available. -1 if no update is available.
+ * The caller must hold {@link android.Manifest.permission#NOTIFY_PENDING_SYSTEM_UPDATE}
+ * permission. This method should only be used when it is unknown whether the pending system
+ * update is a security patch. Otherwise, use
+ * {@link #notifyPendingSystemUpdate(long, boolean)}.
+ *
+ * @param updateReceivedTime The time as given by {@link System#currentTimeMillis()}
+ * indicating when the current pending update was first available. {@code -1} if no
+ * update is available.
+ * @see #notifyPendingSystemUpdate(long, boolean)
* @hide
*/
@SystemApi
@@ -6211,7 +6522,36 @@
throwIfParentInstance("notifyPendingSystemUpdate");
if (mService != null) {
try {
- mService.notifyPendingSystemUpdate(updateReceivedTime);
+ mService.notifyPendingSystemUpdate(SystemUpdateInfo.of(updateReceivedTime));
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Called by the system update service to notify device and profile owners of pending system
+ * updates.
+ *
+ * The caller must hold {@link android.Manifest.permission#NOTIFY_PENDING_SYSTEM_UPDATE}
+ * permission. This method should be used instead of {@link #notifyPendingSystemUpdate(long)}
+ * when it is known whether the pending system update is a security patch.
+ *
+ * @param updateReceivedTime The time as given by {@link System#currentTimeMillis()}
+ * indicating when the current pending update was first available. {@code -1} if no
+ * update is available.
+ * @param isSecurityPatch {@code true} if this system update is purely a security patch;
+ * {@code false} if not.
+ * @see #notifyPendingSystemUpdate(long)
+ * @hide
+ */
+ @SystemApi
+ public void notifyPendingSystemUpdate(long updateReceivedTime, boolean isSecurityPatch) {
+ throwIfParentInstance("notifyPendingSystemUpdate");
+ if (mService != null) {
+ try {
+ mService.notifyPendingSystemUpdate(SystemUpdateInfo.of(updateReceivedTime,
+ isSecurityPatch));
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -6236,12 +6576,14 @@
}
/**
- * Called by profile or device owners to set the default response for future runtime permission
- * requests by applications. The policy can allow for normal operation which prompts the user to
- * grant a permission, or can allow automatic granting or denying of runtime permission requests
- * by an application. This also applies to new permissions declared by app updates. When a
- * permission is denied or granted this way, the effect is equivalent to setting the permission
- * grant state via {@link #setPermissionGrantState}.
+ * Set the default response for future runtime permission requests by applications. This
+ * function can be called by a device owner, profile owner, or by a delegate given the
+ * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}.
+ * The policy can allow for normal operation which prompts the user to grant a permission, or
+ * can allow automatic granting or denying of runtime permission requests by an application.
+ * This also applies to new permissions declared by app updates. When a permission is denied or
+ * granted this way, the effect is equivalent to setting the permission * grant state via
+ * {@link #setPermissionGrantState}.
* <p/>
* As this policy only acts on runtime permission requests, it only applies to applications
* built with a {@code targetSdkVersion} of {@link android.os.Build.VERSION_CODES#M} or later.
@@ -6251,11 +6593,13 @@
* {@link #PERMISSION_POLICY_AUTO_GRANT} and {@link #PERMISSION_POLICY_AUTO_DENY}.
* @throws SecurityException if {@code admin} is not a device or profile owner.
* @see #setPermissionGrantState
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_PERMISSION_GRANT
*/
public void setPermissionPolicy(@NonNull ComponentName admin, int policy) {
throwIfParentInstance("setPermissionPolicy");
try {
- mService.setPermissionPolicy(admin, policy);
+ mService.setPermissionPolicy(admin, mContext.getPackageName(), policy);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -6264,6 +6608,7 @@
/**
* Returns the current runtime permission policy set by the device or profile owner. The
* default is {@link #PERMISSION_POLICY_PROMPT}.
+ *
* @param admin Which profile or device owner this request is associated with.
* @return the current policy for future permission requests.
*/
@@ -6283,7 +6628,8 @@
* cannot manage it through the UI, and {@link #PERMISSION_GRANT_STATE_GRANTED granted} in which
* the permission is granted and the user cannot manage it through the UI. This might affect all
* permissions in a group that the runtime permission belongs to. This method can only be called
- * by a profile or device owner.
+ * by a profile owner, device owner, or a delegate given the
+ * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}.
* <p/>
* Setting the grant state to {@link #PERMISSION_GRANT_STATE_DEFAULT default} does not revoke
* the permission. It retains the previous grant, if any.
@@ -6302,21 +6648,27 @@
* @see #PERMISSION_GRANT_STATE_DENIED
* @see #PERMISSION_GRANT_STATE_DEFAULT
* @see #PERMISSION_GRANT_STATE_GRANTED
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_PERMISSION_GRANT
*/
public boolean setPermissionGrantState(@NonNull ComponentName admin, String packageName,
String permission, int grantState) {
throwIfParentInstance("setPermissionGrantState");
try {
- return mService.setPermissionGrantState(admin, packageName, permission, grantState);
+ return mService.setPermissionGrantState(admin, mContext.getPackageName(), packageName,
+ permission, grantState);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
- * Returns the current grant state of a runtime permission for a specific application.
+ * Returns the current grant state of a runtime permission for a specific application. This
+ * function can be called by a device owner, profile owner, or by a delegate given the
+ * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}.
*
- * @param admin Which profile or device owner this request is associated with.
+ * @param admin Which profile or device owner this request is associated with, or {@code null}
+ * if the caller is a permission grant delegate.
* @param packageName The application to check the grant state for.
* @param permission The permission to check for.
* @return the current grant state specified by device policy. If the profile or device owner
@@ -6331,12 +6683,15 @@
* @throws SecurityException if {@code admin} is not a device or profile owner.
* @see #setPermissionGrantState(ComponentName, String, String, int)
* @see PackageManager#checkPermission(String, String)
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_PERMISSION_GRANT
*/
public int getPermissionGrantState(@Nullable ComponentName admin, String packageName,
String permission) {
throwIfParentInstance("getPermissionGrantState");
try {
- return mService.getPermissionGrantState(admin, packageName, permission);
+ return mService.getPermissionGrantState(admin, mContext.getPackageName(), packageName,
+ permission);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -7137,15 +7492,14 @@
}
/**
- * @hide
- * Enable backup service.
- * <p>This includes all backup and restore mechanisms.
- * Setting this to {@code false} will make backup service no-op or return empty results.
+ * Allows the device owner to enable or disable the backup service.
*
- * <p>There must be only one user on the device, managed by the device owner.
- * Otherwise a {@link SecurityException} will be thrown.
+ * <p> Backup service manages all backup and restore mechanisms on the device. Setting this to
+ * false will prevent data from being backed up or restored.
*
- * <p>Backup service is off by default when device owner is present.
+ * <p> Backup service is off by default when device owner is present.
+ *
+ * @throws SecurityException if {@code admin} is not a device owner.
*/
public void setBackupServiceEnabled(@NonNull ComponentName admin, boolean enabled) {
try {
@@ -7156,8 +7510,12 @@
}
/**
- * @hide
+ * Return whether the backup service is enabled by the device owner.
+ *
+ * <p> Backup service manages all backup and restore mechanisms on the device.
+ *
* @return {@code true} if backup service is enabled, {@code false} otherwise.
+ * @see #setBackupServiceEnabled
*/
public boolean isBackupServiceEnabled(@NonNull ComponentName admin) {
try {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8891f93..79fe10e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -150,20 +150,24 @@
void setDeviceOwnerLockScreenInfo(in ComponentName who, CharSequence deviceOwnerInfo);
CharSequence getDeviceOwnerLockScreenInfo();
- String[] setPackagesSuspended(in ComponentName admin, in String[] packageNames, boolean suspended);
- boolean isPackageSuspended(in ComponentName admin, String packageName);
+ String[] setPackagesSuspended(in ComponentName admin, in String callerPackage, in String[] packageNames, boolean suspended);
+ boolean isPackageSuspended(in ComponentName admin, in String callerPackage, 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);
@@ -197,15 +201,16 @@
List getPermittedInputMethodsForCurrentUser();
boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId);
- boolean setApplicationHidden(in ComponentName admin, in String packageName, boolean hidden);
- boolean isApplicationHidden(in ComponentName admin, in String packageName);
+ Intent createAdminSupportIntent(in String restriction);
+ boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden);
+ boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName);
UserHandle createAndManageUser(in ComponentName who, in String name, in ComponentName profileOwner, in PersistableBundle adminExtras, in int flags);
boolean removeUser(in ComponentName who, in UserHandle userHandle);
boolean switchUser(in ComponentName who, in UserHandle userHandle);
- void enableSystemApp(in ComponentName admin, in String packageName);
- int enableSystemAppWithIntent(in ComponentName admin, in Intent intent);
+ void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName);
+ int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
String[] getAccountTypesWithManagementDisabled();
@@ -223,7 +228,7 @@
void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userId);
- void setUninstallBlocked(in ComponentName admin, in String packageName, boolean uninstallBlocked);
+ void setUninstallBlocked(in ComponentName admin, in String callerPackage, in String packageName, boolean uninstallBlocked);
boolean isUninstallBlocked(in ComponentName admin, in String packageName);
void setCrossProfileCallerIdDisabled(in ComponentName who, boolean disabled);
@@ -264,18 +269,18 @@
boolean setStatusBarDisabled(in ComponentName who, boolean disabled);
boolean getDoNotAskCredentialsOnBoot();
- void notifyPendingSystemUpdate(in long updateReceivedTime);
+ void notifyPendingSystemUpdate(in SystemUpdateInfo info);
SystemUpdateInfo getPendingSystemUpdate(in ComponentName admin);
- void setPermissionPolicy(in ComponentName admin, int policy);
+ void setPermissionPolicy(in ComponentName admin, in String callerPackage, int policy);
int getPermissionPolicy(in ComponentName admin);
- boolean setPermissionGrantState(in ComponentName admin, String packageName,
+ boolean setPermissionGrantState(in ComponentName admin, in String callerPackage, String packageName,
String permission, int grantState);
- int getPermissionGrantState(in ComponentName admin, String packageName, String permission);
+ int getPermissionGrantState(in ComponentName admin, in String callerPackage, String packageName, String permission);
boolean isProvisioningAllowed(String action, String packageName);
int checkProvisioningPreCondition(String action, String packageName);
- void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList);
- List<String> getKeepUninstalledPackages(in ComponentName admin);
+ void setKeepUninstalledPackages(in ComponentName admin, in String callerPackage, in List<String> packageList);
+ List<String> getKeepUninstalledPackages(in ComponentName admin, in String callerPackage);
boolean isManagedProfile(in ComponentName admin);
boolean isSystemOnlyUser(in ComponentName admin);
String getWifiMacAddress(in ComponentName admin);
diff --git a/core/java/android/app/admin/SystemUpdateInfo.java b/core/java/android/app/admin/SystemUpdateInfo.java
index 0937f3c..6bb9f2d 100644
--- a/core/java/android/app/admin/SystemUpdateInfo.java
+++ b/core/java/android/app/admin/SystemUpdateInfo.java
@@ -16,6 +16,7 @@
package android.app.admin;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.os.Build;
import android.os.Parcel;
@@ -25,41 +26,86 @@
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* A class containing information about a pending system update.
*/
public final class SystemUpdateInfo implements Parcelable {
- private static final String ATTR_RECEIVED_TIME = "mReceivedTime";
- // Tag used to store original build fingerprint to detect when the update is applied.
- private static final String ATTR_ORIGINAL_BUILD = "originalBuild";
- private final long mReceivedTime;
- private SystemUpdateInfo(long receivedTime) {
+ /**
+ * Represents it is unknown whether the system update is a security patch.
+ */
+ public static final int SECURITY_PATCH_STATE_UNKNOWN = 0;
+
+ /**
+ * Represents the system update is not a security patch.
+ */
+ public static final int SECURITY_PATCH_STATE_FALSE = 1;
+
+ /**
+ * Represents the system update is a security patch.
+ */
+ public static final int SECURITY_PATCH_STATE_TRUE = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SECURITY_PATCH_STATE_FALSE, SECURITY_PATCH_STATE_TRUE, SECURITY_PATCH_STATE_UNKNOWN})
+ public @interface SecurityPatchState {}
+
+ private static final String ATTR_RECEIVED_TIME = "received-time";
+ private static final String ATTR_SECURITY_PATCH_STATE = "security-patch-state";
+ // Tag used to store original build fingerprint to detect when the update is applied.
+ private static final String ATTR_ORIGINAL_BUILD = "original-build";
+
+ private final long mReceivedTime;
+ @SecurityPatchState
+ private final int mSecurityPatchState;
+
+ private SystemUpdateInfo(long receivedTime, @SecurityPatchState int securityPatchState) {
this.mReceivedTime = receivedTime;
+ this.mSecurityPatchState = securityPatchState;
}
private SystemUpdateInfo(Parcel in) {
mReceivedTime = in.readLong();
+ mSecurityPatchState = in.readInt();
}
- /**
- * @hide
- */
+ /** @hide */
@Nullable
public static SystemUpdateInfo of(long receivedTime) {
- return receivedTime == -1 ? null : new SystemUpdateInfo(receivedTime);
+ return receivedTime == -1
+ ? null : new SystemUpdateInfo(receivedTime, SECURITY_PATCH_STATE_UNKNOWN);
+ }
+
+ /** @hide */
+ @Nullable
+ public static SystemUpdateInfo of(long receivedTime, boolean isSecurityPatch) {
+ return receivedTime == -1 ? null : new SystemUpdateInfo(receivedTime,
+ isSecurityPatch ? SECURITY_PATCH_STATE_TRUE : SECURITY_PATCH_STATE_FALSE);
}
/**
- * Get time when the update was first available.
- * @return time as given by {@link System#currentTimeMillis()}
+ * Gets time when the update was first available.
+ * @return Time as given by {@link System#currentTimeMillis()}
*/
public long getReceivedTime() {
return mReceivedTime;
}
+ /**
+ * Gets whether the update is a security patch.
+ * @return {@link #SECURITY_PATCH_STATE_FALSE}, {@link #SECURITY_PATCH_STATE_TRUE}, or
+ * {@link #SECURITY_PATCH_STATE_UNKNOWN}.
+ */
+ @SecurityPatchState
+ public int getSecurityPatchState() {
+ return mSecurityPatchState;
+ }
+
public static final Creator<SystemUpdateInfo> CREATOR =
new Creator<SystemUpdateInfo>() {
@Override
@@ -73,19 +119,16 @@
}
};
- /**
- * @hide
- */
+ /** @hide */
public void writeToXml(XmlSerializer out, String tag) throws IOException {
out.startTag(null, tag);
out.attribute(null, ATTR_RECEIVED_TIME, String.valueOf(mReceivedTime));
+ out.attribute(null, ATTR_SECURITY_PATCH_STATE, String.valueOf(mSecurityPatchState));
out.attribute(null, ATTR_ORIGINAL_BUILD , Build.FINGERPRINT);
out.endTag(null, tag);
}
- /**
- * @hide
- */
+ /** @hide */
@Nullable
public static SystemUpdateInfo readFromXml(XmlPullParser parser) {
// If an OTA has been applied (build fingerprint has changed), discard stale info.
@@ -95,7 +138,9 @@
}
final long receivedTime =
Long.parseLong(parser.getAttributeValue(null, ATTR_RECEIVED_TIME));
- return of(receivedTime);
+ final int securityPatchState =
+ Integer.parseInt(parser.getAttributeValue(null, ATTR_SECURITY_PATCH_STATE));
+ return new SystemUpdateInfo(receivedTime, securityPatchState);
}
@Override
@@ -106,11 +151,26 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(getReceivedTime());
+ dest.writeInt(getSecurityPatchState());
}
@Override
public String toString() {
- return String.format("SystemUpdateInfo (receivedTime = %d)", mReceivedTime);
+ return String.format("SystemUpdateInfo (receivedTime = %d, securityPatchState = %s)",
+ mReceivedTime, securityPatchStateToString(mSecurityPatchState));
+ }
+
+ private static String securityPatchStateToString(@SecurityPatchState int state) {
+ switch (state) {
+ case SECURITY_PATCH_STATE_FALSE:
+ return "false";
+ case SECURITY_PATCH_STATE_TRUE:
+ return "true";
+ case SECURITY_PATCH_STATE_UNKNOWN:
+ return "unknown";
+ default:
+ throw new IllegalArgumentException("Unrecognized security patch state: " + state);
+ }
}
@Override
@@ -118,11 +178,12 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SystemUpdateInfo that = (SystemUpdateInfo) o;
- return mReceivedTime == that.mReceivedTime;
+ return mReceivedTime == that.mReceivedTime
+ && mSecurityPatchState == that.mSecurityPatchState;
}
@Override
public int hashCode() {
- return Objects.hash(mReceivedTime);
+ return Objects.hash(mReceivedTime, mSecurityPatchState);
}
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index bad6325..45d9fb7 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -493,7 +493,7 @@
* <p class="note">Attempting to back up files in directories that are ignored by
* the backup system will have no effect. For example, if the app calls this method
* with a file inside the {@link #getNoBackupFilesDir()} directory, it will be ignored.
- * See {@link #onFullBackup(FullBackupDataOutput) for details on what directories
+ * See {@link #onFullBackup(FullBackupDataOutput)} for details on what directories
* are excluded from backups.
*
* @param file The file to be backed up. The file must exist and be readable by
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 80bc136..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;
@@ -136,6 +137,46 @@
*/
public static final String EXTRA_BACKUP_SERVICES_AVAILABLE = "backup_services_available";
+ /**
+ * If this flag is passed to {@link #requestBackup(String[], BackupObserver, int)},
+ * BackupManager will pass a blank old state to BackupAgents of requested packages.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_NON_INCREMENTAL_BACKUP = 1;
+
+ /**
+ * Use with {@link #requestBackup} to force backup of
+ * package meta data. Typically you do not need to explicitly request this be backed up as it is
+ * handled internally by the BackupManager. If you are requesting backups with
+ * FLAG_NON_INCREMENTAL, this package won't automatically be backed up and you have to
+ * explicitly request for its backup.
+ *
+ * @hide
+ */
+ @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;
@@ -369,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();
@@ -394,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
@@ -484,13 +556,34 @@
*/
@SystemApi
public int requestBackup(String[] packages, BackupObserver observer) {
+ return requestBackup(packages, observer, 0);
+ }
+
+ /**
+ * Request an immediate backup, providing an observer to which results of the backup operation
+ * will be published. The Android backup system will decide for each package whether it will
+ * be full app data backup or key/value-pair-based backup.
+ *
+ * <p>If this method returns {@link BackupManager#SUCCESS}, the OS will attempt to backup all
+ * provided packages using the remote transport.
+ *
+ * @param packages List of package names to backup.
+ * @param observer The {@link BackupObserver} to receive callbacks during the backup
+ * operation. Could be {@code null}.
+ * @param flags {@link #FLAG_NON_INCREMENTAL_BACKUP}.
+ * @return {@link BackupManager#SUCCESS} on success; nonzero on error.
+ * @throws IllegalArgumentException on null or empty {@code packages} param.
+ * @hide
+ */
+ @SystemApi
+ public int requestBackup(String[] packages, BackupObserver observer, int flags) {
checkServiceBinder();
if (sService != null) {
try {
BackupObserverWrapper observerWrapper = observer == null
? null
: new BackupObserverWrapper(mContext, observer);
- return sService.requestBackup(packages, observerWrapper);
+ return sService.requestBackup(packages, observerWrapper, flags);
} catch (RemoteException e) {
Log.e(TAG, "requestBackup() couldn't connect");
}
@@ -556,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 a320bbf..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.
*
@@ -357,7 +376,9 @@
* @param observer The {@link BackupObserver} to receive callbacks during the backup
* operation.
*
+ * @param flags {@link BackupManager#FLAG_NON_INCREMENTAL_BACKUP}.
+ *
* @return Zero on success; nonzero on error.
*/
- int requestBackup(in String[] packages, IBackupObserver observer);
+ int requestBackup(in String[] packages, IBackupObserver observer, int flags);
}
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/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index d3d02e5..a10de45 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -25,6 +25,7 @@
*/
interface ITrustManager {
void reportUnlockAttempt(boolean successful, int userId);
+ void reportUnlockLockout(int timeoutMs, int userId);
void reportEnabledTrustAgentsChanged(int userId);
void registerTrustListener(in ITrustListener trustListener);
void unregisterTrustListener(in ITrustListener trustListener);
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 0f5cb6f..a64a023 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -81,6 +81,26 @@
}
/**
+ * Reports that user {@param userId} has entered a temporary device lockout.
+ *
+ * This generally occurs when the user has unsuccessfully tried to unlock the device too many
+ * times. The user will then be unable to unlock the device until a set amount of time has
+ * elapsed.
+ *
+ * @param timeout The amount of time that needs to elapse, in milliseconds, until the user may
+ * attempt to unlock the device again.
+ *
+ * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ */
+ public void reportUnlockLockout(int timeoutMs, int userId) {
+ try {
+ mService.reportUnlockLockout(timeoutMs, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Reports that the list of enabled trust agents changed for user {@param userId}.
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
index 695994b..9d30771 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -18,6 +18,7 @@
import android.annotation.WorkerThread;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -99,6 +100,8 @@
* @param volumeUuid the UUID of the storage volume you're interested in, or
* {@code null} to specify the default internal storage.
* @param uid the UID you're interested in.
+ * @see ApplicationInfo#volumeUuid
+ * @see ApplicationInfo#uid
*/
@WorkerThread
public StorageStats queryStatsForUid(String volumeUuid, int uid) {
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index cd14469..6ba52b7 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -36,7 +36,6 @@
import android.os.ServiceManager;
import android.util.DisplayMetrics;
import android.util.SparseArray;
-import android.util.TypedValue;
import android.widget.RemoteViews;
import android.widget.RemoteViews.OnClickHandler;
@@ -381,15 +380,7 @@
// Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
// AppWidgetService, which doesn't have our context, hence we need to do the
// conversion here.
- appWidget.minWidth =
- TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics);
- appWidget.minHeight =
- TypedValue.complexToDimensionPixelSize(appWidget.minHeight, mDisplayMetrics);
- appWidget.minResizeWidth =
- TypedValue.complexToDimensionPixelSize(appWidget.minResizeWidth, mDisplayMetrics);
- appWidget.minResizeHeight =
- TypedValue.complexToDimensionPixelSize(appWidget.minResizeHeight, mDisplayMetrics);
-
+ appWidget.updateDimensions(mDisplayMetrics);
synchronized (mViews) {
v = mViews.get(appWidgetId);
}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 31e779f..077331e 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -31,7 +31,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.DisplayMetrics;
-import android.util.TypedValue;
import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetService;
@@ -760,7 +759,7 @@
}
for (AppWidgetProviderInfo info : providers.getList()) {
// Converting complex to dp.
- convertSizesToPixels(info);
+ info.updateDimensions(mDisplayMetrics);
}
return providers.getList();
} catch (RemoteException e) {
@@ -782,7 +781,7 @@
AppWidgetProviderInfo info = mService.getAppWidgetInfo(mPackageName, appWidgetId);
if (info != null) {
// Converting complex to dp.
- convertSizesToPixels(info);
+ info.updateDimensions(mDisplayMetrics);
}
return info;
} catch (RemoteException e) {
@@ -1072,16 +1071,16 @@
}
}
- private void convertSizesToPixels(AppWidgetProviderInfo info) {
- // Converting complex to dp.
- info.minWidth = TypedValue.complexToDimensionPixelSize(info.minWidth,
- mDisplayMetrics);
- info.minHeight = TypedValue.complexToDimensionPixelSize(info.minHeight,
- mDisplayMetrics);
- info.minResizeWidth = TypedValue.complexToDimensionPixelSize(info.minResizeWidth,
- mDisplayMetrics);
- info.minResizeHeight = TypedValue.complexToDimensionPixelSize(info.minResizeHeight,
- mDisplayMetrics);
+ /**
+ * Return {@code TRUE} if the default launcher supports
+ * {@link #requestPinAppWidget(ComponentName, PendingIntent)}
+ */
+ public boolean isRequestPinAppWidgetSupported() {
+ try {
+ return mService.isRequestPinAppWidgetSupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -1108,6 +1107,7 @@
*
* @see android.content.pm.ShortcutManager#isRequestPinShortcutSupported()
* @see android.content.pm.ShortcutManager#requestPinShortcut(ShortcutInfo, IntentSender)
+ * @see #isRequestPinAppWidgetSupported()
*
* @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
* service or when the user is locked.
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index b4d79b4..06fdb32 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -26,6 +26,8 @@
import android.os.Parcelable;
import android.content.ComponentName;
import android.os.UserHandle;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
/**
* Describes the meta data for an installed AppWidget provider. The fields in this class
@@ -379,6 +381,17 @@
}
/**
+ * @hide
+ */
+ public void updateDimensions(DisplayMetrics displayMetrics) {
+ // Converting complex to dp.
+ minWidth = TypedValue.complexToDimensionPixelSize(minWidth, displayMetrics);
+ minHeight = TypedValue.complexToDimensionPixelSize(minHeight, displayMetrics);
+ minResizeWidth = TypedValue.complexToDimensionPixelSize(minResizeWidth, displayMetrics);
+ minResizeHeight = TypedValue.complexToDimensionPixelSize(minResizeHeight, displayMetrics);
+ }
+
+ /**
* Parcelable.Creator that instantiates AppWidgetProviderInfo objects
*/
public static final Parcelable.Creator<AppWidgetProviderInfo> CREATOR
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 52cd2de..a37a0b3 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -51,9 +51,10 @@
// NOTE: The values should be same as those listed in the following file:
// hardware/libhardware/include/hardware/bt_av.h
public static final int SOURCE_CODEC_TYPE_SBC = 0;
- public static final int SOURCE_CODEC_TYPE_APTX = 1;
- public static final int SOURCE_CODEC_TYPE_APTX_HD = 2;
- public static final int SOURCE_CODEC_TYPE_LDAC = 3;
+ public static final int SOURCE_CODEC_TYPE_AAC = 1;
+ public static final int SOURCE_CODEC_TYPE_APTX = 2;
+ public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
+ public static final int SOURCE_CODEC_TYPE_LDAC = 4;
public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index c7c64c4..544b3b95 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -965,38 +965,6 @@
}
/**
- * Accept the incoming connection.
- */
- public boolean acceptIncomingConnect(BluetoothDevice device) {
- if (DBG) log("acceptIncomingConnect");
- if (mService != null && isEnabled()) {
- try {
- return mService.acceptIncomingConnect(device);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
- return false;
- }
-
- /**
- * Reject the incoming connection.
- */
- public boolean rejectIncomingConnect(BluetoothDevice device) {
- if (DBG) log("rejectIncomingConnect");
- if (mService != null) {
- try {
- return mService.rejectIncomingConnect(device);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
- return false;
- }
-
- /**
* Returns current audio state of Audio Gateway.
*
* Note: This is an internal function and shouldn't be exposed
@@ -1017,13 +985,15 @@
/**
* Sets whether audio routing is allowed.
*
+ * @param device remote device
+ * @param allowed if routing is allowed to the device
* Note: This is an internal function and shouldn't be exposed
*/
- public void setAudioRouteAllowed(boolean allowed) {
+ public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
if (VDBG) log("setAudioRouteAllowed");
if (mService != null && isEnabled()) {
try {
- mService.setAudioRouteAllowed(allowed);
+ mService.setAudioRouteAllowed(device, allowed);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
} else {
Log.w(TAG, "Proxy not attached to service");
@@ -1033,14 +1003,15 @@
/**
* Returns whether audio routing is allowed.
- *
+ * @param device remote device
+ * @return whether the command succeeded
* Note: This is an internal function and shouldn't be exposed
*/
- public boolean getAudioRouteAllowed() {
+ public boolean getAudioRouteAllowed(BluetoothDevice device) {
if (VDBG) log("getAudioRouteAllowed");
if (mService != null && isEnabled()) {
try {
- return mService.getAudioRouteAllowed();
+ return mService.getAudioRouteAllowed(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
} else {
Log.w(TAG, "Proxy not attached to service");
@@ -1054,15 +1025,16 @@
*
* It setup SCO channel with remote connected Handsfree AG device.
*
+ * @param device remote device
* @return <code>true</code> if command has been issued successfully;
* <code>false</code> otherwise;
* upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED}
* intent;
*/
- public boolean connectAudio() {
+ public boolean connectAudio(BluetoothDevice device) {
if (mService != null && isEnabled()) {
try {
- return mService.connectAudio();
+ return mService.connectAudio(device);
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
@@ -1078,15 +1050,16 @@
*
* It tears down the SCO channel from remote AG device.
*
+ * @param device remote device
* @return <code>true</code> if command has been issued successfully;
* <code>false</code> otherwise;
* upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED}
* intent;
*/
- public boolean disconnectAudio() {
+ public boolean disconnectAudio(BluetoothDevice device) {
if (mService != null && isEnabled()) {
try {
- return mService.disconnectAudio();
+ return mService.disconnectAudio(device);
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
diff --git a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
index a351bd2..e571b00 100644
--- a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
@@ -29,9 +29,6 @@
boolean connect(in BluetoothDevice device);
boolean disconnect(in BluetoothDevice device);
- boolean acceptIncomingConnect(in BluetoothDevice device);
- boolean rejectIncomingConnect(in BluetoothDevice device);
-
List<BluetoothDevice> getConnectedDevices();
List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
int getConnectionState(in BluetoothDevice device);
@@ -58,10 +55,10 @@
boolean getLastVoiceTagNumber(in BluetoothDevice device);
int getAudioState(in BluetoothDevice device);
- boolean connectAudio();
- boolean disconnectAudio();
- void setAudioRouteAllowed(boolean allowed);
- boolean getAudioRouteAllowed();
+ boolean connectAudio(in BluetoothDevice device);
+ boolean disconnectAudio(in BluetoothDevice device);
+ void setAudioRouteAllowed(in BluetoothDevice device, boolean allowed);
+ boolean getAudioRouteAllowed(in BluetoothDevice device);
Bundle getCurrentAgFeatures(in BluetoothDevice device);
}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index bc5e986..7096771 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -32,6 +32,8 @@
import android.text.style.URLSpan;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
+
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -762,6 +764,18 @@
static public ClipData newUri(ContentResolver resolver, CharSequence label,
Uri uri) {
Item item = new Item(uri);
+ String[] mimeTypes = getMimeTypes(resolver, uri);
+ return new ClipData(label, mimeTypes, item);
+ }
+
+ /**
+ * Finds all applicable MIME types for a given URI.
+ *
+ * @param resolver ContentResolver used to get information about the URI.
+ * @param uri The URI.
+ * @return Returns an array of MIME types.
+ */
+ private static String[] getMimeTypes(ContentResolver resolver, Uri uri) {
String[] mimeTypes = null;
if ("content".equals(uri.getScheme())) {
String realType = resolver.getType(uri);
@@ -769,7 +783,7 @@
if (realType != null) {
if (mimeTypes == null) {
mimeTypes = new String[] { realType };
- } else {
+ } else if (!ArrayUtils.contains(mimeTypes, realType)) {
String[] tmp = new String[mimeTypes.length + 1];
tmp[0] = realType;
System.arraycopy(mimeTypes, 0, tmp, 1, mimeTypes.length);
@@ -780,7 +794,7 @@
if (mimeTypes == null) {
mimeTypes = MIMETYPES_TEXT_URILIST;
}
- return new ClipData(label, mimeTypes, item);
+ return mimeTypes;
}
/**
@@ -811,8 +825,8 @@
* Add a new Item to the overall ClipData container.
* <p> This method will <em>not</em> update the list of available MIME types in the
* {@link ClipDescription}. It should be used only when adding items which do not add new
- * MIME types to this clip. If this is not the case, {@link #ClipData(CharSequence, String[],
- * Item)} should be used with a complete list of MIME types.
+ * MIME types to this clip. If this is not the case, use {@link #addItem(Item, ContentResolver)}
+ * or call {@link #ClipData(CharSequence, String[], Item)} with a complete list of MIME types.
* @param item Item to be added.
*/
public void addItem(Item item) {
@@ -822,6 +836,32 @@
mItems.add(item);
}
+ /**
+ * Add a new Item to the overall ClipData container.
+ * <p> Unlike {@link #addItem(Item)}, this method will update the list of available MIME types
+ * in the {@link ClipDescription}.
+ * @param item Item to be added.
+ * @param resolver ContentResolver used to get information about the URI possibly contained in
+ * the item.
+ */
+ public void addItem(Item item, ContentResolver resolver) {
+ addItem(item);
+
+ if (item.getHtmlText() != null) {
+ mClipDescription.addMimeTypes(MIMETYPES_TEXT_HTML);
+ } else if (item.getText() != null) {
+ mClipDescription.addMimeTypes(MIMETYPES_TEXT_PLAIN);
+ }
+
+ if (item.getIntent() != null) {
+ mClipDescription.addMimeTypes(MIMETYPES_TEXT_INTENT);
+ }
+
+ if (item.getUri() != null) {
+ mClipDescription.addMimeTypes(getMimeTypes(resolver, item.getUri()));
+ }
+ }
+
/** @hide */
public Bitmap getIcon() {
return mIcon;
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index 461d1e0..b33a915a 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -22,6 +22,7 @@
import android.text.TextUtils;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Meta-data describing the contents of a {@link ClipData}. Provides enough
@@ -89,7 +90,7 @@
final CharSequence mLabel;
- final String[] mMimeTypes;
+ private final ArrayList<String> mMimeTypes;
private PersistableBundle mExtras;
/**
@@ -103,7 +104,7 @@
throw new NullPointerException("mimeTypes is null");
}
mLabel = label;
- mMimeTypes = mimeTypes;
+ mMimeTypes = new ArrayList<String>(Arrays.asList(mimeTypes));
}
/**
@@ -111,7 +112,7 @@
*/
public ClipDescription(ClipDescription o) {
mLabel = o.mLabel;
- mMimeTypes = o.mMimeTypes;
+ mMimeTypes = new ArrayList<String>(o.mMimeTypes);
}
/**
@@ -155,8 +156,9 @@
* matches the desired MIME type, else false.
*/
public boolean hasMimeType(String mimeType) {
- for (int i=0; i<mMimeTypes.length; i++) {
- if (compareMimeTypes(mMimeTypes[i], mimeType)) {
+ final int size = mMimeTypes.size();
+ for (int i=0; i<size; i++) {
+ if (compareMimeTypes(mMimeTypes.get(i), mimeType)) {
return true;
}
}
@@ -173,12 +175,13 @@
*/
public String[] filterMimeTypes(String mimeType) {
ArrayList<String> array = null;
- for (int i=0; i<mMimeTypes.length; i++) {
- if (compareMimeTypes(mMimeTypes[i], mimeType)) {
+ final int size = mMimeTypes.size();
+ for (int i=0; i<size; i++) {
+ if (compareMimeTypes(mMimeTypes.get(i), mimeType)) {
if (array == null) {
array = new ArrayList<String>();
}
- array.add(mMimeTypes[i]);
+ array.add(mMimeTypes.get(i));
}
}
if (array == null) {
@@ -193,14 +196,26 @@
* Return the number of MIME types the clip is available in.
*/
public int getMimeTypeCount() {
- return mMimeTypes.length;
+ return mMimeTypes.size();
}
/**
* Return one of the possible clip MIME types.
*/
public String getMimeType(int index) {
- return mMimeTypes[index];
+ return mMimeTypes.get(index);
+ }
+
+ /**
+ * Add MIME types to the clip description.
+ */
+ void addMimeTypes(String[] mimeTypes) {
+ for (int i=0; i!=mimeTypes.length; i++) {
+ final String mimeType = mimeTypes[i];
+ if (!mMimeTypes.contains(mimeType)) {
+ mMimeTypes.add(mimeType);
+ }
+ }
}
/**
@@ -229,11 +244,12 @@
if (mMimeTypes == null) {
throw new NullPointerException("null mime types");
}
- if (mMimeTypes.length <= 0) {
+ final int size = mMimeTypes.size();
+ if (size <= 0) {
throw new IllegalArgumentException("must have at least 1 mime type");
}
- for (int i=0; i<mMimeTypes.length; i++) {
- if (mMimeTypes[i] == null) {
+ for (int i=0; i<size; i++) {
+ if (mMimeTypes.get(i) == null) {
throw new NullPointerException("mime type at " + i + " is null");
}
}
@@ -275,12 +291,13 @@
/** @hide */
public boolean toShortStringTypesOnly(StringBuilder b) {
boolean first = true;
- for (int i=0; i<mMimeTypes.length; i++) {
+ final int size = mMimeTypes.size();
+ for (int i=0; i<size; i++) {
if (!first) {
b.append(' ');
}
first = false;
- b.append(mMimeTypes[i]);
+ b.append(mMimeTypes.get(i));
}
return !first;
}
@@ -293,13 +310,13 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
TextUtils.writeToParcel(mLabel, dest, flags);
- dest.writeStringArray(mMimeTypes);
+ dest.writeStringList(mMimeTypes);
dest.writePersistableBundle(mExtras);
}
ClipDescription(Parcel in) {
mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- mMimeTypes = in.createStringArray();
+ mMimeTypes = in.createStringArrayList();
mExtras = in.readPersistableBundle();
}
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 7ed827c..3a87cb3 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -414,7 +414,11 @@
return (Boolean) value;
} catch (ClassCastException e) {
if (value instanceof CharSequence) {
- return Boolean.valueOf(value.toString());
+ // Note that we also check against 1 here because SQLite's internal representation
+ // for booleans is an integer with a value of 0 or 1. Without this check, boolean
+ // values obtained via DatabaseUtils#cursorRowToContentValues will always return
+ // false.
+ return Boolean.valueOf(value.toString()) || "1".equals(value);
} else if (value instanceof Number) {
return ((Number) value).intValue() != 0;
} else {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index af5e643..c7c680f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -34,8 +34,7 @@
import android.annotation.UserIdInt;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
-import android.app.LoadedApk;
-import android.app.admin.DevicePolicyManager;
+import android.app.Notification;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -63,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;
@@ -1672,7 +1672,7 @@
*
* @param intents An array of Intents to be started.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws ActivityNotFoundException
@@ -1700,7 +1700,7 @@
* @param intents An array of Intents to be started.
* @param options Additional options for how the Activity should be started.
* @param userHandle The user for whom to launch the activities
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws ActivityNotFoundException
@@ -1749,7 +1749,7 @@
* <var>flagsMask</var>
* @param extraFlags Always set to 0.
* @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details. If options
* have also been supplied by the IntentSender, options given here will
* override any that conflict with those given by the IntentSender.
@@ -2508,7 +2508,10 @@
* {@link ComponentName} of the actual service that was started is
* returned; else if the service does not exist null is returned.
*
- * @throws SecurityException
+ * @throws SecurityException If the caller does not permission to access the service
+ * or the service can not be found.
+ * @throws IllegalStateException If the application is in a state where the service
+ * can not be started (such as not in the foreground in a state when services are allowed).
*
* @see #stopService
* @see #bindService
@@ -2517,6 +2520,17 @@
public abstract ComponentName startService(Intent service);
/**
+ * Start a service directly into the "foreground service" state. Unlike {@link #startService},
+ * this method can be used from within background operations like broadcast receivers
+ * or scheduled jobs. The API entry point for this is in NotificationManager in order to
+ * preserve appropriate public package layering.
+ * @hide
+ */
+ @Nullable
+ public abstract ComponentName startServiceInForeground(Intent service,
+ int id, Notification notification);
+
+ /**
* Request that a given application service be stopped. If the service is
* not running, nothing happens. Otherwise it is stopped. Note that calls
* to startService() are not counted -- this stops the service no matter
@@ -2538,7 +2552,10 @@
* @return If there is a service matching the given Intent that is already
* running, then it is stopped and {@code true} is returned; else {@code false} is returned.
*
- * @throws SecurityException
+ * @throws SecurityException If the caller does not permission to access the service
+ * or the service can not be found.
+ * @throws IllegalStateException If the application is in a state where the service
+ * can not be started (such as not in the foreground in a state when services are allowed).
*
* @see #startService
*/
@@ -2547,9 +2564,18 @@
/**
* @hide like {@link #startService(Intent)} but for a specific user.
*/
+ @Nullable
public abstract ComponentName startServiceAsUser(Intent service, UserHandle user);
/**
+ * @hide like {@link #startServiceInForeground(Intent, int, Notification)}
+ * but for a specific user.
+ */
+ @Nullable
+ public abstract ComponentName startServiceInForegroundAsUser(Intent service,
+ int id, Notification notification, UserHandle user);
+
+ /**
* @hide like {@link #stopService(Intent)} but for a specific user.
*/
public abstract boolean stopServiceAsUser(Intent service, UserHandle user);
@@ -2591,7 +2617,8 @@
* {@code false} is returned if the connection is not made so you will not
* receive the service object.
*
- * @throws SecurityException
+ * @throws SecurityException If the caller does not permission to access the service
+ * or the service can not be found.
*
* @see #unbindService
* @see #startService
@@ -3320,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";
@@ -3716,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
*/
@@ -4221,6 +4253,20 @@
int flags) throws PackageManager.NameNotFoundException;
/**
+ * Return a new Context object for the given split name. The new Context has a ClassLoader and
+ * Resources object that can access the split's and all of its dependencies' code/resources.
+ * Each call to this method returns a new instance of a Context object;
+ * Context objects are not shared, however common state (ClassLoader, other Resources for
+ * the same split) may be so the Context itself can be fairly lightweight.
+ *
+ * @param splitName The name of the split to include, as declared in the split's
+ * <code>AndroidManifest.xml</code>.
+ * @return A {@link Context} with the given split's code and/or resources loaded.
+ */
+ public abstract Context createContextForSplit(String splitName)
+ throws PackageManager.NameNotFoundException;
+
+ /**
* Get the userId associated with this context
* @return user id
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index e437de0..546bfc4 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -16,10 +16,10 @@
package android.content;
-import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
+import android.app.Notification;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -623,6 +623,13 @@
return mBase.startService(service);
}
+ /** @hide */
+ @Override
+ public ComponentName startServiceInForeground(Intent service,
+ int id, Notification notification) {
+ return mBase.startServiceInForeground(service, id, notification);
+ }
+
@Override
public boolean stopService(Intent name) {
return mBase.stopService(name);
@@ -636,6 +643,13 @@
/** @hide */
@Override
+ public ComponentName startServiceInForegroundAsUser(Intent service,
+ int id, Notification notification, UserHandle user) {
+ return mBase.startServiceInForegroundAsUser(service, id, notification, user);
+ }
+
+ /** @hide */
+ @Override
public boolean stopServiceAsUser(Intent name, UserHandle user) {
return mBase.stopServiceAsUser(name, user);
}
@@ -811,6 +825,13 @@
/** @hide */
@Override
+ public Context createContextForSplit(String splitName)
+ throws PackageManager.NameNotFoundException {
+ return mBase.createContextForSplit(splitName);
+ }
+
+ /** @hide */
+ @Override
public int getUserId() {
return mBase.getUserId();
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8cc9a3a..249001b 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;
@@ -8334,7 +8357,7 @@
* @return Returns a bit mask of {@link #FILL_IN_ACTION},
* {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
* {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS},
- * {@link #FILL_IN_SELECTOR} and {@link #FILL_IN_CLIP_DATA indicating which fields were
+ * {@link #FILL_IN_SELECTOR} and {@link #FILL_IN_CLIP_DATA} indicating which fields were
* changed.
*/
@FillInFlags
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index 541ebbd..dd53eac 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -175,7 +175,7 @@
}
/**
- * Builder class for a @link SyncRequest. As you build your SyncRequest this class will also
+ * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also
* perform validation.
*/
public static class Builder {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 44dff00..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,6 +615,7 @@
CONFIG_SMALLEST_SCREEN_SIZE,
CONFIG_DENSITY,
CONFIG_LAYOUT_DIRECTION,
+ CONFIG_COLOR_MODE,
CONFIG_FONT_SCALE,
})
@Retention(RetentionPolicy.SOURCE)
@@ -671,6 +721,20 @@
public static final int CONFIG_LAYOUT_DIRECTION = 0x2000;
/**
* Bit in {@link #configChanges} that indicates that the activity
+ * 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_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}
+ * attribute. This is not a core resource configuration, but a higher-level value, so its
+ * constant starts at the high bits.
+ * @hide We do not want apps handling this yet, but we do need some kind of bit for diffs.
+ */
+ public static final int CONFIG_ASSETS_PATHS = 0x80000000;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
* can itself handle changes to the font scaling factor. Set from the
* {@link android.R.attr#configChanges} attribute. This is
* not a core resource configuration, but a higher-level value, so its
@@ -698,6 +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_COLOR_MODE, // COLOR_MODE
};
/**
@@ -753,7 +818,8 @@
* {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN},
* {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
* {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT},
- * {@link #CONFIG_DENSITY}, and {@link #CONFIG_LAYOUT_DIRECTION}.
+ * {@link #CONFIG_DENSITY}, {@link #CONFIG_LAYOUT_DIRECTION} and
+ * {@link #CONFIG_COLOR_MODE}.
* Set from the {@link android.R.attr#configChanges} attribute.
*/
public int configChanges;
@@ -856,6 +922,7 @@
resizeMode = orig.resizeMode;
requestedVrComponent = orig.requestedVrComponent;
rotationAnimation = orig.rotationAnimation;
+ colorMode = orig.colorMode;
}
/**
@@ -909,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
@@ -936,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:
@@ -1038,6 +1110,7 @@
dest.writeInt(resizeMode);
dest.writeString(requestedVrComponent);
dest.writeInt(rotationAnimation);
+ dest.writeInt(colorMode);
}
public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -1073,6 +1146,7 @@
resizeMode = source.readInt();
requestedVrComponent = source.readString();
rotationAnimation = source.readInt();
+ colorMode = source.readInt();
}
/**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 71071e1..ec74617 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -31,6 +31,7 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Printer;
+import android.util.SparseIntArray;
import com.android.internal.util.ArrayUtils;
@@ -551,6 +552,21 @@
public static final int PRIVATE_FLAG_BACKUP_IN_FOREGROUND = 1 << 12;
/**
+ * Value for {@link #privateFlags}: {@code true} means this application
+ * contains a static shared library. Defaults to {@code false} if unspecified.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_STATIC_SHARED_LIBRARY = 1 << 13;
+
+ /**
+ * Value for {@linl #privateFlags}: When set, the application will only have its splits loaded
+ * if they are required to load a component. Splits can be loaded on demand using the
+ * {@link Context#createContextForSplit(String)} API.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ISOLATED_SPLIT_LOADING = 1 << 14;
+
+ /**
* Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
* {@hide}
*/
@@ -580,8 +596,13 @@
*/
public int largestWidthLimitDp = 0;
- /** {@hide} */
+ /**
+ * UUID of the storage volume on which this application is being hosted. For
+ * apps hosted on the default internal storage at
+ * {@link Environment#getDataDirectory()}, the UUID value is {@code null}.
+ */
public String volumeUuid;
+
/** {@hide} */
public String scanSourceDir;
/** {@hide} */
@@ -600,8 +621,12 @@
public String publicSourceDir;
/**
- * Full paths to zero or more split APKs that, when combined with the base
- * APK defined in {@link #sourceDir}, form a complete application.
+ * The names of all installed split APKs, ordered lexicographically.
+ */
+ public String[] splitNames;
+
+ /**
+ * Full paths to zero or more split APKs, indexed by the same order as {@link #splitNames}.
*/
public String[] splitSourceDirs;
@@ -609,14 +634,35 @@
* Full path to the publicly available parts of {@link #splitSourceDirs},
* including resources and manifest. This may be different from
* {@link #splitSourceDirs} if an application is forward locked.
+ *
+ * @see #splitSourceDirs
*/
public String[] splitPublicSourceDirs;
/**
- * Full paths to the locations of extra resource packages this application
- * uses. This field is only used if there are extra resource packages,
- * otherwise it is null.
- *
+ * Maps the dependencies between split APKs. All splits implicitly depend on the base APK.
+ *
+ * Available since platform version O.
+ *
+ * Only populated if the application opts in to isolated split loading via the
+ * {@link android.R.attr.isolatedSplits} attribute in the <manifest> tag of the app's
+ * AndroidManifest.xml.
+ *
+ * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
+ * and {@link #splitPublicSourceDirs} arrays.
+ * Each key represents a split and its value is its parent split.
+ * Cycles do not exist because they are illegal and screened for during installation.
+ *
+ * May be null if no splits are installed, or if no dependencies exist between them.
+ * @hide
+ */
+ public SparseIntArray splitDependencies;
+
+ /**
+ * Full paths to the locations of extra resource packages (runtime overlays)
+ * this application uses. This field is only used if there are extra resource
+ * packages, otherwise it is null.
+ *
* {@hide}
*/
public String[] resourceDirs;
@@ -1051,8 +1097,10 @@
scanPublicSourceDir = orig.scanPublicSourceDir;
sourceDir = orig.sourceDir;
publicSourceDir = orig.publicSourceDir;
+ splitNames = orig.splitNames;
splitSourceDirs = orig.splitSourceDirs;
splitPublicSourceDirs = orig.splitPublicSourceDirs;
+ splitDependencies = orig.splitDependencies;
nativeLibraryDir = orig.nativeLibraryDir;
secondaryNativeLibraryDir = orig.secondaryNativeLibraryDir;
nativeLibraryRootDir = orig.nativeLibraryRootDir;
@@ -1091,6 +1139,7 @@
return 0;
}
+ @SuppressWarnings("unchecked")
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
dest.writeString(taskAffinity);
@@ -1108,8 +1157,10 @@
dest.writeString(scanPublicSourceDir);
dest.writeString(sourceDir);
dest.writeString(publicSourceDir);
+ dest.writeStringArray(splitNames);
dest.writeStringArray(splitSourceDirs);
dest.writeStringArray(splitPublicSourceDirs);
+ dest.writeSparseIntArray(splitDependencies);
dest.writeString(nativeLibraryDir);
dest.writeString(secondaryNativeLibraryDir);
dest.writeString(nativeLibraryRootDir);
@@ -1148,6 +1199,7 @@
}
};
+ @SuppressWarnings("unchecked")
private ApplicationInfo(Parcel source) {
super(source);
taskAffinity = source.readString();
@@ -1165,8 +1217,10 @@
scanPublicSourceDir = source.readString();
sourceDir = source.readString();
publicSourceDir = source.readString();
+ splitNames = source.readStringArray();
splitSourceDirs = source.readStringArray();
splitPublicSourceDirs = source.readStringArray();
+ splitDependencies = source.readSparseIntArray();
nativeLibraryDir = source.readString();
secondaryNativeLibraryDir = source.readString();
nativeLibraryRootDir = source.readString();
@@ -1356,6 +1410,22 @@
}
/**
+ * Returns true if the app has declared in its manifest that it wants its split APKs to be
+ * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
+ * @hide
+ */
+ public boolean requestsIsolatedSplitLoading() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isStaticSharedLibrary() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0;
+ }
+
+ /**
* @hide
*/
@Override protected ApplicationInfo getApplicationInfo() {
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 5cd15dd..53be953 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -45,6 +45,12 @@
public String processName;
/**
+ * The name of the split in which this component is declared.
+ * Null if the component was declared in the base APK.
+ */
+ public String splitName;
+
+ /**
* A string resource identifier (in the package's resources) containing
* a user-readable description of the component. From the "description"
* attribute or, if not set, 0.
@@ -53,7 +59,7 @@
/**
* Indicates whether or not this component may be instantiated. Note that this value can be
- * overriden by the one in its parent {@link ApplicationInfo}.
+ * overridden by the one in its parent {@link ApplicationInfo}.
*/
public boolean enabled = true;
@@ -83,6 +89,7 @@
super(orig);
applicationInfo = orig.applicationInfo;
processName = orig.processName;
+ splitName = orig.splitName;
descriptionRes = orig.descriptionRes;
enabled = orig.enabled;
exported = orig.exported;
@@ -163,6 +170,9 @@
if (processName != null && !packageName.equals(processName)) {
pw.println(prefix + "processName=" + processName);
}
+ if (splitName != null) {
+ pw.println(prefix + "splitName=" + splitName);
+ }
pw.println(prefix + "enabled=" + enabled + " exported=" + exported
+ " directBootAware=" + directBootAware);
if (descriptionRes != 0) {
@@ -195,6 +205,7 @@
applicationInfo.writeToParcel(dest, parcelableFlags);
}
dest.writeString(processName);
+ dest.writeString(splitName);
dest.writeInt(descriptionRes);
dest.writeInt(enabled ? 1 : 0);
dest.writeInt(exported ? 1 : 0);
@@ -208,6 +219,7 @@
applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
}
processName = source.readString();
+ splitName = source.readString();
descriptionRes = source.readInt();
enabled = (source.readInt() != 0);
exported = (source.readInt() != 0);
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/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 154ff85..ecc8cd6 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -21,6 +21,7 @@
import android.content.pm.IPackageInstallerSession;
import android.content.pm.PackageInstaller;
import android.content.pm.ParceledListSlice;
+import android.content.pm.VersionedPackage;
import android.content.IntentSender;
import android.graphics.Bitmap;
@@ -44,7 +45,7 @@
void registerCallback(IPackageInstallerCallback callback, int userId);
void unregisterCallback(IPackageInstallerCallback callback);
- void uninstall(String packageName, String callerPackageName, int flags,
+ void uninstall(in VersionedPackage versionedPackage, String callerPackageName, int flags,
in IntentSender statusReceiver, int userId);
void setPermissionsResult(int sessionId, boolean accepted);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 19cca8e..61531ae 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -47,6 +47,7 @@
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
@@ -63,6 +64,8 @@
void checkPackageStartable(String packageName, int userId);
boolean isPackageAvailable(String packageName, int userId);
PackageInfo getPackageInfo(String packageName, int flags, int userId);
+ PackageInfo getPackageInfoVersioned(in VersionedPackage versionedPackage,
+ int flags, int userId);
int getPackageUid(String packageName, int flags, int userId);
int[] getPackageGids(String packageName, int flags, int userId);
@@ -138,6 +141,8 @@
ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId);
+ ResolveInfo findPersistentPreferredActivity(in Intent intent, int userId);
+
boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId);
ParceledListSlice queryIntentActivities(in Intent intent,
@@ -229,18 +234,19 @@
void setApplicationCategoryHint(String packageName, int categoryHint, String callerPackageName);
/** @deprecated rawr, don't call AIDL methods directly! */
- void deletePackageAsUser(in String packageName, IPackageDeleteObserver observer,
- int userId, int flags);
+ void deletePackageAsUser(in String packageName, int versionCode,
+ IPackageDeleteObserver observer, int userId, int flags);
/**
* Delete a package for a specific user.
*
- * @param packageName The fully qualified name of the package to delete.
+ * @param versionedPackage The package to delete.
* @param observer a callback to use to notify when the package deletion in finished.
* @param userId the id of the user for whom to delete the package
* @param flags - possible values: {@link #DONT_DELETE_DATA}
*/
- void deletePackage(in String packageName, IPackageDeleteObserver2 observer, int userId, int flags);
+ void deletePackageVersioned(in VersionedPackage versionedPackage,
+ IPackageDeleteObserver2 observer, int userId, int flags);
String getInstallerPackageName(in String packageName);
@@ -586,4 +592,8 @@
List<String> getPreviousCodePaths(in String packageName);
int getInstallReason(String packageName, int userId);
+
+ ParceledListSlice getSharedLibraries(int flags, int userId);
+
+ boolean canRequestPackageInstalls(String packageName, int userId);
}
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index c90134a..03124be 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -71,5 +71,5 @@
void applyRestore(in byte[] payload, int user);
- boolean isRequestPinShortcutSupported(int user);
+ boolean isRequestPinItemSupported(int user, int requestType);
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 9d88cdd..a135d8f 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -18,6 +18,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
/**
* Information you can retrieve about a particular piece of test
@@ -44,8 +46,12 @@
public String publicSourceDir;
/**
- * Full paths to zero or more split APKs that, when combined with the base
- * APK defined in {@link #sourceDir}, form a complete application.
+ * The names of all installed split APKs, ordered lexicographically.
+ */
+ public String[] splitNames;
+
+ /**
+ * Full paths to zero or more split APKs, indexed by the same order as {@link #splitNames}.
*/
public String[] splitSourceDirs;
@@ -53,10 +59,31 @@
* Full path to the publicly available parts of {@link #splitSourceDirs},
* including resources and manifest. This may be different from
* {@link #splitSourceDirs} if an application is forward locked.
+ *
+ * @see #splitSourceDirs
*/
public String[] splitPublicSourceDirs;
/**
+ * Maps the dependencies between split APKs. All splits implicitly depend on the base APK.
+ *
+ * Available since platform version O.
+ *
+ * Only populated if the application opts in to isolated split loading via the
+ * {@link android.R.attr.isolatedSplits} attribute in the <manifest> tag of the app's
+ * AndroidManifest.xml.
+ *
+ * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
+ * and {@link #splitPublicSourceDirs} arrays.
+ * Each key represents a split and its value is its parent split.
+ * Cycles do not exist because they are illegal and screened for during installation.
+ *
+ * May be null if no splits are installed, or if no dependencies exist between them.
+ * @hide
+ */
+ public SparseIntArray splitDependencies;
+
+ /**
* Full path to a directory assigned to the package for its persistent data.
*/
public String dataDir;
@@ -88,8 +115,10 @@
targetPackage = orig.targetPackage;
sourceDir = orig.sourceDir;
publicSourceDir = orig.publicSourceDir;
+ splitNames = orig.splitNames;
splitSourceDirs = orig.splitSourceDirs;
splitPublicSourceDirs = orig.splitPublicSourceDirs;
+ splitDependencies = orig.splitDependencies;
dataDir = orig.dataDir;
deviceProtectedDataDir = orig.deviceProtectedDataDir;
credentialProtectedDataDir = orig.credentialProtectedDataDir;
@@ -114,8 +143,10 @@
dest.writeString(targetPackage);
dest.writeString(sourceDir);
dest.writeString(publicSourceDir);
+ dest.writeStringArray(splitNames);
dest.writeStringArray(splitSourceDirs);
dest.writeStringArray(splitPublicSourceDirs);
+ dest.writeSparseIntArray(splitDependencies);
dest.writeString(dataDir);
dest.writeString(deviceProtectedDataDir);
dest.writeString(credentialProtectedDataDir);
@@ -135,13 +166,16 @@
}
};
+ @SuppressWarnings("unchecked")
private InstrumentationInfo(Parcel source) {
super(source);
targetPackage = source.readString();
sourceDir = source.readString();
publicSourceDir = source.readString();
+ splitNames = source.readStringArray();
splitSourceDirs = source.readStringArray();
splitPublicSourceDirs = source.readStringArray();
+ splitDependencies = source.readSparseIntArray();
dataDir = source.readString();
deviceProtectedDataDir = source.readString();
credentialProtectedDataDir = source.readString();
@@ -156,8 +190,10 @@
ai.packageName = packageName;
ai.sourceDir = sourceDir;
ai.publicSourceDir = publicSourceDir;
+ ai.splitNames = splitNames;
ai.splitSourceDirs = splitSourceDirs;
ai.splitPublicSourceDirs = splitPublicSourceDirs;
+ ai.splitDependencies = splitDependencies;
ai.dataDir = dataDir;
ai.deviceProtectedDataDir = deviceProtectedDataDir;
ai.credentialProtectedDataDir = credentialProtectedDataDir;
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 cf873b0..999b34f 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -61,15 +61,18 @@
/**
* Class for retrieving a list of launchable activities for the current user and any associated
- * managed profiles. This is mainly for use by launchers. Apps can be queried for each user profile.
+ * managed profiles that are visible to the current user, which can be retrieved with
+ * {@link #getProfiles}. This is mainly for use by launchers.
+ *
+ * Apps can be queried for each user profile.
* Since the PackageManager will not deliver package broadcasts for other profiles, you can register
* for package changes here.
* <p>
* To watch for managed profiles being added or removed, register for the following broadcasts:
* {@link Intent#ACTION_MANAGED_PROFILE_ADDED} and {@link Intent#ACTION_MANAGED_PROFILE_REMOVED}.
* <p>
- * You can retrieve the list of profiles associated with this user with
- * {@link UserManager#getUserProfiles()}.
+ * Note as of Android O, apps on a managed profile are no longer allowed to access apps on the
+ * main profile. Apps can only access profiles returned by {@link #getProfiles()}.
*/
public class LauncherApps {
@@ -90,22 +93,40 @@
* @see #EXTRA_PIN_ITEM_REQUEST
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_CONFIRM_PIN_ITEM =
- "android.content.pm.action.CONFIRM_PIN_ITEM";
+ public static final String ACTION_CONFIRM_PIN_SHORTCUT =
+ "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
/**
- * An extra for {@link #ACTION_CONFIRM_PIN_ITEM} containing a
- * {@link ShortcutInfo} of the shortcut the publisher app asked to pin.
+ * Activity Action: For the default launcher to show the confirmation dialog to create
+ * a pinned app widget.
+ *
+ * <p>See the {@link android.appwidget.AppWidgetManager#requestPinAppWidget} javadoc for
+ * details.
+ *
+ * <p>
+ * Use {@link #getPinItemRequest(Intent)} to get a {@link PinItemRequest} object,
+ * and call {@link PinItemRequest#accept(Bundle)}
+ * if the user accepts. If the user doesn't accept, no further action is required.
+ *
+ * @see #EXTRA_PIN_ITEM_REQUEST
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CONFIRM_PIN_APPWIDGET =
+ "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
+
+ /**
+ * An extra for {@link #ACTION_CONFIRM_PIN_SHORTCUT} & {@link #ACTION_CONFIRM_PIN_APPWIDGET}
+ * containing a {@link PinItemRequest} of appropriate type asked to pin.
*
* <p>A helper function {@link #getPinItemRequest(Intent)} can be used
* instead of using this constant directly.
*
- * @see #ACTION_CONFIRM_PIN_ITEM
+ * @see #ACTION_CONFIRM_PIN_SHORTCUT
+ * @see #ACTION_CONFIRM_PIN_APPWIDGET
*/
public static final String EXTRA_PIN_ITEM_REQUEST =
"android.content.pm.extra.PIN_ITEM_REQUEST";
-
private Context mContext;
private ILauncherApps mService;
private PackageManager mPm;
@@ -376,6 +397,24 @@
}
/**
+ * Return a list of profiles that the caller can access via the {@link LauncherApps} APIs.
+ *
+ * <p>If the caller is running on a managed profile, it'll return only the current profile.
+ * Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
+ */
+ public List<UserHandle> getProfiles() {
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ if (um.isManagedProfile()) {
+ // If it's a managed profile, only return the current profile.
+ final List result = new ArrayList(1);
+ result.add(android.os.Process.myUserHandle());
+ return result;
+ } else {
+ return um.getUserProfiles();
+ }
+ }
+
+ /**
* Retrieves a list of launchable activities that match {@link Intent#ACTION_MAIN} and
* {@link Intent#CATEGORY_LAUNCHER}, for a specified user.
*
@@ -386,7 +425,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();
}
@@ -402,7 +442,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;
@@ -427,7 +468,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();
}
@@ -445,7 +487,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();
}
@@ -465,7 +508,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();
@@ -528,7 +572,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();
}
@@ -548,7 +592,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();
}
@@ -564,7 +608,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();
}
@@ -1182,8 +1226,19 @@
}
/**
- * Represents a "pin shortcut" request made by an app, which is sent with
- * an {@link #ACTION_CONFIRM_PIN_ITEM} intent to the default launcher app.
+ * Represents a "pin shortcut" or a "pin appwidget" request made by an app, which is sent with
+ * an {@link #ACTION_CONFIRM_PIN_SHORTCUT} or {@link #ACTION_CONFIRM_PIN_APPWIDGET} intent
+ * respectively to the default launcher app.
+ *
+ * <p>Note the launcher may receive a request to pin a shortcut that is already pinned, because
+ * the user may actually want to have multiple icons of the same shortcut on the launcher.
+ * The launcher can tell this case by calling {@link ShortcutInfo#isPinned()} on the shortcut
+ * returned by {@link #getShortcutInfo()}. In this case, calling {@link #accept()} is optional;
+ * even if the launcher does not call it, the shortcut is already pinned. Also in this case,
+ * the {@code options} argument to {@link #accept(Bundle)} will be ignored.
+ *
+ * <p>For AppWidget pin requests launcher should send back the appwidget id as an extra for
+ * {@link #accept(Bundle)} as {@link android.appwidget.AppWidgetManager#EXTRA_APPWIDGET_ID}.
*
* @see #EXTRA_PIN_ITEM_REQUEST
* @see #getPinItemRequest(Intent)
@@ -1249,8 +1304,13 @@
* {@link #REQUEST_TYPE_APPWIDGET} request.
*/
@Nullable
- public AppWidgetProviderInfo getAppWidgetProviderInfo() {
- return mAppWidgetInfo;
+ public AppWidgetProviderInfo getAppWidgetProviderInfo(Context context) {
+ if (mAppWidgetInfo != null) {
+ AppWidgetProviderInfo info = mAppWidgetInfo.clone();
+ info.updateDimensions(context.getResources().getDisplayMetrics());
+ return info;
+ }
+ return null;
}
/**
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index d40bab5..5d5696b 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -142,7 +142,7 @@
* {@link PackageManager#GET_INSTRUMENTATION} was set.
*/
public InstrumentationInfo[] instrumentation;
-
+
/**
* Array of all {@link android.R.styleable#AndroidManifestPermission
* <permission>} tags included under <manifest>,
@@ -150,7 +150,7 @@
* {@link PackageManager#GET_PERMISSIONS} was set.
*/
public PermissionInfo[] permissions;
-
+
/**
* Array of all {@link android.R.styleable#AndroidManifestUsesPermission
* <uses-permission>} tags included under <manifest>,
@@ -160,7 +160,7 @@
* by the system at install time.
*/
public String[] requestedPermissions;
-
+
/**
* Array of flags of all {@link android.R.styleable#AndroidManifestUsesPermission
* <uses-permission>} tags included under <manifest>,
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index db3f637..4de967c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -23,7 +24,6 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.app.ActivityManager;
-import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.graphics.Bitmap;
@@ -36,9 +36,11 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.annotation.IntRange;
import android.util.ExceptionUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
import java.io.Closeable;
import java.io.IOException;
@@ -256,8 +258,6 @@
*/
public static final int STATUS_FAILURE_INCOMPATIBLE = 7;
- private final Context mContext;
- private final PackageManager mPm;
private final IPackageInstaller mInstaller;
private final int mUserId;
private final String mInstallerPackageName;
@@ -265,10 +265,8 @@
private final ArrayList<SessionCallbackDelegate> mDelegates = new ArrayList<>();
/** {@hide} */
- public PackageInstaller(Context context, PackageManager pm, IPackageInstaller installer,
+ public PackageInstaller(IPackageInstaller installer,
String installerPackageName, int userId) {
- mContext = context;
- mPm = pm;
mInstaller = installer;
mInstallerPackageName = installerPackageName;
mUserId = userId;
@@ -413,10 +411,35 @@
* Uninstall the given package, removing it completely from the device. This
* method is only available to the current "installer of record" for the
* package.
+ *
+ * @param packageName The package to uninstall.
+ * @param statusReceiver Where to deliver the result.
*/
public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) {
+ uninstall(new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ statusReceiver);
+ }
+
+ /**
+ * Uninstall the given package with a specific version code, removing it
+ * completely from the device. This method is only available to the current
+ * "installer of record" for the package. If the version code of the package
+ * does not match the one passed in the versioned package argument this
+ * method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to
+ * uninstall the latest version of the package.
+ *
+ * @param versionedPackage The versioned package to uninstall.
+ * @param statusReceiver Where to deliver the result.
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
+ public void uninstall(@NonNull VersionedPackage versionedPackage,
+ @NonNull IntentSender statusReceiver) {
+ Preconditions.checkNotNull(versionedPackage, "versionedPackage cannot be null");
try {
- mInstaller.uninstall(packageName, mInstallerPackageName, 0, statusReceiver, mUserId);
+ mInstaller.uninstall(versionedPackage, mInstallerPackageName,
+ 0, statusReceiver, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index bc79f41..11830c2 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -24,14 +24,10 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.UserHandle;
-import android.text.BidiFormatter;
import android.text.Html;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Printer;
-import android.text.BidiFormatter;
-import android.text.TextPaint;
-import android.text.Html;
import java.text.Collator;
import java.util.Comparator;
@@ -50,31 +46,31 @@
* Public name of this item. From the "android:name" attribute.
*/
public String name;
-
+
/**
* Name of the package that this item is in.
*/
public String packageName;
-
+
/**
* A string resource identifier (in the package's resources) of this
* component's label. From the "label" attribute or, if not set, 0.
*/
public int labelRes;
-
+
/**
* The string provided in the AndroidManifest file, if any. You
* probably don't want to use this. You probably want
* {@link PackageManager#getApplicationLabel}
*/
public CharSequence nonLocalizedLabel;
-
+
/**
* A drawable resource identifier (in the package's resources) of this
* component's icon. From the "icon" attribute or, if not set, 0.
*/
public int icon;
-
+
/**
* A drawable resource identifier (in the package's resources) of this
* component's banner. From the "banner" attribute or, if not set, 0.
@@ -85,10 +81,10 @@
* A drawable resource identifier (in the package's resources) of this
* component's logo. Logos may be larger/wider than icons and are
* displayed by certain UI elements in place of a name or name/icon
- * combination. From the "logo" attribute or, if not set, 0.
+ * combination. From the "logo" attribute or, if not set, 0.
*/
public int logo;
-
+
/**
* Additional meta-data associated with this component. This field
* will only be filled in if you set the
@@ -124,10 +120,10 @@
* Retrieve the current textual label associated with this item. This
* will call back on the given PackageManager to load the label from
* the application.
- *
+ *
* @param pm A PackageManager from which the label can be loaded; usually
* the PackageManager from which you originally retrieved this item.
- *
+ *
* @return Returns a CharSequence containing the item's label. If the
* item does not have a label, its name is returned.
*/
@@ -146,7 +142,7 @@
}
return packageName;
}
-
+
/**
* Same as {@link #loadLabel(PackageManager)} with the addition that
* the returned label is safe for being presented in the UI since it
@@ -207,10 +203,10 @@
* Retrieve the current graphical icon associated with this item. This
* will call back on the given PackageManager to load the icon from
* the application.
- *
+ *
* @param pm A PackageManager from which the icon can be loaded; usually
* the PackageManager from which you originally retrieved this item.
- *
+ *
* @return Returns a Drawable containing the item's icon. If the
* item does not have an icon, the item's default icon is returned
* such as the default activity icon.
@@ -259,13 +255,13 @@
/**
* Retrieve the default graphical icon associated with this item.
- *
+ *
* @param pm A PackageManager from which the icon can be loaded; usually
* the PackageManager from which you originally retrieved this item.
- *
+ *
* @return Returns a Drawable containing the item's default icon
* such as the default activity icon.
- *
+ *
* @hide
*/
public Drawable loadDefaultIcon(PackageManager pm) {
@@ -291,10 +287,10 @@
* Retrieve the current graphical logo associated with this item. This
* will call back on the given PackageManager to load the logo from
* the application.
- *
+ *
* @param pm A PackageManager from which the logo can be loaded; usually
* the PackageManager from which you originally retrieved this item.
- *
+ *
* @return Returns a Drawable containing the item's logo. If the item
* does not have a logo, this method will return null.
*/
@@ -307,31 +303,31 @@
}
return loadDefaultLogo(pm);
}
-
+
/**
* Retrieve the default graphical logo associated with this item.
- *
+ *
* @param pm A PackageManager from which the logo can be loaded; usually
* the PackageManager from which you originally retrieved this item.
- *
+ *
* @return Returns a Drawable containing the item's default logo
* or null if no default logo is available.
- *
+ *
* @hide
*/
protected Drawable loadDefaultLogo(PackageManager pm) {
return null;
}
-
+
/**
* Load an XML resource attached to the meta-data of this item. This will
* retrieved the name meta-data entry, and if defined call back on the
* given PackageManager to load its XML file from the application.
- *
+ *
* @param pm A PackageManager from which the XML can be loaded; usually
* the PackageManager from which you originally retrieved this item.
* @param name Name of the meta-date you would like to load.
- *
+ *
* @return Returns an XmlPullParser you can use to parse the XML file
* assigned as the given meta-data. If the meta-data name is not defined
* or the XML resource could not be found, null is returned.
@@ -373,11 +369,11 @@
+ " banner=0x" + Integer.toHexString(banner));
}
}
-
+
protected void dumpBack(Printer pw, String prefix) {
// no back here
}
-
+
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeString(name);
dest.writeString(packageName);
@@ -389,7 +385,7 @@
dest.writeInt(banner);
dest.writeInt(showUserIcon);
}
-
+
protected PackageItemInfo(Parcel source) {
name = source.readString();
packageName = source.readString();
@@ -406,9 +402,9 @@
/**
* Get the ApplicationInfo for the application to which this item belongs,
* if available, otherwise returns null.
- *
+ *
* @return Returns the ApplicationInfo of this item, or null if not known.
- *
+ *
* @hide
*/
protected ApplicationInfo getApplicationInfo() {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 04e649c..184f1856 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -20,6 +20,7 @@
import android.annotation.CheckResult;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -1287,6 +1288,13 @@
public static final int DELETE_FAILED_ABORTED = -5;
/**
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} if the system failed to delete the package
+ * because the packge is a shared library used by other installed packages.
+ * {@hide} */
+ public static final int DELETE_FAILED_USED_SHARED_LIBRARY = -6;
+
+ /**
* Return code that is passed to the {@link IPackageMoveObserver} when the
* package has been successfully moved by the system.
*
@@ -1360,6 +1368,14 @@
public static final int MOVE_FAILED_DEVICE_ADMIN = -8;
/**
+ * Error code that is passed to the {@link IPackageMoveObserver} if system does not allow
+ * non-system apps to be moved to internal storage.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL = -9;
+
+ /**
* Flag parameter for {@link #movePackage} to indicate that
* the package should be moved to internal storage if its
* been installed on external media.
@@ -1428,6 +1444,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0;
/**
@@ -1438,6 +1455,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1;
/**
@@ -1449,6 +1467,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2;
/**
@@ -1460,6 +1479,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3;
/**
@@ -1473,6 +1493,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4;
/**
@@ -1854,6 +1875,20 @@
public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports telephony carrier restriction mechanism.
+ *
+ * <p>Devices declaring this feature must have an implementation of the
+ * {@link android.telephony.TelephonyManager#getAllowedCarriers} and
+ * {@link android.telephony.TelephonyManager#setAllowedCarriers}.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_CARRIERLOCK =
+ "android.hardware.telephony.carrierlock";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports connecting to USB devices
* as the USB host.
@@ -2127,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.
*/
@@ -2602,6 +2646,11 @@
public static final int NOTIFY_PACKAGE_USE_REASONS_COUNT = 8;
/**
+ * Constant for specifying the highest installed package version code.
+ */
+ public static final int VERSION_CODE_HIGHEST = -1;
+
+ /**
* Retrieve overall information about an application package that is
* installed on the system.
*
@@ -2649,7 +2698,58 @@
throws NameNotFoundException;
/**
- * @hide
+ * Retrieve overall information about an application package that is
+ * installed on the system. This method can be used for retrieving
+ * information about packages for which multiple versions can be
+ * installed at the time. Currently only packages hosting static shared
+ * libraries can have multiple installed versions. The method can also
+ * be used to get info for a package that has a single version installed
+ * by passing {@link #VERSION_CODE_HIGHEST} in the {@link VersionedPackage}
+ * constructor.
+ *
+ * @param versionedPackage The versioned packages for which to query.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_ACTIVITIES}, {@link #GET_CONFIGURATIONS},
+ * {@link #GET_GIDS}, {@link #GET_INSTRUMENTATION},
+ * {@link #GET_INTENT_FILTERS}, {@link #GET_META_DATA},
+ * {@link #GET_PERMISSIONS}, {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS}, {@link #GET_SERVICES},
+ * {@link #GET_SHARED_LIBRARY_FILES}, {@link #GET_SIGNATURES},
+ * {@link #GET_URI_PERMISSION_PATTERNS}, {@link #GET_UNINSTALLED_PACKAGES},
+ * {@link #MATCH_DISABLED_COMPONENTS}, {@link #MATCH_DISABLED_UNTIL_USED_COMPONENTS},
+ * {@link #MATCH_UNINSTALLED_PACKAGES}
+ * to modify the data returned.
+ *
+ * @return A PackageInfo object containing information about the
+ * package. If flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if the
+ * package is not found in the list of installed applications, the
+ * package information is retrieved from the list of uninstalled
+ * applications (which includes installed applications as well as
+ * applications with data directory i.e. applications which had been
+ * deleted with {@code DONT_DELETE_DATA} flag set).
+ * @throws NameNotFoundException if a package with the given name cannot be
+ * found on the system.
+ * @see #GET_ACTIVITIES
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_GIDS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_META_DATA
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
+ * @see #GET_RECEIVERS
+ * @see #GET_SERVICES
+ * @see #GET_SHARED_LIBRARY_FILES
+ * @see #GET_SIGNATURES
+ * @see #GET_URI_PERMISSION_PATTERNS
+ * @see #MATCH_DISABLED_COMPONENTS
+ * @see #MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ * @see #MATCH_UNINSTALLED_PACKAGES
+ */
+ public abstract PackageInfo getPackageInfo(VersionedPackage versionedPackage,
+ @PackageInfoFlags int flags) throws NameNotFoundException;
+
+ /**
* Retrieve overall information about an application package that is
* installed on the system.
*
@@ -2693,6 +2793,8 @@
* @see #MATCH_DISABLED_COMPONENTS
* @see #MATCH_DISABLED_UNTIL_USED_COMPONENTS
* @see #MATCH_UNINSTALLED_PACKAGES
+ *
+ * @hide
*/
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
public abstract PackageInfo getPackageInfoAsUser(String packageName,
@@ -3683,6 +3785,37 @@
public abstract String[] getSystemSharedLibraryNames();
/**
+ * Get a list of shared libraries on the device.
+ *
+ * @param flags To filter the libraries to return.
+ * @return The shared library list.
+ *
+ * @see #MATCH_FACTORY_ONLY
+ * @see #MATCH_KNOWN_PACKAGES
+ * @see #MATCH_ANY_USER
+ * @see #MATCH_UNINSTALLED_PACKAGES
+ */
+ public abstract @NonNull List<SharedLibraryInfo> getSharedLibraries(
+ @InstallFlags int flags);
+
+ /**
+ * Get a list of shared libraries on the device.
+ *
+ * @param flags To filter the libraries to return.
+ * @param userId The user to query for.
+ * @return The shared library list.
+ *
+ * @see #MATCH_FACTORY_ONLY
+ * @see #MATCH_KNOWN_PACKAGES
+ * @see #MATCH_ANY_USER
+ * @see #MATCH_UNINSTALLED_PACKAGES
+ *
+ * @hide
+ */
+ public abstract @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(
+ @InstallFlags int flags, @UserIdInt int userId);
+
+ /**
* Get the name of the package hosting the services shared library.
*
* @return The library host package.
@@ -4951,6 +5084,7 @@
*
* @hide
*/
+ @SystemApi
public abstract int getIntentVerificationStatusAsUser(String packageName, @UserIdInt int userId);
/**
@@ -4973,6 +5107,7 @@
*
* @hide
*/
+ @SystemApi
public abstract boolean updateIntentVerificationStatusAsUser(String packageName, int status,
@UserIdInt int userId);
@@ -4988,6 +5123,7 @@
*
* @hide
*/
+ @SystemApi
public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications(
String packageName);
@@ -5002,6 +5138,7 @@
*
* @hide
*/
+ @SystemApi
public abstract List<IntentFilter> getAllIntentFilters(String packageName);
/**
@@ -5015,6 +5152,7 @@
* @hide
*/
@TestApi
+ @SystemApi
public abstract String getDefaultBrowserPackageNameAsUser(@UserIdInt int userId);
/**
@@ -5029,6 +5167,7 @@
*
* @hide
*/
+ @SystemApi
public abstract boolean setDefaultBrowserPackageNameAsUser(String packageName,
@UserIdInt int userId);
@@ -5066,6 +5205,7 @@
* indicate that no callback is desired.
* @hide
*/
+ @RequiresPermission(Manifest.permission.DELETE_PACKAGES)
public abstract void deletePackage(String packageName, IPackageDeleteObserver observer,
@DeleteFlags int flags);
@@ -5084,11 +5224,11 @@
* @param userId The user Id
* @hide
*/
- @RequiresPermission(anyOf = {
+ @RequiresPermission(anyOf = {
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.INTERACT_ACROSS_USERS_FULL})
- public abstract void deletePackageAsUser(String packageName, IPackageDeleteObserver observer,
- @DeleteFlags int flags, @UserIdInt int userId);
+ public abstract void deletePackageAsUser(@NonNull String packageName,
+ IPackageDeleteObserver observer, @DeleteFlags int flags, @UserIdInt int userId);
/**
* Retrieve the package name of the application that installed a package. This identifies
@@ -5829,6 +5969,7 @@
case DELETE_FAILED_USER_RESTRICTED: return "DELETE_FAILED_USER_RESTRICTED";
case DELETE_FAILED_OWNER_BLOCKED: return "DELETE_FAILED_OWNER_BLOCKED";
case DELETE_FAILED_ABORTED: return "DELETE_FAILED_ABORTED";
+ case DELETE_FAILED_USED_SHARED_LIBRARY: return "DELETE_FAILED_USED_SHARED_LIBRARY";
default: return Integer.toString(status);
}
}
@@ -5842,6 +5983,7 @@
case DELETE_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_BLOCKED;
case DELETE_FAILED_OWNER_BLOCKED: return PackageInstaller.STATUS_FAILURE_BLOCKED;
case DELETE_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED;
+ case DELETE_FAILED_USED_SHARED_LIBRARY: return PackageInstaller.STATUS_FAILURE_CONFLICT;
default: return PackageInstaller.STATUS_FAILURE;
}
}
@@ -5917,4 +6059,21 @@
@TestApi
public abstract @InstallReason int getInstallReason(String packageName,
@NonNull UserHandle user);
+
+ /**
+ * Checks whether the calling package is allowed to request package installs through package
+ * installer. Apps are encouraged to call this api before launching the package installer via
+ * intent {@link android.content.Intent#ACTION_INSTALL_PACKAGE}. Starting from Android O, the
+ * user can explicitly choose what external sources they trust to install apps on the device.
+ * If this api returns false, the install request will be blocked by the package installer and
+ * a dialog will be shown to the user with an option to launch settings to change their
+ * preference. An application must target Android O or higher and declare permission
+ * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES} in order to use this api.
+ *
+ * @return true if the calling package is trusted by the user to request install packages on
+ * the device, false otherwise.
+ * @see {@link android.content.Intent#ACTION_INSTALL_PACKAGE}
+ * @see {@link android.provider.Settings#ACTION_MANAGE_EXTERNAL_SOURCES}
+ */
+ public abstract boolean canRequestPackageInstalls();
}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 2590a6b..a90b18a 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -138,9 +138,8 @@
* @param userId The user under which to check.
*
* @return An {@link ApplicationInfo} containing information about the
- * package.
- * @throws NameNotFoundException if a package with the given name cannot be
- * found on the system.
+ * package, or {@code null} if no application exists with that
+ * package name.
*/
public abstract ApplicationInfo getApplicationInfo(String packageName, int userId);
@@ -223,7 +222,49 @@
int userId);
/**
+ * Grants access to the package metadata for an ephemeral application.
+ * <p>
+ * When an ephemeral application explicitly tries to interact with a full
+ * install application [via an activity, service or provider that has been
+ * exposed using the {@code visibleToInstantApp} attribute], the normal
+ * application must be able to see metadata about the connecting ephemeral
+ * app. If the ephemeral application uses an implicit intent [ie action VIEW,
+ * category BROWSABLE], it remains hidden from the launched activity.
+ * <p>
+ * If the {@code sourceUid} is not for an ephemeral app or {@code targetUid}
+ * is not for a fully installed app, this method will be a no-op.
+ *
+ * @param userId the user
+ * @param intent the intent that triggered the grant
+ * @param targetAppId The app ID of the fully installed application
+ * @param ephemeralAppId The app ID of the ephemeral application
+ */
+ public abstract void grantEphemeralAccess(int userId, Intent intent,
+ int targetAppId, int ephemeralAppId);
+
+ /**
* @return The SetupWizard package name.
*/
public abstract String getSetupWizardPackageName();
+
+ public interface ExternalSourcesPolicy {
+
+ int USER_TRUSTED = 0; // User has trusted the package to install apps
+ int USER_BLOCKED = 1; // User has blocked the package to install apps
+ int USER_DEFAULT = 2; // Default code to use when user response is unavailable
+
+ /**
+ * Checks the user preference for whether a package is trusted to request installs through
+ * package installer
+ *
+ * @param packageName The package to check for
+ * @param uid the uid in which the package is running
+ * @return {@link USER_TRUSTED} if the user has trusted the package, {@link USER_BLOCKED}
+ * if user has blocked requests from the package, {@link USER_DEFAULT} if the user response
+ * is not yet available
+ */
+ int getPackageTrustedToInstallApps(String packageName, int uid);
+ }
+
+ public abstract void setExternalSourcesPolicy(ExternalSourcesPolicy policy);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 083e4cc6..7032cc0 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;
@@ -49,6 +49,9 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.split.SplitAssetDependencyLoader;
+import android.content.pm.split.SplitAssetLoader;
+import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -74,6 +77,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseIntArray;
import android.util.TypedValue;
import android.util.apk.ApkSignatureSchemeV2Verifier;
import android.util.jar.StrictJarFile;
@@ -106,6 +110,7 @@
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -155,6 +160,7 @@
private static final String TAG_MANIFEST = "manifest";
private static final String TAG_APPLICATION = "application";
+ private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
private static final String TAG_OVERLAY = "overlay";
private static final String TAG_KEY_SETS = "key-sets";
private static final String TAG_PERMISSION_GROUP = "permission-group";
@@ -178,6 +184,14 @@
private static final String TAG_EAT_COMMENT = "eat-comment";
private static final String TAG_PACKAGE = "package";
private static final String TAG_RESTRICT_UPDATE = "restrict-update";
+ private static final String TAG_USES_SPLIT = "uses-split";
+
+ /**
+ * Bit mask of all the valid bits that can be set in restartOnConfigChanges.
+ * @hide
+ */
+ private static final int RESTART_ON_CONFIG_CHANGES_MASK =
+ ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
// These are the tags supported by child packages
private static final Set<String> CHILD_PACKAGE_TAGS = new ArraySet<>();
@@ -345,6 +359,9 @@
/** Names of any split APKs, ordered by parsed splitName */
public final String[] splitNames;
+ /** Dependencies of any split APKs, ordered by parsed splitName */
+ public final String[] usesSplitNames;
+
/**
* Path where this package was found on disk. For monolithic packages
* this is path to single base APK file; for cluster packages this is
@@ -367,14 +384,17 @@
public final boolean multiArch;
public final boolean use32bitAbi;
public final boolean extractNativeLibs;
+ public final boolean isolatedSplits;
public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
- String[] splitCodePaths, int[] splitRevisionCodes) {
+ String[] usesSplitNames, String[] splitCodePaths,
+ int[] splitRevisionCodes) {
this.packageName = baseApk.packageName;
this.versionCode = baseApk.versionCode;
this.installLocation = baseApk.installLocation;
this.verifiers = baseApk.verifiers;
this.splitNames = splitNames;
+ this.usesSplitNames = usesSplitNames;
this.codePath = codePath;
this.baseCodePath = baseApk.codePath;
this.splitCodePaths = splitCodePaths;
@@ -385,6 +405,7 @@
this.multiArch = baseApk.multiArch;
this.use32bitAbi = baseApk.use32bitAbi;
this.extractNativeLibs = baseApk.extractNativeLibs;
+ this.isolatedSplits = baseApk.isolatedSplits;
}
public List<String> getAllCodePaths() {
@@ -404,6 +425,7 @@
public final String codePath;
public final String packageName;
public final String splitName;
+ public final String usesSplitName;
public final int versionCode;
public final int revisionCode;
public final int installLocation;
@@ -415,15 +437,17 @@
public final boolean multiArch;
public final boolean use32bitAbi;
public final boolean extractNativeLibs;
+ public final boolean isolatedSplits;
- public ApkLite(String codePath, String packageName, String splitName, int versionCode,
- int revisionCode, int installLocation, List<VerifierInfo> verifiers,
+ public ApkLite(String codePath, String packageName, String splitName, String usesSplitName,
+ int versionCode, int revisionCode, int installLocation, List<VerifierInfo> verifiers,
Signature[] signatures, Certificate[][] certificates, boolean coreApp,
boolean debuggable, boolean multiArch, boolean use32bitAbi,
- boolean extractNativeLibs) {
+ boolean extractNativeLibs, boolean isolatedSplits) {
this.codePath = codePath;
this.packageName = packageName;
this.splitName = splitName;
+ this.usesSplitName = usesSplitName;
this.versionCode = versionCode;
this.revisionCode = revisionCode;
this.installLocation = installLocation;
@@ -435,6 +459,7 @@
this.multiArch = multiArch;
this.use32bitAbi = use32bitAbi;
this.extractNativeLibs = extractNativeLibs;
+ this.isolatedSplits = isolatedSplits;
}
}
@@ -485,7 +510,7 @@
return isApkPath(file.getName());
}
- private static boolean isApkPath(String path) {
+ public static boolean isApkPath(String path) {
return path.endsWith(".apk");
}
@@ -731,23 +756,23 @@
public static PackageLite parsePackageLite(File packageFile, int flags)
throws PackageParserException {
if (packageFile.isDirectory()) {
- return parseClusterPackageLite(packageFile, flags, null);
+ return parseClusterPackageLite(packageFile, flags);
} else {
- return parseMonolithicPackageLite(packageFile, flags, null);
+ return parseMonolithicPackageLite(packageFile, flags);
}
}
- private static PackageLite parseMonolithicPackageLite(File packageFile, int flags,
- AssetManager cachedAssetManager) throws PackageParserException {
+ private static PackageLite parseMonolithicPackageLite(File packageFile, int flags)
+ throws PackageParserException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
- final ApkLite baseApk = parseApkLite(packageFile, flags, cachedAssetManager);
+ final ApkLite baseApk = parseApkLite(packageFile, flags);
final String packagePath = packageFile.getAbsolutePath();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- return new PackageLite(packagePath, baseApk, null, null, null);
+ return new PackageLite(packagePath, baseApk, null, null, null, null);
}
- private static PackageLite parseClusterPackageLite(File packageDir, int flags,
- AssetManager cachedAssetManager) throws PackageParserException {
+ private static PackageLite parseClusterPackageLite(File packageDir, int flags)
+ throws PackageParserException {
final File[] files = packageDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
@@ -761,7 +786,7 @@
final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
for (File file : files) {
if (isApkFile(file)) {
- final ApkLite lite = parseApkLite(file, flags, cachedAssetManager);
+ final ApkLite lite = parseApkLite(file, flags);
// Assert that all package names and version codes are
// consistent with the first one we encounter.
@@ -801,10 +826,12 @@
final int size = apks.size();
String[] splitNames = null;
+ String[] usesSplitNames = null;
String[] splitCodePaths = null;
int[] splitRevisionCodes = null;
if (size > 0) {
splitNames = new String[size];
+ usesSplitNames = new String[size];
splitCodePaths = new String[size];
splitRevisionCodes = new int[size];
@@ -812,13 +839,15 @@
Arrays.sort(splitNames, sSplitNameComparator);
for (int i = 0; i < size; i++) {
- splitCodePaths[i] = apks.get(splitNames[i]).codePath;
- splitRevisionCodes[i] = apks.get(splitNames[i]).revisionCode;
+ final ApkLite apk = apks.get(splitNames[i]);
+ usesSplitNames[i] = apk.usesSplitName;
+ splitCodePaths[i] = apk.codePath;
+ splitRevisionCodes[i] = apk.revisionCode;
}
}
final String codePath = packageDir.getAbsolutePath();
- return new PackageLite(codePath, baseApk, splitNames, splitCodePaths,
+ return new PackageLite(codePath, baseApk, splitNames, usesSplitNames, splitCodePaths,
splitRevisionCodes);
}
@@ -997,6 +1026,42 @@
}
}
+ private static SparseIntArray buildSplitDependencyTree(PackageLite pkg)
+ throws PackageParserException {
+ SparseIntArray splitDependencies = new SparseIntArray();
+ for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+ final String splitDependency = pkg.usesSplitNames[splitIdx];
+ if (splitDependency != null) {
+ final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+ if (depIdx < 0) {
+ throw new PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Split '" + pkg.splitNames[splitIdx] + "' requires split '"
+ + splitDependency + "', which is missing.");
+ }
+ splitDependencies.put(splitIdx, depIdx);
+ }
+ }
+
+ // Verify that there are no cycles.
+ final BitSet bitset = new BitSet();
+ for (int i = 0; i < splitDependencies.size(); i++) {
+ int splitIdx = splitDependencies.keyAt(i);
+
+ bitset.clear();
+ while (splitIdx != -1) {
+ if (bitset.get(splitIdx)) {
+ throw new PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Cycle detected in split dependencies.");
+ }
+ bitset.set(splitIdx);
+ splitIdx = splitDependencies.get(splitIdx, -1);
+ }
+ }
+ return splitDependencies.size() != 0 ? splitDependencies : null;
+ }
+
/**
* Parse all APKs contained in the given directory, treating them as a
* single package. This also performs sanity checking, such as requiring
@@ -1007,25 +1072,24 @@
* must be done separately in {@link #collectCertificates(Package, int)}.
*/
private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
- final AssetManager assets = newConfiguredAssetManager();
- final PackageLite lite = parseClusterPackageLite(packageDir, 0, assets);
-
+ final PackageLite lite = parseClusterPackageLite(packageDir, 0);
if (mOnlyCoreApps && !lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + packageDir);
}
+ // Build the split dependency tree.
+ SparseIntArray splitDependencies = null;
+ final SplitAssetLoader assetLoader;
+ if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
+ splitDependencies = buildSplitDependencyTree(lite);
+ assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+ } else {
+ assetLoader = new DefaultSplitAssetLoader(lite, flags);
+ }
+
try {
- // Load all splits into the AssetManager (base has already been loaded earlier)
- // so that resources can be overriden when parsing the manifests.
- loadApkIntoAssetManager(assets, lite.baseCodePath, flags);
-
- if (!ArrayUtils.isEmpty(lite.splitCodePaths)) {
- for (String path : lite.splitCodePaths) {
- loadApkIntoAssetManager(assets, path, flags);
- }
- }
-
+ final AssetManager assets = assetLoader.getBaseAssetManager();
final File baseApk = new File(lite.baseCodePath);
final Package pkg = parseBaseApk(baseApk, assets, flags);
if (pkg == null) {
@@ -1040,9 +1104,12 @@
pkg.splitRevisionCodes = lite.splitRevisionCodes;
pkg.splitFlags = new int[num];
pkg.splitPrivateFlags = new int[num];
+ pkg.applicationInfo.splitNames = pkg.splitNames;
+ pkg.applicationInfo.splitDependencies = splitDependencies;
for (int i = 0; i < num; i++) {
- parseSplitApk(pkg, i, assets, flags);
+ final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
+ parseSplitApk(pkg, i, splitAssets, flags);
}
}
@@ -1050,7 +1117,7 @@
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} finally {
- IoUtils.closeQuietly(assets);
+ IoUtils.closeQuietly(assetLoader);
}
}
@@ -1067,7 +1134,7 @@
@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
final AssetManager assets = newConfiguredAssetManager();
- final PackageLite lite = parseMonolithicPackageLite(apkFile, flags, assets);
+ final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (mOnlyCoreApps) {
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
@@ -1161,11 +1228,11 @@
final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
- Resources res = null;
+ final Resources res;
XmlResourceParser parser = null;
try {
res = new Resources(assets, mMetrics, null);
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
@@ -1218,7 +1285,7 @@
}
String tagName = parser.getName();
- if (tagName.equals("application")) {
+ if (tagName.equals(TAG_APPLICATION)) {
if (foundApp) {
if (RIGID_PARSER) {
outError[0] = "<manifest> has more than one <application>";
@@ -1366,6 +1433,11 @@
"No APK Signature Scheme v2 signature in ephemeral package " + apkPath,
e);
}
+ // Static shared libraries must use only the V2 signing scheme
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Static shared libs must use v2 signature scheme " + apkPath);
+ }
} catch (Exception e) {
// APK Signature Scheme v2 signature was found but did not verify
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
@@ -1496,7 +1568,7 @@
private static AssetManager newConfiguredAssetManager() {
AssetManager assetManager = new AssetManager();
- assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
return assetManager;
}
@@ -1511,17 +1583,12 @@
*/
public static ApkLite parseApkLite(File apkFile, int flags)
throws PackageParserException {
- return parseApkLite(apkFile, flags, null);
- }
-
- private static ApkLite parseApkLite(File apkFile, int flags,
- @Nullable AssetManager cachedAssetManager) throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
AssetManager assets = null;
XmlResourceParser parser = null;
try {
- assets = cachedAssetManager == null ? newConfiguredAssetManager() : cachedAssetManager;
+ assets = newConfiguredAssetManager();
int cookie = assets.addAssetPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
@@ -1531,7 +1598,6 @@
final DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
- final Resources res = new Resources(assets, metrics, null);
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
final Signature[] signatures;
@@ -1553,16 +1619,14 @@
}
final AttributeSet attrs = parser;
- return parseApkLite(apkPath, res, parser, attrs, flags, signatures, certificates);
+ return parseApkLite(apkPath, parser, attrs, flags, signatures, certificates);
} catch (XmlPullParserException | IOException | RuntimeException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to parse " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
- if (cachedAssetManager == null) {
- IoUtils.closeQuietly(assets);
- }
+ IoUtils.closeQuietly(assets);
}
}
@@ -1640,9 +1704,9 @@
(splitName != null) ? splitName.intern() : splitName);
}
- private static ApkLite parseApkLite(String codePath, Resources res, XmlPullParser parser,
- AttributeSet attrs, int flags, Signature[] signatures, Certificate[][] certificates)
- throws IOException, XmlPullParserException, PackageParserException {
+ private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
+ int flags, Signature[] signatures, Certificate[][] certificates)
+ throws IOException, XmlPullParserException, PackageParserException {
final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
@@ -1653,6 +1717,8 @@
boolean multiArch = false;
boolean use32bitAbi = false;
boolean extractNativeLibs = true;
+ boolean isolatedSplits = false;
+ String usesSplitName = null;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
final String attr = attrs.getAttributeName(i);
@@ -1665,6 +1731,8 @@
revisionCode = attrs.getAttributeIntValue(i, 0);
} else if (attr.equals("coreApp")) {
coreApp = attrs.getAttributeBooleanValue(i, false);
+ } else if (attr.equals("isolatedSplits")) {
+ isolatedSplits = attrs.getAttributeBooleanValue(i, false);
}
}
@@ -1679,14 +1747,16 @@
continue;
}
- if (parser.getDepth() == searchDepth && "package-verifier".equals(parser.getName())) {
- final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags);
+ if (parser.getDepth() != searchDepth) {
+ continue;
+ }
+
+ if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
+ final VerifierInfo verifier = parseVerifier(attrs);
if (verifier != null) {
verifiers.add(verifier);
}
- }
-
- if (parser.getDepth() == searchDepth && "application".equals(parser.getName())) {
+ } else if (TAG_APPLICATION.equals(parser.getName())) {
for (int i = 0; i < attrs.getAttributeCount(); ++i) {
final String attr = attrs.getAttributeName(i);
if ("debuggable".equals(attr)) {
@@ -1702,12 +1772,25 @@
extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
}
}
+ } else if (TAG_USES_SPLIT.equals(parser.getName())) {
+ if (usesSplitName != null) {
+ Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
+ continue;
+ }
+
+ usesSplitName = attrs.getAttributeValue(ANDROID_RESOURCES, "name");
+ if (usesSplitName == null) {
+ throw new PackageParserException(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<uses-split> tag requires 'android:name' attribute");
+ }
}
}
- return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
- revisionCode, installLocation, verifiers, signatures, certificates, coreApp,
- debuggable, multiArch, use32bitAbi, extractNativeLibs);
+ return new ApkLite(codePath, packageSplit.first, packageSplit.second, usesSplitName,
+ versionCode, revisionCode, installLocation, verifiers, signatures,
+ certificates, coreApp, debuggable, multiArch, use32bitAbi, extractNativeLibs,
+ isolatedSplits);
}
/**
@@ -1928,6 +2011,10 @@
pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_EPHEMERAL;
}
+ if (sa.getBoolean(com.android.internal.R.styleable.AndroidManifest_isolatedSplits, false)) {
+ pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
+ }
+
// Resource boolean are -1, so 1 means we don't know the value.
int supportsSmallScreens = 1;
int supportsNormalScreens = 1;
@@ -2390,7 +2477,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;
}
@@ -2401,9 +2488,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;
}
}
@@ -2561,6 +2649,52 @@
return fi;
}
+ private boolean parseUsesStaticLibrary(Package pkg, Resources res, XmlResourceParser parser,
+ String[] outError) throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary);
+
+ // Note: don't allow this value to be a reference to a resource that may change.
+ String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
+ final int version = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary_version, -1);
+ String certSha256 = sa.getNonResourceString(com.android.internal.R.styleable
+ .AndroidManifestUsesStaticLibrary_certDigest);
+ sa.recycle();
+
+ // Since an APK providing a static shared lib can only provide the lib - fail if malformed
+ if (lname == null || version < 0 || certSha256 == null) {
+ outError[0] = "Bad uses-static-library declaration name: " + lname + " version: "
+ + version + " certDigest" + certSha256;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ // Can depend only on one version of the same library
+ if (pkg.usesStaticLibraries != null && pkg.usesStaticLibraries.contains(lname)) {
+ outError[0] = "Depending on multiple versions of static library " + lname;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ lname = lname.intern();
+ // We allow ":" delimiters in the SHA declaration as this is the format
+ // emitted by the certtool making it easy for developers to copy/paste.
+ certSha256 = certSha256.replace(":", "").toLowerCase();
+ pkg.usesStaticLibraries = ArrayUtils.add(pkg.usesStaticLibraries, lname);
+ pkg.usesStaticLibrariesVersions = ArrayUtils.appendInt(
+ pkg.usesStaticLibrariesVersions, version);
+ pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String.class,
+ pkg.usesStaticLibrariesCertDigests, certSha256);
+
+ XmlUtils.skipCurrentTag(parser);
+
+ return true;
+ }
+
private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser,
@@ -3409,6 +3543,47 @@
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
+ } else if (tagName.equals("static-library")) {
+ sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestStaticLibrary);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ final String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestStaticLibrary_name);
+ final int version = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestStaticLibrary_version, -1);
+
+ sa.recycle();
+
+ // Since the app canot run without a static lib - fail if malformed
+ if (lname == null || version < 0) {
+ outError[0] = "Bad static-library declaration name: " + lname
+ + " version: " + version;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ if (owner.mSharedUserId != null) {
+ outError[0] = "sharedUserId not allowed in static shared library";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ if (owner.staticSharedLibName != null) {
+ outError[0] = "Multiple static-shared libs for package " + pkgName;
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ XmlUtils.skipCurrentTag(parser);
+ return false;
+ }
+
+ owner.staticSharedLibName = lname.intern();
+ owner.staticSharedLibVersion = version;
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY;
+
+ XmlUtils.skipCurrentTag(parser);
} else if (tagName.equals("library")) {
sa = res.obtainAttributes(parser,
@@ -3424,12 +3599,18 @@
if (lname != null) {
lname = lname.intern();
if (!ArrayUtils.contains(owner.libraryNames, lname)) {
- owner.libraryNames = ArrayUtils.add(owner.libraryNames, lname);
+ owner.libraryNames = ArrayUtils.add(
+ owner.libraryNames, lname);
}
}
XmlUtils.skipCurrentTag(parser);
+ } else if (tagName.equals("uses-static-library")) {
+ if (!parseUsesStaticLibrary(owner, res, parser, outError)) {
+ return false;
+ }
+
} else if (tagName.equals("uses-library")) {
sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestUsesLibrary);
@@ -3551,6 +3732,8 @@
continue;
}
+ ComponentInfo parsedComponent = null;
+
String tagName = parser.getName();
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, flags, outError, false,
@@ -3561,6 +3744,7 @@
}
owner.activities.add(a);
+ parsedComponent = a.info;
} else if (tagName.equals("receiver")) {
Activity a = parseActivity(owner, res, parser, flags, outError, true, false);
@@ -3570,6 +3754,7 @@
}
owner.receivers.add(a);
+ parsedComponent = a.info;
} else if (tagName.equals("service")) {
Service s = parseService(owner, res, parser, flags, outError);
@@ -3579,6 +3764,7 @@
}
owner.services.add(s);
+ parsedComponent = s.info;
} else if (tagName.equals("provider")) {
Provider p = parseProvider(owner, res, parser, flags, outError);
@@ -3588,6 +3774,7 @@
}
owner.providers.add(p);
+ parsedComponent = p.info;
} else if (tagName.equals("activity-alias")) {
Activity a = parseActivityAlias(owner, res, parser, flags, outError);
@@ -3597,6 +3784,7 @@
}
owner.activities.add(a);
+ parsedComponent = a.info;
} else if (parser.getName().equals("meta-data")) {
// note: application meta-data is stored off to the side, so it can
@@ -3608,6 +3796,11 @@
return false;
}
+ } else if (tagName.equals("uses-static-library")) {
+ if (!parseUsesStaticLibrary(owner, res, parser, outError)) {
+ return false;
+ }
+
} else if (tagName.equals("uses-library")) {
sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestUsesLibrary);
@@ -3658,6 +3851,14 @@
return false;
}
}
+
+ if (parsedComponent != null && parsedComponent.splitName == null) {
+ // If the loaded component did not specify a split, inherit the split name
+ // based on the split it is defined in.
+ // This is used to later load the correct split when starting this
+ // component.
+ parsedComponent.splitName = owner.splitNames[splitIndex];
+ }
}
return true;
@@ -3789,6 +3990,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)) {
@@ -3855,7 +4059,9 @@
a.info.maxRecents = sa.getInt(
R.styleable.AndroidManifestActivity_maxRecents,
ActivityManager.getDefaultAppRecentsLimitStatic());
- a.info.configChanges = sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0);
+ a.info.configChanges = getActivityConfigChanges(
+ sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0),
+ sa.getInt(R.styleable.AndroidManifestActivity_restartOnConfigChanges, 0));
a.info.softInputMode = sa.getInt(
R.styleable.AndroidManifestActivity_windowSoftInputMode, 0);
@@ -3885,6 +4091,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;
}
@@ -3905,6 +4116,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;
@@ -3931,10 +4145,10 @@
}
final boolean hasVisibleToEphemeral =
- sa.hasValue(R.styleable.AndroidManifestActivity_visibleToEphemeral);
+ sa.hasValue(R.styleable.AndroidManifestActivity_visibleToInstantApps);
final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
final boolean visibleToEphemeral = isEphemeral
- || sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToEphemeral, false);
+ || sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false);
if (visibleToEphemeral) {
a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
@@ -4046,16 +4260,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;
}
@@ -4083,6 +4294,17 @@
}
}
+ /**
+ * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
+ * @param restartOnConfigChanges The bit mask restartOnConfigChanges fetched from
+ * AndroidManifest.xml.
+ * @hide Exposed for unit testing only.
+ */
+ @TestApi
+ public static int getActivityConfigChanges(int configChanges, int restartOnConfigChanges) {
+ return configChanges | ((~restartOnConfigChanges) & RESTART_ON_CONFIG_CHANGES_MASK);
+ }
+
private void parseLayout(Resources res, AttributeSet attrs, Activity a) {
TypedArray sw = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestLayout);
@@ -4244,7 +4466,7 @@
}
}
- // TODO add visibleToInstantApp attribute to activity alias
+ // TODO add visibleToInstantApps attribute to activity alias
final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
final boolean visibleToEphemeral = isEphemeral
|| ((a.info.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) != 0);
@@ -4397,6 +4619,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(
@@ -4420,10 +4645,10 @@
}
final boolean hasVisibleToEphemeral =
- sa.hasValue(R.styleable.AndroidManifestProvider_visibleToEphemeral);
+ sa.hasValue(R.styleable.AndroidManifestProvider_visibleToInstantApps);
final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
final boolean visibleToEphemeral = isEphemeral
- || sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToEphemeral, false);
+ || sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
if (visibleToEphemeral) {
p.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
@@ -4693,6 +4918,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,
@@ -4731,10 +4959,10 @@
}
final boolean hasVisibleToEphemeral =
- sa.hasValue(R.styleable.AndroidManifestService_visibleToEphemeral);
+ sa.hasValue(R.styleable.AndroidManifestService_visibleToInstantApps);
final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
final boolean visibleToEphemeral = isEphemeral
- || sa.getBoolean(R.styleable.AndroidManifestService_visibleToEphemeral, false);
+ || sa.getBoolean(R.styleable.AndroidManifestService_visibleToInstantApps, false);
if (visibleToEphemeral) {
s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
@@ -4901,18 +5129,23 @@
return data;
}
- private static VerifierInfo parseVerifier(Resources res, XmlPullParser parser,
- AttributeSet attrs, int flags) {
- final TypedArray sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AndroidManifestPackageVerifier);
+ private static VerifierInfo parseVerifier(AttributeSet attrs) {
+ String packageName = null;
+ String encodedPublicKey = null;
- final String packageName = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPackageVerifier_name);
+ final int attrCount = attrs.getAttributeCount();
+ for (int i = 0; i < attrCount; i++) {
+ final int attrResId = attrs.getAttributeNameResource(i);
+ switch (attrResId) {
+ case com.android.internal.R.attr.name:
+ packageName = attrs.getAttributeValue(i);
+ break;
- final String encodedPublicKey = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPackageVerifier_publicKey);
-
- sa.recycle();
+ case com.android.internal.R.attr.publicKey:
+ encodedPublicKey = attrs.getAttributeValue(i);
+ break;
+ }
+ }
if (packageName == null || packageName.length() == 0) {
Slog.i(TAG, "verifier package name was null; skipping");
@@ -5167,6 +5400,10 @@
public String packageName;
+ // The package name declared in the manifest as the package can be
+ // renamed, for example static shared libs use synthetic package names.
+ public String manifestPackageName;
+
/** Names of any split APKs, ordered by parsed splitName */
public String[] splitNames;
@@ -5221,8 +5458,13 @@
public Package parentPackage;
public ArrayList<Package> childPackages;
+ public String staticSharedLibName = null;
+ public int staticSharedLibVersion = 0;
public ArrayList<String> libraryNames = null;
public ArrayList<String> usesLibraries = null;
+ public ArrayList<String> usesStaticLibraries = null;
+ public int[] usesStaticLibrariesVersions = null;
+ public String[] usesStaticLibrariesCertDigests = null;
public ArrayList<String> usesOptionalLibraries = null;
public String[] usesLibraryFiles = null;
@@ -5321,6 +5563,7 @@
public Package(String packageName) {
this.packageName = packageName;
+ this.manifestPackageName = packageName;
applicationInfo.packageName = packageName;
applicationInfo.uid = -1;
}
@@ -5639,6 +5882,7 @@
final ClassLoader boot = Object.class.getClassLoader();
packageName = dest.readString();
+ manifestPackageName = dest.readString();
splitNames = dest.readStringArray();
volumeUuid = dest.readString();
codePath = dest.readString();
@@ -5679,11 +5923,23 @@
childPackages = null;
}
+ staticSharedLibName = dest.readString();
+ staticSharedLibVersion = dest.readInt();
libraryNames = dest.createStringArrayList();
usesLibraries = dest.createStringArrayList();
usesOptionalLibraries = dest.createStringArrayList();
usesLibraryFiles = dest.readStringArray();
+ final int libCount = dest.readInt();
+ if (libCount > 0) {
+ usesStaticLibraries = new ArrayList<>(libCount);
+ dest.readStringList(usesStaticLibraries);
+ usesStaticLibrariesVersions = new int[libCount];
+ dest.readIntArray(usesStaticLibrariesVersions);
+ usesStaticLibrariesCertDigests = new String[libCount];
+ dest.readStringArray(usesStaticLibrariesCertDigests);
+ }
+
preferredActivityFilters = new ArrayList<>();
dest.readParcelableList(preferredActivityFilters, boot);
if (preferredActivityFilters.size() == 0) {
@@ -5769,6 +6025,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(packageName);
+ dest.writeString(manifestPackageName);
dest.writeStringArray(splitNames);
dest.writeString(volumeUuid);
dest.writeString(codePath);
@@ -5793,11 +6050,22 @@
dest.writeStringList(protectedBroadcasts);
dest.writeParcelable(parentPackage, flags);
dest.writeParcelableList(childPackages, flags);
+ dest.writeString(staticSharedLibName);
+ dest.writeInt(staticSharedLibVersion);
dest.writeStringList(libraryNames);
dest.writeStringList(usesLibraries);
dest.writeStringList(usesOptionalLibraries);
dest.writeStringArray(usesLibraryFiles);
+ if (ArrayUtils.isEmpty(usesStaticLibraries)) {
+ dest.writeInt(-1);
+ } else {
+ dest.writeInt(usesStaticLibraries.size());
+ dest.writeStringList(usesStaticLibraries);
+ dest.writeIntArray(usesStaticLibrariesVersions);
+ dest.writeStringArray(usesStaticLibrariesCertDigests);
+ }
+
dest.writeParcelableList(preferredActivityFilters, flags);
dest.writeStringList(mOriginalPackages);
@@ -6219,6 +6487,9 @@
&& p.usesLibraryFiles != null) {
return true;
}
+ if (p.staticSharedLibName != null) {
+ return true;
+ }
return false;
}
diff --git a/core/java/android/content/pm/SharedLibraryInfo.aidl b/core/java/android/content/pm/SharedLibraryInfo.aidl
new file mode 100644
index 0000000..56d7c832
--- /dev/null
+++ b/core/java/android/content/pm/SharedLibraryInfo.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+parcelable SharedLibraryInfo;
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
new file mode 100644
index 0000000..d79deb2
--- /dev/null
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class provides information for a shared library. There are
+ * three types of shared libraries: builtin - non-updatable part of
+ * the OS; dynamic - updatable backwards-compatible dynamically linked;
+ * static - updatable non backwards-compatible emulating static linking.
+ */
+public final class SharedLibraryInfo implements Parcelable {
+ /**
+ * Shared library type: this library is a part of the OS
+ * and cannot be updated or uninstalled.
+ * @hide
+ */
+ public static final int TYPE_BUILTIN = 0x1<<0;
+
+ /**
+ * Shared library type: this library is backwards-compatible, can
+ * be updated, and updates can be uninstalled. Clients link against
+ * the latest version of the library.
+ * @hide
+ */
+ public static final int TYPE_DYNAMIC = 0x1<<1;
+
+ /**
+ * Shared library type: this library is <strong>not</strong> backwards
+ * -compatible, can be updated and updates can be uninstalled. Clients
+ * link against a specific version of the library.
+ * @hide
+ */
+ public static final int TYPE_STATIC = 0x1<<2;
+
+ /**
+ * Constant for referring to an undefined version.
+ */
+ public static final int VERSION_UNDEFINED = -1;
+
+ private final String mName;
+ private final int mVersion;
+ private final int mType;
+ private final VersionedPackage mDeclaringPackage;
+ private final List<VersionedPackage> mDependentPackages;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param name The lib name.
+ * @param version The lib version if not builtin.
+ * @param type The lib type.
+ * @param declaringPackage The package that declares the library.
+ * @param dependentPackages The packages that depend on the library.
+ *
+ * @hide
+ */
+ public SharedLibraryInfo(String name, int version, int type,
+ VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages) {
+ mName = name;
+ mVersion = version;
+ mType = type;
+ mDeclaringPackage = declaringPackage;
+ mDependentPackages = dependentPackages;
+ }
+
+ private SharedLibraryInfo(Parcel parcel) {
+ this(parcel.readString(), parcel.readInt(), parcel.readInt(),
+ parcel.readParcelable(null), parcel.readArrayList(null));
+ }
+
+ /** @hide */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Gets the library name.
+ *
+ * @return The name.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the version of the library. For {@link #isStatic()} static} libraries
+ * this is the declared version and for {@link #isDynamic()} dynamic} and
+ * {@link #isBuiltin()} builtin} it is {@link #VERSION_UNDEFINED} as these
+ * are not versioned.
+ *
+ * @return The version.
+ */
+ public @IntRange(from = -1) int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * @return whether this library is builtin which means that it
+ * is a part of the OS and cannot be updated or uninstalled.
+ */
+ public boolean isBuiltin() {
+ return mType == TYPE_BUILTIN;
+ }
+
+ /**
+ * @return whether this library is dynamic which means that it
+ * is backwards-compatible, can be updated, and updates can be
+ * uninstalled. Clients link against the latest version of the
+ * library.
+ */
+ public boolean isDynamic() {
+ return mType == TYPE_DYNAMIC;
+ }
+
+ /**
+ * @return whether this library is dynamic which means that it
+ * is <strong>not</strong> backwards-compatible, can be updated
+ * and updates can be uninstalled. Clients link against a specific
+ * version of the library.
+ */
+ public boolean isStatic() {
+ return mType == TYPE_STATIC;
+ }
+
+ /**
+ * Gets the package that declares the library.
+ *
+ * @return The package declaring the library.
+ */
+ public @NonNull VersionedPackage getDeclaringPackage() {
+ return mDeclaringPackage;
+ }
+
+ /**
+ * Gets the packages that depend on the library.
+ *
+ * @return The dependent packages.
+ */
+ public @NonNull List<VersionedPackage> getDependentPackages() {
+ if (mDependentPackages == null) {
+ return Collections.emptyList();
+ }
+ return mDependentPackages;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "SharedLibraryInfo[name:" + mName + ", type:" + typeToString(mType)
+ + ", version:" + mVersion + (!getDependentPackages().isEmpty()
+ ? " has dependents" : "");
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mName);
+ parcel.writeInt(mVersion);
+ parcel.writeInt(mType);
+ parcel.writeParcelable(mDeclaringPackage, flags);
+ parcel.writeList(mDependentPackages);
+ }
+
+ private static String typeToString(int type) {
+ switch (type) {
+ case TYPE_BUILTIN: {
+ return "builtin";
+ }
+ case TYPE_DYNAMIC: {
+ return "dynamic";
+ }
+ case TYPE_STATIC: {
+ return "static";
+ }
+ default: {
+ return "unknown";
+ }
+ }
+ }
+
+ public static final Parcelable.Creator<SharedLibraryInfo> CREATOR =
+ new Parcelable.Creator<SharedLibraryInfo>() {
+ public SharedLibraryInfo createFromParcel(Parcel source) {
+ return new SharedLibraryInfo(source);
+ }
+
+ public SharedLibraryInfo[] newArray(int size) {
+ return new SharedLibraryInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 805054f..fb280a1 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -830,7 +830,8 @@
*/
public boolean isRequestPinShortcutSupported() {
try {
- return mService.isRequestPinShortcutSupported(injectMyUserId());
+ return mService.isRequestPinItemSupported(injectMyUserId(),
+ LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 4773c73..87a6d4a 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -74,4 +74,6 @@
public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
@NonNull AppWidgetProviderInfo appWidget, @Nullable IntentSender resultIntent,
int userId);
+
+ public abstract boolean isRequestPinItemSupported(int callingUserId, int requestType);
}
diff --git a/core/java/android/content/pm/VersionedPackage.aidl b/core/java/android/content/pm/VersionedPackage.aidl
new file mode 100644
index 0000000..43412a4
--- /dev/null
+++ b/core/java/android/content/pm/VersionedPackage.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+parcelable VersionedPackage;
diff --git a/core/java/android/content/pm/VersionedPackage.java b/core/java/android/content/pm/VersionedPackage.java
new file mode 100644
index 0000000..83e7815
--- /dev/null
+++ b/core/java/android/content/pm/VersionedPackage.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Encapsulates a package and its version code.
+ */
+public final class VersionedPackage implements Parcelable {
+ private final String mPackageName;
+ private final long mVersionCode;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntRange(from = PackageManager.VERSION_CODE_HIGHEST)
+ public @interface VersionCode{}
+
+ /**
+ * Creates a new instance. Use {@link PackageManager#VERSION_CODE_HIGHEST}
+ * to refer to the highest version code of this package.
+ * @param packageName The package name.
+ * @param versionCode The version code.
+ */
+ public VersionedPackage(@NonNull String packageName,
+ @VersionCode int versionCode) {
+ mPackageName = packageName;
+ mVersionCode = versionCode;
+ }
+
+ private VersionedPackage(Parcel parcel) {
+ mPackageName = parcel.readString();
+ mVersionCode = parcel.readLong();
+ }
+
+ /**
+ * Gets the package name.
+ *
+ * @return The package name.
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Gets the version code.
+ *
+ * @return The version code.
+ */
+ public @VersionCode long getVersionCode() {
+ return mVersionCode;
+ }
+
+ @Override
+ public String toString() {
+ return "VersionedPackage[" + mPackageName + "/" + mVersionCode + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mPackageName);
+ parcel.writeLong(mVersionCode);
+ }
+
+ public static final Creator<VersionedPackage> CREATOR = new Creator<VersionedPackage>() {
+ @Override
+ public VersionedPackage createFromParcel(Parcel source) {
+ return new VersionedPackage(source);
+ }
+
+ @Override
+ public VersionedPackage[] newArray(int size) {
+ return new VersionedPackage[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
new file mode 100644
index 0000000..99eb470
--- /dev/null
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -0,0 +1,100 @@
+/*
+ * 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.content.pm.split;
+
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+
+import android.content.pm.PackageParser;
+import android.content.res.AssetManager;
+import android.os.Build;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoUtils;
+
+/**
+ * Loads the base and split APKs into a single AssetManager.
+ * @hide
+ */
+public class DefaultSplitAssetLoader implements SplitAssetLoader {
+ private final String mBaseCodePath;
+ private final String[] mSplitCodePaths;
+ private final int mFlags;
+
+ private AssetManager mCachedAssetManager;
+
+ public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) {
+ mBaseCodePath = pkg.baseCodePath;
+ mSplitCodePaths = pkg.splitCodePaths;
+ mFlags = flags;
+ }
+
+ private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
+ throws PackageParser.PackageParserException {
+ if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) {
+ throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + apkPath);
+ }
+
+ if (assets.addAssetPath(apkPath) == 0) {
+ throw new PackageParser.PackageParserException(
+ INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + apkPath);
+ }
+ }
+
+ @Override
+ public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+ if (mCachedAssetManager != null) {
+ return mCachedAssetManager;
+ }
+
+ AssetManager assets = new AssetManager();
+ try {
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
+ loadApkIntoAssetManager(assets, mBaseCodePath, mFlags);
+
+ if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+ for (String apkPath : mSplitCodePaths) {
+ loadApkIntoAssetManager(assets, apkPath, mFlags);
+ }
+ }
+
+ mCachedAssetManager = assets;
+ assets = null;
+ return mCachedAssetManager;
+ } finally {
+ if (assets != null) {
+ IoUtils.closeQuietly(assets);
+ }
+ }
+ }
+
+ @Override
+ public AssetManager getSplitAssetManager(int splitIdx)
+ throws PackageParser.PackageParserException {
+ return getBaseAssetManager();
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (mCachedAssetManager != null) {
+ IoUtils.closeQuietly(mCachedAssetManager);
+ }
+ }
+}
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
new file mode 100644
index 0000000..4df90eb
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -0,0 +1,153 @@
+/*
+ * 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.content.pm.split;
+
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+
+import android.content.pm.PackageParser;
+import android.content.res.AssetManager;
+import android.os.Build;
+import android.util.SparseIntArray;
+
+import libcore.io.IoUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation
+ * is to be used when an application opts-in to isolated split loading.
+ * @hide
+ */
+public class SplitAssetDependencyLoader
+ extends SplitDependencyLoaderHelper<PackageParser.PackageParserException>
+ implements SplitAssetLoader {
+ private static final int BASE_ASSET_PATH_IDX = -1;
+ private final String mBasePath;
+ private final String[] mSplitNames;
+ private final String[] mSplitPaths;
+ private final int mFlags;
+
+ private String[] mCachedBasePaths;
+ private AssetManager mCachedBaseAssetManager;
+
+ private String[][] mCachedSplitPaths;
+ private AssetManager[] mCachedAssetManagers;
+
+ public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, SparseIntArray dependencies,
+ int flags) {
+ super(dependencies);
+ mBasePath = pkg.baseCodePath;
+ mSplitNames = pkg.splitNames;
+ mSplitPaths = pkg.splitCodePaths;
+ mFlags = flags;
+ mCachedBasePaths = null;
+ mCachedBaseAssetManager = null;
+ mCachedSplitPaths = new String[mSplitNames.length][];
+ mCachedAssetManagers = new AssetManager[mSplitNames.length];
+ }
+
+ @Override
+ protected boolean isSplitCached(int splitIdx) {
+ if (splitIdx != -1) {
+ return mCachedAssetManagers[splitIdx] != null;
+ }
+ return mCachedBaseAssetManager != null;
+ }
+
+ // Adds all non-code configuration splits for this split name. The split name is expected
+ // to represent a feature split.
+ private void addAllConfigSplits(String splitName, ArrayList<String> outAssetPaths) {
+ for (int i = 0; i < mSplitNames.length; i++) {
+ if (isConfigurationSplitOf(mSplitNames[i], splitName)) {
+ outAssetPaths.add(mSplitPaths[i]);
+ }
+ }
+ }
+
+ private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
+ throws PackageParser.PackageParserException {
+ final AssetManager assets = new AssetManager();
+ try {
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
+
+ for (String assetPath : assetPaths) {
+ if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 &&
+ !PackageParser.isApkPath(assetPath)) {
+ throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + assetPath);
+ }
+
+ if (assets.addAssetPath(assetPath) == 0) {
+ throw new PackageParser.PackageParserException(
+ INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + assetPath);
+ }
+ }
+ return assets;
+ } catch (Throwable e) {
+ IoUtils.closeQuietly(assets);
+ throw e;
+ }
+ }
+
+ @Override
+ protected void constructSplit(int splitIdx, int parentSplitIdx) throws
+ PackageParser.PackageParserException {
+ final ArrayList<String> assetPaths = new ArrayList<>();
+ if (splitIdx == BASE_ASSET_PATH_IDX) {
+ assetPaths.add(mBasePath);
+ addAllConfigSplits(null, assetPaths);
+ mCachedBasePaths = assetPaths.toArray(new String[assetPaths.size()]);
+ mCachedBaseAssetManager = createAssetManagerWithPaths(mCachedBasePaths, mFlags);
+ return;
+ }
+
+ if (parentSplitIdx == BASE_ASSET_PATH_IDX) {
+ Collections.addAll(assetPaths, mCachedBasePaths);
+ } else {
+ Collections.addAll(assetPaths, mCachedSplitPaths[parentSplitIdx]);
+ }
+
+ assetPaths.add(mSplitPaths[splitIdx]);
+ addAllConfigSplits(mSplitNames[splitIdx], assetPaths);
+ mCachedSplitPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
+ mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedSplitPaths[splitIdx],
+ mFlags);
+ }
+
+ @Override
+ public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+ loadDependenciesForSplit(BASE_ASSET_PATH_IDX);
+ return mCachedBaseAssetManager;
+ }
+
+ @Override
+ public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
+ loadDependenciesForSplit(idx);
+ return mCachedAssetManagers[idx];
+ }
+
+ @Override
+ public void close() throws Exception {
+ IoUtils.closeQuietly(mCachedBaseAssetManager);
+ for (AssetManager assets : mCachedAssetManagers) {
+ IoUtils.closeQuietly(assets);
+ }
+ }
+}
diff --git a/core/java/android/content/pm/split/SplitAssetLoader.java b/core/java/android/content/pm/split/SplitAssetLoader.java
new file mode 100644
index 0000000..108fb95
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitAssetLoader.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.split;
+
+import android.content.pm.PackageParser;
+import android.content.res.AssetManager;
+
+/**
+ * Simple interface for loading base Assets and Splits. Used by PackageParser when parsing
+ * split APKs.
+ *
+ * @hide
+ */
+public interface SplitAssetLoader extends AutoCloseable {
+ AssetManager getBaseAssetManager() throws PackageParser.PackageParserException;
+ AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException;
+}
diff --git a/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java b/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
new file mode 100644
index 0000000..b493480
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
@@ -0,0 +1,149 @@
+/*
+ * 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.content.pm.split;
+
+import android.annotation.Nullable;
+import android.util.IntArray;
+import android.util.SparseIntArray;
+
+/**
+ * A helper class that implements the dependency tree traversal for splits. Callbacks
+ * are implemented by subclasses to notify whether a split has already been constructed
+ * and is cached, and to actually create the split requested.
+ *
+ * This helper is meant to be subclassed so as to reduce the number of allocations
+ * needed to make use of it.
+ *
+ * All inputs and outputs are assumed to be indices into an array of splits.
+ *
+ * @hide
+ */
+public abstract class SplitDependencyLoaderHelper<E extends Exception> {
+ @Nullable private final SparseIntArray mDependencies;
+
+ /**
+ * Construct a new SplitDependencyLoaderHelper. Meant to be called from the
+ * subclass constructor.
+ * @param dependencies The dependency tree of splits. Can be null, which leads to
+ * just the implicit dependency of all splits on the base.
+ */
+ protected SplitDependencyLoaderHelper(@Nullable SparseIntArray dependencies) {
+ mDependencies = dependencies;
+ }
+
+ /**
+ * Traverses the dependency tree and constructs any splits that are not already
+ * cached. This routine short-circuits and skips the creation of splits closer to the
+ * root if they are cached, as reported by the subclass implementation of
+ * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
+ * implementation of {@link #constructSplit(int, int)}.
+ * @param splitIdx The index of the split to load. Can be -1, which represents the
+ * base Application.
+ */
+ protected void loadDependenciesForSplit(int splitIdx) throws E {
+ // Quick check before any allocations are done.
+ if (isSplitCached(splitIdx)) {
+ return;
+ }
+
+ final IntArray linearDependencies = new IntArray();
+ linearDependencies.add(splitIdx);
+
+ // Collect all the dependencies that need to be constructed.
+ // They will be listed from leaf to root.
+ while (splitIdx >= 0) {
+ splitIdx = mDependencies != null ? mDependencies.get(splitIdx, -1) : -1;
+ if (isSplitCached(splitIdx)) {
+ break;
+ }
+ linearDependencies.add(splitIdx);
+ }
+
+ // Visit each index, from right to left (root to leaf).
+ int parentIdx = splitIdx;
+ for (int i = linearDependencies.size() - 1; i >= 0; i--) {
+ final int idx = linearDependencies.get(i);
+ constructSplit(idx, parentIdx);
+ parentIdx = idx;
+ }
+ }
+
+ /**
+ * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
+ * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
+ * @param splitIdx The index of the split to check for in the cache.
+ * @return true if the split is cached and does not need to be constructed.
+ */
+ protected abstract boolean isSplitCached(int splitIdx);
+
+ /**
+ * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
+ * The result is expected to be cached by the subclass in its own structures.
+ * @param splitIdx The index of the split to construct. Can be -1, which represents the
+ * base Application.
+ * @param parentSplitIdx The index of the parent split. Can be -1, which represents the
+ * base Application.
+ * @throws E
+ */
+ protected abstract void constructSplit(int splitIdx, int parentSplitIdx) throws E;
+
+ /**
+ * Returns true if `splitName` represents a Configuration split of `featureSplitName`.
+ *
+ * A Configuration split's name is prefixed with the associated Feature split's name
+ * or the empty string if the split is for the base Application APK. It is then followed by the
+ * dollar sign character "$" and some unique string that should represent the configurations
+ * the split contains.
+ *
+ * Example:
+ * <table>
+ * <tr>
+ * <th>Feature split name</th>
+ * <th>Configuration split name: xhdpi</th>
+ * <th>Configuration split name: fr-rFR</th>
+ * </tr>
+ * <tr>
+ * <td>(base APK)</td>
+ * <td><code>$xhdpi</code></td>
+ * <td><code>$fr-rFR</code></td>
+ * </tr>
+ * <tr>
+ * <td><code>Extras</code></td>
+ * <td><code>Extras$xhdpi</code></td>
+ * <td><code>Extras$fr-rFR</code></td>
+ * </tr>
+ * </table>
+ *
+ * @param splitName The name of the split to check.
+ * @param featureSplitName The name of the Feature split. May be null or "" if checking
+ * the base Application APK.
+ * @return true if the splitName represents a Configuration split of featureSplitName.
+ */
+ protected static boolean isConfigurationSplitOf(String splitName, String featureSplitName) {
+ if (featureSplitName == null || featureSplitName.length() == 0) {
+ // We are looking for configuration splits of the base, which have some legacy support.
+ if (splitName.startsWith("config_")) {
+ return true;
+ } else if (splitName.startsWith("$")) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return splitName.startsWith(featureSplitName + "$");
+ }
+ }
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index db24ffe..6e3d343 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -759,7 +759,7 @@
int orientation, int touchscreen, int density, int keyboard,
int keyboardHidden, int navigation, int screenWidth, int screenHeight,
int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
- int screenLayout, int uiMode, int majorVersion);
+ int screenLayout, int uiMode, int colorMode, int majorVersion);
/**
* Retrieve the resource identifier for the given resource name.
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 48860f7..99fbee1 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -101,6 +101,68 @@
*/
public boolean userSetLocale;
+
+ /** 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 #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 COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0x0;
+ /**
+ * 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 COLOR_MODE_WIDE_COLOR_GAMUT_NO = 0x1;
+ /**
+ * 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 COLOR_MODE_WIDE_COLOR_GAMUT_YES = 0x2;
+
+ /** 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 #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
+ * indicating that it is unknown whether or not the screen is HDR.
+ */
+ public static final int COLOR_MODE_HDR_UNDEFINED = 0x0;
+ /**
+ * 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 COLOR_MODE_HDR_NO = 0x1 << COLOR_MODE_HDR_SHIFT;
+ /**
+ * 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 COLOR_MODE_HDR_YES = 0x2 << COLOR_MODE_HDR_SHIFT;
+
+ /** Constant for {@link #colorMode}: a value indicating that the color mode is undefined */
+ @SuppressWarnings("PointlessBitwiseExpression")
+ 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 #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} bits define the color gamut of
+ * the screen. They may be one of
+ * {@link #COLOR_MODE_WIDE_COLOR_GAMUT_NO} or {@link #COLOR_MODE_WIDE_COLOR_GAMUT_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 colorMode;
+
/** Constant for {@link #screenLayout}: bits that encode the size. */
public static final int SCREENLAYOUT_SIZE_MASK = 0x0f;
/** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK}
@@ -331,6 +393,9 @@
if ((diff & ActivityInfo.CONFIG_SCREEN_LAYOUT) != 0) {
list.add("CONFIG_SCREEN_LAYOUT");
}
+ if ((diff & ActivityInfo.CONFIG_COLOR_MODE) != 0) {
+ list.add("CONFIG_COLOR_MODE");
+ }
if ((diff & ActivityInfo.CONFIG_UI_MODE) != 0) {
list.add("CONFIG_UI_MODE");
}
@@ -346,6 +411,9 @@
if ((diff & ActivityInfo.CONFIG_FONT_SCALE) != 0) {
list.add("CONFIG_FONT_SCALE");
}
+ if ((diff & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
+ list.add("CONFIG_ASSETS_PATHS");
+ }
StringBuilder builder = new StringBuilder("{");
for (int i = 0, n = list.size(); i < n; i++) {
builder.append(list.get(i));
@@ -671,6 +739,21 @@
public int compatSmallestScreenWidthDp;
/**
+ * An undefined assetsSeq. This will not override an existing assetsSeq.
+ * @hide
+ */
+ public static final int ASSETS_SEQ_UNDEFINED = 0;
+
+ /**
+ * Internal counter that allows us to piggyback off the configuration change mechanism to
+ * signal to apps that the the assets for an Application have changed. A difference in these
+ * between two Configurations will yield a diff flag of
+ * {@link ActivityInfo#CONFIG_ASSETS_PATHS}.
+ * @hide
+ */
+ public int assetsSeq;
+
+ /**
* @hide Internal book-keeping.
*/
public int seq;
@@ -693,6 +776,7 @@
NATIVE_CONFIG_UI_MODE,
NATIVE_CONFIG_SMALLEST_SCREEN_SIZE,
NATIVE_CONFIG_LAYOUTDIR,
+ NATIVE_CONFIG_COLOR_MODE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface NativeConfig {}
@@ -729,6 +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 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
@@ -787,6 +873,7 @@
navigationHidden = o.navigationHidden;
orientation = o.orientation;
screenLayout = o.screenLayout;
+ colorMode = o.colorMode;
uiMode = o.uiMode;
screenWidthDp = o.screenWidthDp;
screenHeightDp = o.screenHeightDp;
@@ -795,6 +882,7 @@
compatScreenWidthDp = o.compatScreenWidthDp;
compatScreenHeightDp = o.compatScreenHeightDp;
compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp;
+ assetsSeq = o.assetsSeq;
seq = o.seq;
}
@@ -866,6 +954,20 @@
default: sb.append(" layoutLong=");
sb.append(screenLayout&SCREENLAYOUT_LONG_MASK); 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(colorMode &COLOR_MODE_HDR_MASK); 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(colorMode &COLOR_MODE_WIDE_COLOR_GAMUT_MASK); break;
+ }
switch (orientation) {
case ORIENTATION_UNDEFINED: sb.append(" ?orien"); break;
case ORIENTATION_LANDSCAPE: sb.append(" land"); break;
@@ -930,9 +1032,11 @@
case NAVIGATIONHIDDEN_YES: sb.append("/h"); break;
default: sb.append("/"); sb.append(navigationHidden); break;
}
+ if (assetsSeq != 0) {
+ sb.append(" as.").append(assetsSeq);
+ }
if (seq != 0) {
- sb.append(" s.");
- sb.append(seq);
+ sb.append(" s.").append(seq);
}
sb.append('}');
return sb.toString();
@@ -955,11 +1059,13 @@
navigationHidden = NAVIGATIONHIDDEN_UNDEFINED;
orientation = ORIENTATION_UNDEFINED;
screenLayout = SCREENLAYOUT_UNDEFINED;
+ colorMode = COLOR_MODE_UNDEFINED;
uiMode = UI_MODE_TYPE_UNDEFINED;
screenWidthDp = compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
densityDpi = DENSITY_DPI_UNDEFINED;
+ assetsSeq = ASSETS_SEQ_UNDEFINED;
seq = 0;
}
@@ -1089,6 +1195,23 @@
| (delta.screenLayout & SCREENLAYOUT_COMPAT_NEEDED);
}
+ 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.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)
&& uiMode != delta.uiMode) {
changed |= ActivityInfo.CONFIG_UI_MODE;
@@ -1130,6 +1253,10 @@
if (delta.compatSmallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
compatSmallestScreenWidthDp = delta.compatSmallestScreenWidthDp;
}
+ if (delta.assetsSeq != ASSETS_SEQ_UNDEFINED) {
+ changed |= ActivityInfo.CONFIG_ASSETS_PATHS;
+ assetsSeq = delta.assetsSeq;
+ }
if (delta.seq != 0) {
seq = delta.seq;
}
@@ -1234,6 +1361,19 @@
getScreenLayoutNoDirection(delta.screenLayout)) {
changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
}
+ if ((compareUndefined ||
+ (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.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) {
changed |= ActivityInfo.CONFIG_UI_MODE;
@@ -1254,6 +1394,10 @@
&& densityDpi != delta.densityDpi) {
changed |= ActivityInfo.CONFIG_DENSITY;
}
+ if ((compareUndefined || delta.assetsSeq != ASSETS_SEQ_UNDEFINED)
+ && assetsSeq != delta.assetsSeq) {
+ changed |= ActivityInfo.CONFIG_ASSETS_PATHS;
+ }
return changed;
}
@@ -1272,7 +1416,11 @@
*/
public static boolean needNewResources(@Config int configChanges,
@Config int interestingChanges) {
- return (configChanges & (interestingChanges|ActivityInfo.CONFIG_FONT_SCALE)) != 0;
+ // CONFIG_ASSETS_PATHS and CONFIG_FONT_SCALE are higher level configuration changes that
+ // all resources are subject to change with.
+ interestingChanges = interestingChanges | ActivityInfo.CONFIG_ASSETS_PATHS
+ | ActivityInfo.CONFIG_FONT_SCALE;
+ return (configChanges & interestingChanges) != 0;
}
/**
@@ -1337,6 +1485,7 @@
dest.writeInt(navigationHidden);
dest.writeInt(orientation);
dest.writeInt(screenLayout);
+ dest.writeInt(colorMode);
dest.writeInt(uiMode);
dest.writeInt(screenWidthDp);
dest.writeInt(screenHeightDp);
@@ -1345,6 +1494,7 @@
dest.writeInt(compatScreenWidthDp);
dest.writeInt(compatScreenHeightDp);
dest.writeInt(compatSmallestScreenWidthDp);
+ dest.writeInt(assetsSeq);
dest.writeInt(seq);
}
@@ -1370,6 +1520,7 @@
navigationHidden = source.readInt();
orientation = source.readInt();
screenLayout = source.readInt();
+ colorMode = source.readInt();
uiMode = source.readInt();
screenWidthDp = source.readInt();
screenHeightDp = source.readInt();
@@ -1378,6 +1529,7 @@
compatScreenWidthDp = source.readInt();
compatScreenHeightDp = source.readInt();
compatSmallestScreenWidthDp = source.readInt();
+ assetsSeq = source.readInt();
seq = source.readInt();
}
@@ -1450,6 +1602,8 @@
if (n != 0) return n;
n = this.orientation - that.orientation;
if (n != 0) return n;
+ n = this.colorMode - that.colorMode;
+ if (n != 0) return n;
n = this.screenLayout - that.screenLayout;
if (n != 0) return n;
n = this.uiMode - that.uiMode;
@@ -1461,6 +1615,8 @@
n = this.smallestScreenWidthDp - that.smallestScreenWidthDp;
if (n != 0) return n;
n = this.densityDpi - that.densityDpi;
+ if (n != 0) return n;
+ n = this.assetsSeq - that.assetsSeq;
//if (n != 0) return n;
return n;
}
@@ -1493,11 +1649,13 @@
result = 31 * result + navigationHidden;
result = 31 * result + orientation;
result = 31 * result + screenLayout;
+ result = 31 * result + colorMode;
result = 31 * result + uiMode;
result = 31 * result + screenWidthDp;
result = 31 * result + screenHeightDp;
result = 31 * result + smallestScreenWidthDp;
result = 31 * result + densityDpi;
+ result = 31 * result + assetsSeq;
return result;
}
@@ -1600,6 +1758,24 @@
}
/**
+ * Return whether the screen has a wide color gamut.
+ *
+ * @return true if the screen has a wide color gamut, false otherwise
+ */
+ public boolean isScreenWideColorGamut() {
+ return (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) == COLOR_MODE_WIDE_COLOR_GAMUT_YES;
+ }
+
+ /**
+ * Return whether the screen has a high dynamic range.
+ *
+ * @return true if the screen has a high dynamic range, false otherwise
+ */
+ public boolean isScreenHdr() {
+ return (colorMode & COLOR_MODE_HDR_MASK) == COLOR_MODE_HDR_YES;
+ }
+
+ /**
*
* @hide
*/
@@ -1731,6 +1907,28 @@
break;
}
+ switch (config.colorMode & Configuration.COLOR_MODE_HDR_MASK) {
+ case Configuration.COLOR_MODE_HDR_YES:
+ parts.add("highdr");
+ break;
+ case Configuration.COLOR_MODE_HDR_NO:
+ parts.add("lowdr");
+ break;
+ default:
+ break;
+ }
+
+ switch (config.colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK) {
+ case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES:
+ parts.add("widecg");
+ break;
+ case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO:
+ parts.add("nowidecg");
+ break;
+ default:
+ break;
+ }
+
switch (config.orientation) {
case Configuration.ORIENTATION_LANDSCAPE:
parts.add("land");
@@ -1956,6 +2154,16 @@
delta.screenLayout |= change.screenLayout & SCREENLAYOUT_ROUND_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.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)) {
delta.uiMode |= change.uiMode & UI_MODE_TYPE_MASK;
}
@@ -1979,6 +2187,10 @@
if (base.densityDpi != change.densityDpi) {
delta.densityDpi = change.densityDpi;
}
+
+ if (base.assetsSeq != change.assetsSeq) {
+ delta.assetsSeq = change.assetsSeq;
+ }
return delta;
}
@@ -1994,6 +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_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";
@@ -2036,6 +2249,8 @@
ORIENTATION_UNDEFINED);
configOut.screenLayout = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_LAYOUT,
SCREENLAYOUT_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);
@@ -2046,6 +2261,8 @@
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
configOut.densityDpi = XmlUtils.readIntAttribute(parser, XML_ATTR_DENSITY,
DENSITY_DPI_UNDEFINED);
+
+ // For persistence, we don't care about assetsSeq, so do not read it out.
}
@@ -2096,6 +2313,9 @@
if (config.screenLayout != SCREENLAYOUT_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_LAYOUT, config.screenLayout);
}
+ 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);
}
@@ -2111,5 +2331,7 @@
if (config.densityDpi != DENSITY_DPI_UNDEFINED) {
XmlUtils.writeIntAttribute(xml, XML_ATTR_DENSITY, config.densityDpi);
}
+
+ // For persistence, we do not care about assetsSeq, so do not write it out.
}
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ad11307..c3185a7 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -40,6 +40,7 @@
import android.annotation.XmlRes;
import android.content.pm.ActivityInfo;
import android.graphics.Movie;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.graphics.drawable.DrawableInflater;
@@ -333,7 +334,35 @@
return res;
}
throw new NotFoundException("String resource ID #0x"
- + Integer.toHexString(id));
+ + Integer.toHexString(id));
+ }
+
+ /**
+ * Return the Typeface value associated with a particular resource ID.
+ * {@more}
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return Typeface The Typeface data associated with the resource.
+ */
+ @NonNull public Typeface getFont(@StringRes int id) throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ Typeface typeface = impl.loadFont(value, id);
+ if (typeface != null) {
+ return typeface;
+ }
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ throw new NotFoundException("Font resource ID #0x"
+ + Integer.toHexString(id));
}
/**
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index eb010e4..0ffc6c6 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -31,6 +31,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources.NotFoundException;
+import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.icu.text.PluralRules;
@@ -47,6 +48,7 @@
import android.view.Display;
import android.view.DisplayAdjustments;
+import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Locale;
@@ -418,7 +420,7 @@
mConfiguration.smallestScreenWidthDp,
mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
mConfiguration.screenLayout, mConfiguration.uiMode,
- Build.VERSION.RESOURCES_SDK_INT);
+ mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
if (DEBUG_CONFIG) {
Slog.i(TAG, "**** Updating config of " + this + ": final config is "
@@ -740,6 +742,36 @@
}
/**
+ * Loads a font from XML or resources stream.
+ */
+ @Nullable
+ public Typeface loadFont(TypedValue value, int id) {
+ if (value.string == null) {
+ throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ + Integer.toHexString(id) + ") is not a Font: " + value);
+ }
+
+ final String file = value.string.toString();
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading font for cookie " + value.assetCookie + ": " + file);
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ try {
+ if (file.endsWith(".xml")) {
+ // TODO handle xml type font definitions
+ } else {
+ return Typeface.createFromResources(
+ mAssets, value.string.toString(), value.assetCookie);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ return null;
+ }
+
+ /**
* Given the value and id, we can get the XML filename as in value.data, based on that, we
* first try to load CSL from the cache. If not found, try to get from the constant state.
* Last, parse the XML and generate the CSL.
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 227066d..8cd3d7b 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -728,13 +728,10 @@
* @param values the {@link ContentValues} to put the row into.
*/
public static void cursorRowToContentValues(Cursor cursor, ContentValues values) {
- AbstractWindowedCursor awc =
- (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null;
-
String[] columns = cursor.getColumnNames();
int length = columns.length;
for (int i = 0; i < length; i++) {
- if (awc != null && awc.isBlob(i)) {
+ if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) {
values.put(columns[i], cursor.getBlob(i));
} else {
values.put(columns[i], cursor.getString(i));
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..e97bb2f
--- /dev/null
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -0,0 +1,324 @@
+/*
+ * 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, BLOB})
+ 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 = 0x16;
+ /** Format: opaque format used for raw data transfer; must have a height of 1 */
+ public static final int BLOB = 0x21;
+
+ // 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);
+ }
+ if (format == BLOB && height != 1) {
+ throw new IllegalArgumentException("Height must be 1 when using the BLOB format");
+ }
+ 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:
+ case BLOB:
+ 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/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 98a8904..12b46c1 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1625,6 +1625,34 @@
new Key<Integer>("android.control.postRawSensitivityBoost", int.class);
/**
+ * <p>Allow camera device to enable zero-shutter-lag mode for requests with
+ * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE.</p>
+ * <p>If enableZsl is <code>true</code>, the camera device may enable zero-shutter-lag mode for requests with
+ * STILL_CAPTURE capture intent. The camera device may use images captured in the past to
+ * produce output images for a zero-shutter-lag request. The result metadata including the
+ * {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} reflects the source frames used to produce output images.
+ * Therefore, the contents of the output images and the result metadata may be out of order
+ * compared to previous regular requests. enableZsl does not affect requests with other
+ * capture intents.</p>
+ * <p>For example, when requests are submitted in the following order:
+ * Request A: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is PREVIEW
+ * Request B: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is STILL_CAPTURE</p>
+ * <p>The output images for request B may have contents captured before the output images for
+ * request A, and the result metadata for request B may be older than the result metadata for
+ * request A.</p>
+ * <p>Note that when enableZsl is <code>true</code>, it is not guaranteed to get output images captured in the
+ * past for requests with STILL_CAPTURE capture intent.</p>
+ * <p>The value of enableZsl in capture templates is always <code>false</code> if present.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
+ * @see CaptureResult#SENSOR_TIMESTAMP
+ */
+ @PublicKey
+ public static final Key<Boolean> CONTROL_ENABLE_ZSL =
+ new Key<Boolean>("android.control.enableZsl", boolean.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 23f4b9e..3f8b57a 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2132,6 +2132,34 @@
new Key<Integer>("android.control.postRawSensitivityBoost", int.class);
/**
+ * <p>Allow camera device to enable zero-shutter-lag mode for requests with
+ * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE.</p>
+ * <p>If enableZsl is <code>true</code>, the camera device may enable zero-shutter-lag mode for requests with
+ * STILL_CAPTURE capture intent. The camera device may use images captured in the past to
+ * produce output images for a zero-shutter-lag request. The result metadata including the
+ * {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} reflects the source frames used to produce output images.
+ * Therefore, the contents of the output images and the result metadata may be out of order
+ * compared to previous regular requests. enableZsl does not affect requests with other
+ * capture intents.</p>
+ * <p>For example, when requests are submitted in the following order:
+ * Request A: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is PREVIEW
+ * Request B: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is STILL_CAPTURE</p>
+ * <p>The output images for request B may have contents captured before the output images for
+ * request A, and the result metadata for request B may be older than the result metadata for
+ * request A.</p>
+ * <p>Note that when enableZsl is <code>true</code>, it is not guaranteed to get output images captured in the
+ * past for requests with STILL_CAPTURE capture intent.</p>
+ * <p>The value of enableZsl in capture templates is always <code>false</code> if present.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
+ * @see CaptureResult#SENSOR_TIMESTAMP
+ */
+ @PublicKey
+ public static final Key<Boolean> CONTROL_ENABLE_ZSL =
+ new Key<Boolean>("android.control.enableZsl", boolean.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
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..6b7546f 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,63 @@
}
/**
- * 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.");
+ // TODO: b/34697112. This needs to be reverted once app fix is merged.
+ // Do not throw exception for below case:
+ // - OutputConfiguration(Size(0, 0), klass)
+ // - addSurface(surface)
+ if ((mConfiguredSize.getWidth() != 0 || mConfiguredSize.getHeight() != 0) &&
+ !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 +401,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 +409,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 +419,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 +433,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 +517,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 +545,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 +567,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 +597,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/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 12e1963..6788b67 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -108,13 +108,14 @@
* </p>
*
* <p>
- * A private virtual display belongs to the application that created it.
- * Only the a owner of a private virtual display is allowed to place windows upon it.
- * The private virtual display also does not participate in display mirroring: it will
- * neither receive mirrored content from another display nor allow its own content to
- * be mirrored elsewhere. More precisely, the only processes that are allowed to
- * enumerate or interact with the private display are those that have the same UID as the
- * application that originally created the private virtual display.
+ * A private virtual display belongs to the application that created it. Only the a owner of a
+ * private virtual display and the apps that are already on that display are allowed to place
+ * windows upon it. The private virtual display also does not participate in display mirroring:
+ * it will neither receive mirrored content from another display nor allow its own content to be
+ * mirrored elsewhere. More precisely, the only processes that are allowed to enumerate or
+ * interact with the private display are those that have the same UID as the application that
+ * originally created the private virtual display or as the activities that are already on that
+ * display.
* </p>
*
* @see #createVirtualDisplay
@@ -234,6 +235,21 @@
*/
public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 1 << 4;
+ /**
+ * Virtual display flag: Allows content to be displayed on private virtual displays when
+ * keyguard is shown but is insecure.
+ *
+ * <p>
+ * This flag can only be applied to private displays as defined by the
+ * {@link Display#FLAG_PRIVATE} display flag. It is mutually exclusive with
+ * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}. If both flags are specified then this flag's behavior
+ * will not be applied.
+ * </p>
+ *
+ * @see #createVirtualDisplay
+ */
+ public static final int VIRTUAL_DISPLAY_FLAG_SHOW_WITH_INSECURE_LOCKSCREEN = 1 << 5;
+
/** @hide */
public DisplayManager(Context context) {
mContext = context;
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index aea1258..3eb5844 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -19,6 +19,8 @@
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.PowerManager;
+import android.util.IntArray;
+import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
@@ -147,6 +149,21 @@
public abstract void setDisplayOffsets(int displayId, int x, int y);
/**
+ * Provide a list of UIDs that are present on the display and are allowed to access it.
+ *
+ * @param displayAccessUIDs Mapping displayId -> int array of UIDs.
+ */
+ public abstract void setDisplayAccessUIDs(SparseArray<IntArray> displayAccessUIDs);
+
+ /**
+ * Check if specified UID's content is present on display and should be granted access to it.
+ *
+ * @param uid UID to be checked.
+ * @param displayId id of the display where presence of the content is checked.
+ * */
+ public abstract boolean isUidPresentOnDisplay(int uid, int displayId);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 4b57078..2c9e6c7 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1074,7 +1074,7 @@
@Override // binder call
public void onAuthenticationFailed(long deviceId) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();;
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
}
@Override // binder call
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index a9c09c4..bdb278b 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -81,4 +81,6 @@
void setPointerIconType(int typeId);
void setCustomPointerIcon(in PointerIcon icon);
+
+ void requestPointerCapture(IBinder windowToken, boolean enabled);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 2b0593f..6e202b0 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -40,6 +40,7 @@
import android.util.SparseArray;
import android.view.InputDevice;
import android.view.InputEvent;
+import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -898,6 +899,25 @@
}
}
+ /**
+ * Request or release pointer capture.
+ * <p>
+ * When in capturing mode, the pointer icon disappears and all mouse events are dispatched to
+ * the window which has requested the capture. Relative position changes are available through
+ * {@link MotionEvent#getX} and {@link MotionEvent#getY}.
+ *
+ * @param enable true when requesting pointer capture, false when releasing.
+ *
+ * @hide
+ */
+ public void requestPointerCapture(IBinder windowToken, boolean enable) {
+ try {
+ mIm.requestPointerCapture(windowToken, enable);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private void populateInputDevicesLocked() {
if (mInputDevicesChangedListener == null) {
final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/hardware/usb/UsbEndpoint.java b/core/java/android/hardware/usb/UsbEndpoint.java
index 708d651..c346700 100644
--- a/core/java/android/hardware/usb/UsbEndpoint.java
+++ b/core/java/android/hardware/usb/UsbEndpoint.java
@@ -75,8 +75,8 @@
* if the direction is host to device, and
* {@link UsbConstants#USB_DIR_IN} if the
* direction is device to host.
- * @see {@link UsbConstants#USB_DIR_IN}
- * @see {@link UsbConstants#USB_DIR_OUT}
+ * @see UsbConstants#USB_DIR_IN
+ * @see UsbConstants#USB_DIR_OUT
*
* @return the endpoint's direction
*/
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/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index c6338cb..beed6e9 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -2367,16 +2367,16 @@
}
return true;
}
-
+
/**
* Return text that can be used as a button label for the given
* {@link EditorInfo#imeOptions EditorInfo.imeOptions}. Returns null
* if there is no action requested. Note that there is no guarantee that
* the returned text will be relatively short, so you probably do not
* want to use it as text on a soft keyboard key label.
- *
- * @param imeOptions The value from @link EditorInfo#imeOptions EditorInfo.imeOptions}.
- *
+ *
+ * @param imeOptions The value from {@link EditorInfo#imeOptions EditorInfo.imeOptions}.
+ *
* @return Returns a label to use, or null if there is no action.
*/
public CharSequence getTextForImeAction(int imeOptions) {
diff --git a/core/java/com/android/internal/logging/LogBuilder.java b/core/java/android/metrics/LogMaker.java
similarity index 69%
rename from core/java/com/android/internal/logging/LogBuilder.java
rename to core/java/android/metrics/LogMaker.java
index 7eda3da..83f30be 100644
--- a/core/java/com/android/internal/logging/LogBuilder.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -13,99 +13,133 @@
* 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;
}
/**
* @param tag From your MetricsEvent enum.
- * @param value One of Integer, Long, Float, String
- * @return
+ * @param value One of Integer, Long, Float, or String; or null to clear the tag.
+ * @return modified LogMaker
*/
- public LogBuilder addTaggedData(int tag, Object value) {
- if (isValidValue(value)) {
+ public LogMaker addTaggedData(int tag, Object value) {
+ if (value == null) {
+ return clearTaggedData(tag);
+ }
+ 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;
}
+ /**
+ * Remove a value from the LogMaker.
+ *
+ * @param tag From your MetricsEvent enum.
+ * @return modified LogMaker
+ */
+ public LogMaker clearTaggedData(int tag) {
+ entries.delete(tag);
+ return this;
+ }
+
+ /**
+ * @return true if this object may be added to a LogMaker as a value.
+ */
public boolean isValidValue(Object value) {
- return !(value instanceof Integer ||
+ return value instanceof Integer ||
value instanceof String ||
value instanceof Long ||
- value instanceof Float);
+ value instanceof Float;
}
public Object getTaggedData(int tag) {
@@ -198,18 +232,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/NetworkBadging.java b/core/java/android/net/NetworkBadging.java
new file mode 100644
index 0000000..5cf2f96
--- /dev/null
+++ b/core/java/android/net/NetworkBadging.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.net.ScoredNetwork.Badging;
+import android.net.wifi.WifiManager;
+import android.view.View;
+
+/**
+ * Utility methods for working with network badging.
+ *
+ * TODO: move ScoredNetwork.Badging and related constants to this class.
+ *
+ * @hide
+ */
+@SystemApi
+public class NetworkBadging {
+ private NetworkBadging() {}
+
+ /**
+ * Returns a Wi-Fi icon for a network with a given signal level and badging value.
+ *
+ * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+ * for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+ * @param badging {@see ScoredNetwork#Badging}, retrieved from
+ * {@link ScoredNetwork#calculateBadge(int)}.
+ * @param theme The theme for the current application, may be null.
+ * @return Drawable for the given icon
+ * @throws IllegalArgumentException if {@code signalLevel} is out of range or {@code badging}
+ * is an invalid value
+ */
+ @NonNull public static Drawable getWifiIcon(
+ @IntRange(from=0, to=4) int signalLevel, @Badging int badging, @Nullable Theme theme) {
+ Resources resources = Resources.getSystem();
+ if (badging == ScoredNetwork.BADGING_NONE) {
+ return resources.getDrawable(getWifiSignalResource(signalLevel), theme);
+ }
+ Drawable[] layers = new Drawable[] {
+ resources.getDrawable(getBadgedWifiSignalResource(signalLevel), theme),
+ resources.getDrawable(getWifiBadgeResource(badging), theme)
+ };
+ return new LayerDrawable(layers);
+ }
+
+ /**
+ * Returns the wifi signal resource id for the given signal level.
+ *
+ * <p>This wifi signal resource is a wifi icon to be displayed by itself when there is no badge.
+ *
+ * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+ * for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+ * @return the @DrawableRes for the icon
+ * @throws IllegalArgumentException for an invalid signal level
+ * @hide
+ */
+ @DrawableRes private static int getWifiSignalResource(int signalLevel) {
+ switch (signalLevel) {
+ case 0:
+ return com.android.internal.R.drawable.ic_wifi_signal_0;
+ case 1:
+ return com.android.internal.R.drawable.ic_wifi_signal_1;
+ case 2:
+ return com.android.internal.R.drawable.ic_wifi_signal_2;
+ case 3:
+ return com.android.internal.R.drawable.ic_wifi_signal_3;
+ case 4:
+ return com.android.internal.R.drawable.ic_wifi_signal_4;
+ default:
+ throw new IllegalArgumentException("Invalid signal level: " + signalLevel);
+ }
+ }
+
+ /**
+ * Returns the badged wifi signal resource id for the given signal level.
+ *
+ * <p>This badged wifi signal resource should be displayed with the quality badge retrieved
+ * from {@link #getWifiBadgeResource(int)}. If there is no badge,
+ * {@link #getWifiBadgeResource(int)} should be used instead of this method.
+ *
+ * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+ * for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+ * @return the @DrawableRes for the icon
+ * @throws IllegalArgumentException for an invalid signal level
+ * @hide
+ */
+ @DrawableRes private static int getBadgedWifiSignalResource(int signalLevel) {
+ switch (signalLevel) {
+ case 0:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_0_bars;
+ case 1:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_1_bar;
+ case 2:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_2_bars;
+ case 3:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_3_bars;
+ case 4:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_4_bars;
+ default:
+ throw new IllegalArgumentException("Invalid signal level: " + signalLevel);
+ }
+ }
+
+ /**
+ * Returns the wifi quality badge resource id for the the given badging balue.
+ *
+ * <p>This badge should be displayed with the badge signal resource retrieved from
+ * {@link #getBadgedWifiSignalResource(int)}.
+ *
+ * @param badging {@see ScoredNetwork#Badging} from {@link ScoredNetwork#calculateBadge(int)}.
+ * @return the @DrawableRes for the icon or {@link View#NO_ID} for
+ * {@link ScoredNetwork#BADGING_NONE}
+ * @throws IllegalArgumentException for an invalid badging value.
+ * @hide
+ */
+ @DrawableRes private static int getWifiBadgeResource(@Badging int badging) {
+ switch (badging) {
+ 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:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_hd;
+ case ScoredNetwork.BADGING_4K:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_4k;
+ default:
+ throw new IllegalArgumentException("No resource found for badge: " + badging);
+ }
+ }
+}
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index ffbc401..e5f0bf0 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -16,9 +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;
@@ -52,6 +57,39 @@
public final WifiKey wifiKey;
/**
+ * Constructs a new NetworkKey for the given wifi {@link ScanResult}.
+ *
+ * @throws IllegalArgumentException if the given ScanResult is malformed
+ * @hide
+ */
+ public static NetworkKey createFromScanResult(ScanResult result) {
+ return new NetworkKey(
+ new WifiKey(
+ '"' + result.wifiSsid.toString() + '"', result.BSSID));
+ }
+
+ /**
+ * 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/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
index 23d5af5..9e4dd87 100644
--- a/core/java/android/net/NetworkScorerAppManager.java
+++ b/core/java/android/net/NetworkScorerAppManager.java
@@ -226,16 +226,6 @@
return false;
}
- /** Determine whether the application with the given UID is the enabled scorer. */
- @Deprecated // Use NetworkScoreManager.isCallerActiveScorer()
- public boolean isCallerActiveScorer(int callingUid) {
- NetworkScorerAppData defaultApp = getActiveScorer();
- if (defaultApp == null) {
- return false;
- }
- return callingUid == defaultApp.packageUid;
- }
-
private boolean isNetworkRecommendationsDisabled() {
final ContentResolver cr = mContext.getContentResolver();
// A value of 1 indicates enabled.
diff --git a/core/java/android/net/RecommendationRequest.java b/core/java/android/net/RecommendationRequest.java
index a227f18..b89a245 100644
--- a/core/java/android/net/RecommendationRequest.java
+++ b/core/java/android/net/RecommendationRequest.java
@@ -37,6 +37,8 @@
private final WifiConfiguration mDefaultConfig;
private WifiConfiguration mConnectedConfig;
private WifiConfiguration[] mConnectableConfigs;
+ private final int mLastSelectedNetworkId;
+ private final long mLastSelectedNetworkTimestamp;
/**
* Builder class for constructing {@link RecommendationRequest} instances.
@@ -48,17 +50,9 @@
private WifiConfiguration mDefaultConfig;
private WifiConfiguration mConnectedConfig;
private WifiConfiguration[] mConnectableConfigs;
+ private int mLastSelectedNetworkId;
+ private long mLastSelectedTimestamp;
- /**
- * @param scanResults the array of {@link ScanResult}s the recommendation must be
- * constrained to i.e. if a non-null wifi config recommendation is
- * returned then it must be able to connect to one of the networks in
- * the results list.
- *
- * If the array is {@code null} or empty then there is no constraint.
- *
- * @return this
- */
public Builder setScanResults(ScanResult[] scanResults) {
mScanResults = scanResults;
return this;
@@ -89,7 +83,20 @@
* @return this
*/
public Builder setConnectableConfigs(WifiConfiguration[] connectableConfigs) {
- mConnectableConfigs = connectableConfigs;
+ this.mConnectableConfigs = connectableConfigs;
+ return this;
+ }
+
+ /**
+ * @param networkId The {@link WifiConfiguration#networkId} of the last user selected
+ * network.
+ * @param timestamp The {@link android.os.SystemClock#elapsedRealtime()} when the user
+ * selected {@code networkId}.
+ * @return this
+ */
+ public Builder setLastSelectedNetwork(int networkId, long timestamp) {
+ this.mLastSelectedNetworkId = networkId;
+ this.mLastSelectedTimestamp = timestamp;
return this;
}
@@ -97,10 +104,8 @@
* @return a new {@link RecommendationRequest} instance
*/
public RecommendationRequest build() {
- return new RecommendationRequest(mScanResults,
- mDefaultConfig,
- mConnectedConfig,
- mConnectableConfigs);
+ return new RecommendationRequest(mScanResults, mDefaultConfig, mConnectedConfig,
+ mConnectableConfigs, mLastSelectedNetworkId, mLastSelectedTimestamp);
}
}
@@ -154,15 +159,35 @@
mConnectableConfigs = connectableConfigs;
}
+ /**
+ * @return The {@link WifiConfiguration#networkId} of the last user selected network.
+ * {@code 0} if not set.
+ */
+ public int getLastSelectedNetworkId() {
+ return mLastSelectedNetworkId;
+ }
+
+ /**
+ * @return The {@link android.os.SystemClock#elapsedRealtime()} when the user selected
+ * {@link #getLastSelectedNetworkId()}. {@code 0} if not set.
+ */
+ public long getLastSelectedNetworkTimestamp() {
+ return mLastSelectedNetworkTimestamp;
+ }
+
@VisibleForTesting
RecommendationRequest(ScanResult[] scanResults,
WifiConfiguration defaultWifiConfig,
WifiConfiguration connectedWifiConfig,
- WifiConfiguration[] connectableConfigs) {
+ WifiConfiguration[] connectableConfigs,
+ int lastSelectedNetworkId,
+ long lastSelectedNetworkTimestamp) {
mScanResults = scanResults;
mDefaultConfig = defaultWifiConfig;
mConnectedConfig = connectedWifiConfig;
mConnectableConfigs = connectableConfigs;
+ mLastSelectedNetworkId = lastSelectedNetworkId;
+ mLastSelectedNetworkTimestamp = lastSelectedNetworkTimestamp;
}
protected RecommendationRequest(Parcel in) {
@@ -190,6 +215,9 @@
} else {
mConnectableConfigs = null;
}
+
+ mLastSelectedNetworkId = in.readInt();
+ mLastSelectedNetworkTimestamp = in.readLong();
}
@Override
@@ -220,7 +248,8 @@
dest.writeInt(0);
}
-
+ dest.writeInt(mLastSelectedNetworkId);
+ dest.writeLong(mLastSelectedNetworkTimestamp);
}
public static final Creator<RecommendationRequest> CREATOR =
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index cea56b5..ffc735c 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -96,6 +96,7 @@
public boolean requestTime(InetAddress address, int port, int timeout) {
DatagramSocket socket = null;
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_NTP);
try {
socket = new DatagramSocket();
socket.setSoTimeout(timeout);
@@ -161,6 +162,7 @@
if (socket != null) {
socket.close();
}
+ TrafficStats.setThreadStatsTag(oldTag);
}
return true;
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index e7436be..fc66395 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -168,6 +168,24 @@
/**
* Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ * <p>
+ * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
+ * used internally by system services like {@link DownloadManager} when
+ * performing traffic on behalf of an application.
+ *
+ * @return the current tag for the calling thread, which can be used to
+ * restore any existing values after a nested operation is finished
+ */
+ public static int getAndSetThreadStatsTag(int tag) {
+ return NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
* from the current thread. The tag used internally is well-defined to
* distinguish all backup-related traffic.
*
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 7396189..d5377c7 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -1747,8 +1747,8 @@
* begin with and a scheme component cannot be found.
*
* @return normalized Uri (never null)
- * @see {@link android.content.Intent#setData}
- * @see {@link android.content.Intent#setDataAndNormalize}
+ * @see android.content.Intent#setData
+ * @see android.content.Intent#setDataAndNormalize
*/
public Uri normalizeScheme() {
String scheme = getScheme();
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 83d17ba..bd32314 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -805,7 +805,7 @@
if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
throw new FormatException("expected MB flag");
- } else if (mb && records.size() != 0 && !ignoreMbMe) {
+ } else if (mb && (records.size() != 0 || inChunk) && !ignoreMbMe) {
throw new FormatException("unexpected MB flag");
} else if (inChunk && il) {
throw new FormatException("unexpected IL flag in non-leading chunk");
@@ -839,6 +839,9 @@
if (cf && !inChunk) {
// first chunk
+ if (typeLength == 0) {
+ throw new FormatException("expected non-zero type length in first chunk");
+ }
chunks.clear();
chunkTnf = tnf;
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 80ecf97..817cb5b 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -883,8 +883,11 @@
SystemProperties.getInt("ro.debuggable", 0) == 1;
/** {@hide} */
- public static final boolean IS_ENG =
- "eng".equals(getString("ro.build.type"));
+ public static final boolean IS_ENG = "eng".equals(TYPE);
+ /** {@hide} */
+ public static final boolean IS_USERDEBUG = "userdebug".equals(TYPE);
+ /** {@hide} */
+ public static final boolean IS_USER = "user".equals(TYPE);
/**
* Whether this build is running inside a container.
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 175d883..210ddb6 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1119,8 +1119,8 @@
* @hide
*/
public static void startMethodTracing(String traceName, FileDescriptor fd,
- int bufferSize, int flags) {
- VMDebug.startMethodTracing(traceName, fd, bufferSize, flags, false, 0);
+ int bufferSize, int flags, boolean streamOutput) {
+ VMDebug.startMethodTracing(traceName, fd, bufferSize, flags, false, 0, streamOutput);
}
/**
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 4616af8..7cdb3ce 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -272,6 +272,11 @@
}
/** {@hide} */
+ public static File getDataMiscCeDirectory() {
+ return buildPath(getDataDirectory(), "misc_ce");
+ }
+
+ /** {@hide} */
public static File getDataMiscCeDirectory(int userId) {
return buildPath(getDataDirectory(), "misc_ce", String.valueOf(userId));
}
@@ -380,6 +385,24 @@
}
/**
+ * Returns location of preloaded cache directory for package name
+ * @see #getDataPreloadsDirectory()
+ * {@hide}
+ */
+ public static File getDataPreloadsFileCacheDirectory(String packageName) {
+ return new File(getDataPreloadsFileCacheDirectory(), packageName);
+ }
+
+ /**
+ * Returns location of preloaded cache directory.
+ * @see #getDataPreloadsDirectory()
+ * {@hide}
+ */
+ public static File getDataPreloadsFileCacheDirectory() {
+ return new File(getDataPreloadsDirectory(), "file_cache");
+ }
+
+ /**
* Return the primary shared/external storage directory. This directory may
* not currently be accessible if it has been mounted by the user on their
* computer, has been removed from the device, or some other problem has
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 73c9462..7c015de 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -38,7 +38,6 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
@@ -334,12 +333,7 @@
* @throws IOException
*/
public static void stringToFile(String filename, String string) throws IOException {
- FileWriter out = new FileWriter(filename);
- try {
- out.write(string);
- } finally {
- out.close();
- }
+ bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8));
}
/**
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 4b130ed1..e4cdbce 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.opengl.EGL14;
import android.os.SystemProperties;
import android.util.Log;
@@ -34,6 +35,22 @@
private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
public static void setupGraphicsEnvironment(Context context) {
+ chooseDriver(context);
+
+ // Now that we've figured out which driver to use for this process, load and initialize it.
+ // This can take multiple frame periods, and it would otherwise happen as part of the first
+ // frame, increasing first-frame latency. Starting it here, as a low-priority background
+ // thread, means that it's usually done long before we start drawing the first frame,
+ // without significantly disrupting other activity launch work.
+ Thread eglInitThread = new Thread(
+ () -> {
+ EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ },
+ "EGL Init");
+ eglInitThread.start();
+ }
+
+ private static void chooseDriver(Context context) {
String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
if (driverPackageName == null || driverPackageName.isEmpty()) {
return;
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/Parcel.java b/core/java/android/os/Parcel.java
index e15f086..1db685a 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -26,6 +26,9 @@
import android.util.SizeF;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import libcore.util.SneakyThrow;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -250,6 +253,7 @@
private static final int EX_NETWORK_MAIN_THREAD = -6;
private static final int EX_UNSUPPORTED_OPERATION = -7;
private static final int EX_SERVICE_SPECIFIC = -8;
+ private static final int EX_PARCELABLE = -9;
private static final int EX_HAS_REPLY_HEADER = -128; // special; see below
// EX_TRANSACTION_FAILED is used exclusively in native code.
// see libbinder's binder/Status.h
@@ -888,6 +892,21 @@
}
}
+ public final void writeSparseIntArray(SparseIntArray val) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+ int N = val.size();
+ writeInt(N);
+ int i=0;
+ while (i < N) {
+ writeInt(val.keyAt(i));
+ writeInt(val.valueAt(i));
+ i++;
+ }
+ }
+
public final void writeBooleanArray(boolean[] val) {
if (val != null) {
int N = val.length;
@@ -1597,7 +1616,12 @@
*/
public final void writeException(Exception e) {
int code = 0;
- if (e instanceof SecurityException) {
+ if (e instanceof Parcelable
+ && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
+ // We only send Parcelable exceptions that are in the
+ // BootClassLoader to ensure that the receiver can unpack them
+ code = EX_PARCELABLE;
+ } else if (e instanceof SecurityException) {
code = EX_SECURITY;
} else if (e instanceof BadParcelableException) {
code = EX_BAD_PARCELABLE;
@@ -1623,8 +1647,20 @@
throw new RuntimeException(e);
}
writeString(e.getMessage());
- if (e instanceof ServiceSpecificException) {
- writeInt(((ServiceSpecificException)e).errorCode);
+ switch (code) {
+ case EX_SERVICE_SPECIFIC:
+ writeInt(((ServiceSpecificException) e).errorCode);
+ break;
+ case EX_PARCELABLE:
+ // Write parceled exception prefixed by length
+ final int sizePosition = dataPosition();
+ writeInt(0);
+ writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ final int payloadPosition = dataPosition();
+ setDataPosition(sizePosition);
+ writeInt(payloadPosition - sizePosition);
+ setDataPosition(payloadPosition);
+ break;
}
}
@@ -1722,6 +1758,13 @@
*/
public final void readException(int code, String msg) {
switch (code) {
+ case EX_PARCELABLE:
+ if (readInt() > 0) {
+ SneakyThrow.sneakyThrow(
+ (Exception) readParcelable(Parcelable.class.getClassLoader()));
+ } else {
+ throw new RuntimeException(msg + " [missing Parcelable]");
+ }
case EX_SECURITY:
throw new SecurityException(msg);
case EX_BAD_PARCELABLE:
@@ -2127,6 +2170,20 @@
}
/**
+ * Read and return a new SparseIntArray object from the parcel at the current
+ * dataPosition(). Returns null if the previously written array object was null.
+ */
+ public final SparseIntArray readSparseIntArray() {
+ int N = readInt();
+ if (N < 0) {
+ return null;
+ }
+ SparseIntArray sa = new SparseIntArray(N);
+ readSparseIntArrayInternal(sa, N);
+ return sa;
+ }
+
+ /**
* Read and return a new ArrayList containing a particular object type from
* the parcel that was written with {@link #writeTypedList} at the
* current dataPosition(). Returns null if the
@@ -2895,6 +2952,15 @@
}
}
+ private void readSparseIntArrayInternal(SparseIntArray outVal, int N) {
+ while (N > 0) {
+ int key = readInt();
+ int value = readInt();
+ outVal.append(key, value);
+ N--;
+ }
+ }
+
/**
* @hide For testing
*/
diff --git a/core/java/android/os/ParcelableException.java b/core/java/android/os/ParcelableException.java
new file mode 100644
index 0000000..d84d629
--- /dev/null
+++ b/core/java/android/os/ParcelableException.java
@@ -0,0 +1,88 @@
+/*
+ * 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.os;
+
+import java.io.IOException;
+
+/**
+ * Wrapper class that offers to transport typical {@link Throwable} across a
+ * {@link Binder} call. This class is typically used to transport exceptions
+ * that cannot be modified to add {@link Parcelable} behavior, such as
+ * {@link IOException}.
+ * <ul>
+ * <li>The wrapped throwable must be defined as system class (that is, it must
+ * be in the same {@link ClassLoader} as {@link Parcelable}).
+ * <li>The wrapped throwable must support the
+ * {@link Throwable#Throwable(String)} constructor.
+ * <li>The receiver side must catch any thrown {@link ParcelableException} and
+ * call {@link #maybeRethrow(Class)} for all expected exception types.
+ * </ul>
+ *
+ * @hide
+ */
+public final class ParcelableException extends RuntimeException implements Parcelable {
+ public ParcelableException(Throwable t) {
+ super(t);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends Throwable> void maybeRethrow(Class<T> clazz) throws T {
+ if (clazz.isAssignableFrom(getCause().getClass())) {
+ throw (T) getCause();
+ }
+ }
+
+ /** {@hide} */
+ public static Throwable readFromParcel(Parcel in) {
+ final String name = in.readString();
+ final String msg = in.readString();
+ try {
+ final Class<?> clazz = Class.forName(name, true, Parcelable.class.getClassLoader());
+ return (Throwable) clazz.getConstructor(String.class).newInstance(msg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(name + ": " + msg);
+ }
+ }
+
+ /** {@hide} */
+ public static void writeToParcel(Parcel out, Throwable t) {
+ out.writeString(t.getClass().getName());
+ out.writeString(t.getMessage());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ writeToParcel(dest, getCause());
+ }
+
+ public static final Creator<ParcelableException> CREATOR = new Creator<ParcelableException>() {
+ @Override
+ public ParcelableException createFromParcel(Parcel source) {
+ return new ParcelableException(readFromParcel(source));
+ }
+
+ @Override
+ public ParcelableException[] newArray(int size) {
+ return new ParcelableException[size];
+ }
+ };
+}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index d6688e3..a41f45b 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -189,6 +189,13 @@
/** {@hide} */
public static final int LAST_APPLICATION_CACHE_GID = 29999;
+ /** {@hide} */
+ public static final int MEDIA_AUDIO_GID = 1055;
+ /** {@hide} */
+ public static final int MEDIA_VIDEO_GID = 1056;
+ /** {@hide} */
+ public static final int MEDIA_IMAGE_GID = 1057;
+
/**
* Standard priority of application threads.
* Use with {@link #setThreadPriority(int)} and
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index d48431a..a8822c5 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -650,6 +650,18 @@
bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
}
+ /** {@hide} */
+ public static void rebootPromptAndWipeUserData(Context context, String reason)
+ throws IOException {
+ String reasonArg = null;
+ if (!TextUtils.isEmpty(reason)) {
+ reasonArg = "--reason=" + sanitizeArg(reason);
+ }
+
+ final String localeArg = "--locale=" + Locale.getDefault().toString();
+ bootCommand(context, null, "--prompt_and_wipe_data", reasonArg, localeArg);
+ }
+
/**
* Reboot into the recovery system to wipe the /cache partition.
* @throws IOException if something goes wrong.
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 0da4bd1..b9e4bad 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.net.TrafficStats;
import android.net.Uri;
import android.util.ArrayMap;
import android.util.Log;
@@ -39,6 +40,7 @@
import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
import dalvik.system.VMDebug;
+import dalvik.system.VMRuntime;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -245,11 +247,17 @@
*/
private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
+ /**
+ * @hide
+ */
+ private static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
+
private static final int ALL_VM_DETECT_BITS =
DETECT_VM_CURSOR_LEAKS | DETECT_VM_CLOSABLE_LEAKS |
DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_INSTANCE_LEAKS |
DETECT_VM_REGISTRATION_LEAKS | DETECT_VM_FILE_URI_EXPOSURE |
- DETECT_VM_CLEARTEXT_NETWORK | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION;
+ DETECT_VM_CLEARTEXT_NETWORK | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION |
+ DETECT_VM_UNTAGGED_SOCKET;
// Byte 3: Penalty
@@ -300,6 +308,8 @@
*/
public static final int PENALTY_DEATH_ON_FILE_URI_EXPOSURE = 0x04 << 24;
+ // CAUTION: we started stealing the top bits of Byte 4 for VM above
+
/**
* Mask of all the penalty bits valid for thread policies.
*/
@@ -414,7 +424,21 @@
* disk operations but will likely expand in future releases.
*/
public Builder detectAll() {
- return enable(ALL_THREAD_DETECT_BITS);
+ detectDiskReads();
+ detectDiskWrites();
+ detectNetwork();
+
+ final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
+ if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
+ detectCustomSlowCalls();
+ }
+ if (targetSdk >= Build.VERSION_CODES.M) {
+ detectResourceMismatches();
+ }
+ if (targetSdk >= Build.VERSION_CODES.O) {
+ detectUnbufferedIo();
+ }
+ return this;
}
/**
@@ -713,17 +737,31 @@
* but will likely expand in future releases.
*/
public Builder detectAll() {
- int flags = DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_CURSOR_LEAKS
- | DETECT_VM_CLOSABLE_LEAKS | DETECT_VM_REGISTRATION_LEAKS
- | DETECT_VM_FILE_URI_EXPOSURE | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION;
+ detectLeakedSqlLiteObjects();
- // TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have facility
- // for apps to mark sockets that should be ignored
- if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) {
- flags |= DETECT_VM_CLEARTEXT_NETWORK;
+ final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
+ if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
+ detectActivityLeaks();
+ detectLeakedClosableObjects();
}
-
- return enable(flags);
+ if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
+ detectLeakedRegistrationObjects();
+ }
+ if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ detectFileUriExposure();
+ }
+ if (targetSdk >= Build.VERSION_CODES.M) {
+ // TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have
+ // facility for apps to mark sockets that should be ignored
+ if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) {
+ detectCleartextNetwork();
+ }
+ }
+ if (targetSdk >= Build.VERSION_CODES.O) {
+ detectContentUriWithoutPermission();
+ detectUntaggedSockets();
+ }
+ return this;
}
/**
@@ -820,6 +858,22 @@
}
/**
+ * Detect any sockets in the calling app which have not been tagged
+ * using {@link TrafficStats}. Tagging sockets can help you
+ * investigate network usage inside your app, such as a narrowing
+ * down heavy usage to a specific library or component.
+ * <p>
+ * This currently does not detect sockets created in native code.
+ *
+ * @see TrafficStats#setThreadStatsTag(int)
+ * @see TrafficStats#tagSocket(java.net.Socket)
+ * @see TrafficStats#tagDatagramSocket(java.net.DatagramSocket)
+ */
+ public Builder detectUntaggedSockets() {
+ return enable(DETECT_VM_UNTAGGED_SOCKET);
+ }
+
+ /**
* Crashes the whole process on violation. This penalty runs at the
* end of all enabled penalties so you'll still get your logging or
* other violations before the process dies.
@@ -1152,6 +1206,11 @@
if (IS_ENG_BUILD) {
policyBuilder.penaltyLog();
}
+ // All core system components need to tag their sockets to aid
+ // system health investigations
+ if (android.os.Process.myUid() < android.os.Process.FIRST_APPLICATION_UID) {
+ policyBuilder.detectUntaggedSockets();
+ }
setVmPolicy(policyBuilder.build());
setCloseGuardEnabled(vmClosableObjectLeaksEnabled());
}
@@ -1832,6 +1891,13 @@
/**
* @hide
*/
+ public static boolean vmUntaggedSocketEnabled() {
+ return (sVmPolicyMask & DETECT_VM_UNTAGGED_SOCKET) != 0;
+ }
+
+ /**
+ * @hide
+ */
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
onVmPolicyViolation(message, originStack);
}
@@ -1911,6 +1977,14 @@
forceDeath);
}
+ /**
+ * @hide
+ */
+ public static void onUntaggedSocket() {
+ onVmPolicyViolation(null, new Throwable("Untagged socket detected; use"
+ + " TrafficStats.setThreadSocketTag() to track all network usage"));
+ }
+
// Map from VM violation fingerprint to uptime millis.
private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index efacb20..388054d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -66,6 +66,8 @@
private final IUserManager mService;
private final Context mContext;
+ private Boolean mIsManagedProfileCached;
+
/**
* @hide
* No user restriction.
@@ -970,8 +972,14 @@
*/
@SystemApi
public boolean isManagedProfile() {
+ // No need for synchronization. Once it becomes non-null, it'll be non-null forever.
+ // Worst case we might end up calling the AIDL method multiple times but that's fine.
+ if (mIsManagedProfileCached != null) {
+ return mIsManagedProfileCached;
+ }
try {
- return mService.isManagedProfile(UserHandle.myUserId());
+ mIsManagedProfileCached = mService.isManagedProfile(UserHandle.myUserId());
+ return mIsManagedProfileCached;
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -987,6 +995,9 @@
*/
@SystemApi
public boolean isManagedProfile(@UserIdInt int userId) {
+ if (userId == UserHandle.myUserId()) {
+ return isManagedProfile();
+ }
try {
return mService.isManagedProfile(userId);
} catch (RemoteException re) {
@@ -2082,6 +2093,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 59394b2..35a266b 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -286,10 +286,11 @@
void prepareUserStorage(in String volumeUuid, int userId, int serialNumber, int flags) = 66;
void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67;
boolean isConvertibleToFBE() = 68;
- ParcelFileDescriptor mountAppFuse(in String name) = 69;
void addUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 70;
void fixateNewestUserKeyAuth(int userId) = 71;
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 85df48f..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;
@@ -1319,16 +1325,6 @@
}
/** {@hide} */
- public ParcelFileDescriptor mountAppFuse(String name) {
- try {
- return mStorageManager.mountAppFuse(name);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
-
- /** {@hide} */
@VisibleForTesting
public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory)
@@ -1406,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 371c0f3..f1bffd3 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -30,6 +30,7 @@
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.app.Application;
+import android.app.NotificationChannel;
import android.app.SearchManager;
import android.app.WallpaperManager;
import android.content.ComponentName;
@@ -50,6 +51,7 @@
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.BatteryManager;
+import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.DropBoxManager;
@@ -57,6 +59,7 @@
import android.os.LocaleList;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.speech.tts.TextToSpeech;
@@ -161,13 +164,13 @@
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
* <p>
- * Input: {@link ConnectivityManager.EXTRA_TETHER_TYPE} should be included to specify which type
- * of tethering should be checked. {@link ConnectivityManager.EXTRA_PROVISION_CALLBACK} should
+ * Input: {@link ConnectivityManager#EXTRA_TETHER_TYPE} should be included to specify which type
+ * of tethering should be checked. {@link ConnectivityManager#EXTRA_PROVISION_CALLBACK} should
* contain a {@link ResultReceiver} which will be called back with a tether result code.
* <p>
* Output: The result of the provisioning check.
- * {@link ConnectivityManager.TETHER_ERROR_NO_ERROR} if successful,
- * {@link ConnectivityManager.TETHER_ERROR_PROVISION_FAILED} for failure.
+ * {@link ConnectivityManager#TETHER_ERROR_NO_ERROR} if successful,
+ * {@link ConnectivityManager#TETHER_ERROR_PROVISION_FAILED} for failure.
*
* @hide
*/
@@ -286,6 +289,20 @@
"android.settings.SECURITY_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of trusted external sources
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_EXTERNAL_SOURCES =
+ "android.settings.action.MANAGE_EXTERNAL_SOURCES";
+
+ /**
* Activity Action: Show trusted credentials settings, opening to the user tab,
* to allow management of installed credentials.
* <p>
@@ -1257,14 +1274,48 @@
/**
* Activity Action: Show notification settings for a single app.
- *
+ * <p>
+ * Input: {@link #EXTRA_APP_PACKAGE}, the package containing the channel to display.
+ * Input: Optionally, {@link #EXTRA_CHANNEL_ID}, to highlight that channel.
+ * <p>
+ * Output: Nothing.
* @hide
*/
+ @SystemApi
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_APP_NOTIFICATION_SETTINGS
= "android.settings.APP_NOTIFICATION_SETTINGS";
/**
+ * Activity Action: Show notification settings for a single {@link NotificationChannel}.
+ * <p>
+ * Must be called from an activity.
+ * <p>
+ * Input: {@link #EXTRA_APP_PACKAGE}, the package containing the channel to display.
+ * Input: {@link #EXTRA_CHANNEL_ID}, the id of the channel to display.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANNEL_NOTIFICATION_SETTINGS
+ = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
+
+ /**
+ * Activity Extra: The package owner of the notification channel settings to display.
+ * <p>
+ * This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
+ */
+ public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
+
+ /**
+ * Activity Extra: The {@link NotificationChannel#getId()} of the notification channel settings
+ * to display.
+ * <p>
+ * This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
+ */
+ public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
+
+ /**
* Activity Action: Show notification redaction settings.
*
* @hide
@@ -1274,7 +1325,6 @@
= "android.settings.ACTION_APP_NOTIFICATION_REDACTION";
/** @hide */ public static final String EXTRA_APP_UID = "app_uid";
- /** @hide */ public static final String EXTRA_APP_PACKAGE = "app_package";
/**
* Activity Action: Show a dialog with disabled by policy message.
@@ -1318,6 +1368,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.
@@ -1581,6 +1644,24 @@
// with a partial enable/disable state in multi-threaded situations.
private static final Object mLocationSettingsLock = new Object();
+ // Used in system server calling uid workaround in call()
+ private static boolean sInSystemServer = false;
+ private static final Object sInSystemServerLock = new Object();
+
+ /** @hide */
+ public static void setInSystemServer() {
+ synchronized (sInSystemServerLock) {
+ sInSystemServer = true;
+ }
+ }
+
+ /** @hide */
+ public static boolean isInSystemServer() {
+ synchronized (sInSystemServerLock) {
+ return sInSystemServer;
+ }
+ }
+
public static class SettingNotFoundException extends AndroidException {
public SettingNotFoundException(String msg) {
super(msg);
@@ -1789,7 +1870,23 @@
}
}
}
- Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
+ Bundle b;
+ // If we're in system server and in a binder transaction we need to clear the
+ // calling uid. This works around code in system server that did not call
+ // clearCallingIdentity, previously this wasn't needed because reading settings
+ // did not do permission checking but thats no longer the case.
+ // Long term this should be removed and callers should properly call
+ // clearCallingIdentity or use a ContentResolver from the caller as needed.
+ if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
+ }
if (b != null) {
String value = b.getString(Settings.NameValueTable.VALUE);
// Don't update our cache for reads of other users' data
@@ -1849,7 +1946,19 @@
try {
Bundle queryArgs = ContentResolver.createSqlQueryBundle(
NAME_EQ_PLACEHOLDER, new String[]{name}, null);
- c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE_PROJECTION, queryArgs, null);
+ // Same workaround as above.
+ if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE_PROJECTION, queryArgs,
+ null);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE_PROJECTION, queryArgs,
+ null);
+ }
if (c == null) {
Log.w(TAG, "Can't get key " + name + " from " + mUri);
return null;
@@ -4006,6 +4115,22 @@
}
/**
+ * System settings which can be accessed by ephemeral apps.
+ * @hide
+ */
+ public static final Set<String> EPHEMERAL_SETTINGS = new ArraySet<>();
+ static {
+ EPHEMERAL_SETTINGS.add(TEXT_AUTO_REPLACE);
+ EPHEMERAL_SETTINGS.add(TEXT_AUTO_CAPS);
+ EPHEMERAL_SETTINGS.add(TEXT_AUTO_PUNCTUATE);
+ EPHEMERAL_SETTINGS.add(TEXT_SHOW_PASSWORD);
+ EPHEMERAL_SETTINGS.add(DATE_FORMAT);
+ EPHEMERAL_SETTINGS.add(FONT_SCALE);
+ EPHEMERAL_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
+ EPHEMERAL_SETTINGS.add(TIME_12_24);
+ }
+
+ /**
* When to use Wi-Fi calling
*
* @see android.telephony.TelephonyManager.WifiCallingChoices
@@ -5070,6 +5195,9 @@
*
* <p>1 = permit app installation via the system package installer intent
* <p>0 = do not allow use of the package installer
+ * @deprecated Starting from {@link android.os.Build.VERSION_CODES#O}, apps should use
+ * {@link PackageManager#canRequestPackageInstalls()}
+ * @see PackageManager#canRequestPackageInstalls()
*/
public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
@@ -6884,6 +7012,20 @@
}
/**
+ * Secure settings which can be accessed by ephemeral apps.
+ * @hide
+ */
+ public static final Set<String> EPHEMERAL_SETTINGS = new ArraySet<>();
+ static {
+ EPHEMERAL_SETTINGS.add(ENABLED_ACCESSIBILITY_SERVICES);
+ EPHEMERAL_SETTINGS.add(ACCESSIBILITY_SPEAK_PASSWORD);
+ EPHEMERAL_SETTINGS.add(ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+
+ EPHEMERAL_SETTINGS.add(DEFAULT_INPUT_METHOD);
+ EPHEMERAL_SETTINGS.add(ENABLED_INPUT_METHODS);
+ }
+
+ /**
* Helper method for determining if a location provider is enabled.
*
* @param cr the content resolver to use
@@ -7509,6 +7651,21 @@
"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";
+
+ /**
+ * Packages that are whitelisted for background throttling (throttling will not be applied).
+ * @hide
+ */
+ public static final String LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST =
+ "location_background_throttle_package_whitelist";
+
+ /**
* Whether TV will switch to MHL port when a mobile device is plugged in.
* (0 = false, 1 = true)
* @hide
@@ -8028,6 +8185,14 @@
public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
/**
+ * Value to specify whether network quality scores and badging should be shown in the UI.
+ *
+ * Type: int (0 for false, 1 for true)
+ * @hide
+ */
+ public static final String NETWORK_SCORING_UI_ENABLED = "network_scoring_ui_enabled";
+
+ /**
* Value to specify if network recommendations from
* {@link com.android.server.NetworkScoreService} are enabled.
*
@@ -8039,6 +8204,16 @@
"network_recommendations_enabled";
/**
+ * Value to specify if the Wi-Fi Framework should defer to
+ * {@link com.android.server.NetworkScoreService} for evaluating saved open networks.
+ *
+ * Type: int (0 for false, 1 for true)
+ * @hide
+ */
+ @SystemApi
+ public static final String CURATE_SAVED_OPEN_NETWORKS = "curate_saved_open_networks";
+
+ /**
* The number of milliseconds the {@link com.android.server.NetworkScoreService}
* will give a recommendation request to complete before returning a default response.
*
@@ -8632,6 +8807,26 @@
BLUETOOTH_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_";
/**
+ * Activity manager specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "enforce_bg_check=true,max_cached_processes=24"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * enforce_bg_check (boolean)
+ * max_cached_processes (int)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * @hide
+ * @see com.android.server.am.ActivityManagerConstants
+ */
+ public static final String ACTIVITY_MANAGER_CONSTANTS = "activity_manager_constants";
+
+ /**
* Device Idle (Doze) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -8697,6 +8892,25 @@
public static final String APP_IDLE_CONSTANTS = "app_idle_constants";
/**
+ * Power manager specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "no_cached_wake_locks=1"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * no_cached_wake_locks (boolean)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * @hide
+ * @see com.android.server.power.PowerManagerConstants
+ */
+ public static final String POWER_MANAGER_CONSTANTS = "power_manager_constants";
+
+ /**
* Alarm manager specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -9245,7 +9459,7 @@
/**
* WFC mode on roaming network.
* <p>
- * Type: int - see {@link WFC_IMS_MODE} for values
+ * Type: int - see {@link #WFC_IMS_MODE} for values
*
* @hide
*/
@@ -9367,6 +9581,14 @@
public static final String CONTACTS_DATABASE_WAL_ENABLED = "contacts_database_wal_enabled";
/**
+ * Flag to enable the link to location permissions in location setting. Set to 0 to disable.
+ *
+ * @hide
+ */
+ public static final String LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED =
+ "location_settings_link_to_permissions_enabled";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
@@ -9391,10 +9613,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,
@@ -9891,6 +10114,37 @@
* @hide
*/
public static final String CELL_ON = "cell_on";
+
+ /**
+ * Global settings which can be accessed by ephemeral apps.
+ * @hide
+ */
+ public static final Set<String> EPHEMERAL_SETTINGS = new ArraySet<>();
+ static {
+ EPHEMERAL_SETTINGS.add(WAIT_FOR_DEBUGGER);
+ EPHEMERAL_SETTINGS.add(DEVICE_PROVISIONED);
+ EPHEMERAL_SETTINGS.add(DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
+ EPHEMERAL_SETTINGS.add(DEVELOPMENT_FORCE_RTL);
+ 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";
+
+ /**
+ * Whether the diskstats logging task is enabled/disabled.
+ * @hide
+ */
+ public static final String ENABLE_DISKSTATS_LOGGING = "enable_diskstats_logging";
}
/**
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index c3cbcdc..a8b094e 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -30,6 +30,7 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.Voicemail;
+
import java.util.List;
/**
@@ -279,6 +280,44 @@
public static final String LAST_MODIFIED = "last_modified";
/**
+ * Flag to indicate the voicemail was backed up. The value will be 1 if backed up, 0 if
+ * not.
+ *
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String BACKED_UP = "backed_up";
+
+ /**
+ * Flag to indicate the voicemail was restored from a backup. The value will be 1 if
+ * restored, 0 if not.
+ *
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String RESTORED = "restored";
+
+ /**
+ * Flag to indicate the voicemail was marked as archived. Archived voicemail should not be
+ * deleted even if it no longer exist on the server. The value will be 1 if archived true, 0
+ * if not.
+ *
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String ARCHIVED = "archived";
+
+ /**
+ * Flag to indicate the voicemail is a OMTP voicemail handled by the {@link
+ * android.telephony.VisualVoicemailService}. The UI should only show OMTP voicemails from
+ * the current visual voicemail package. For example, the selection could be
+ * {@code WHERE (IS_OMTP_VOICEMAIL == 0) OR ( IS_OMTP_VOICEMAIL == 1 AND SOURCE_PACKAGE ==
+ * "current.vvm.package")}
+ *
+ * <P>Type: INTEGER (boolean)</P>
+ *
+ * @see android.telephony.TelephonyManager#getVisualVoicemailPackageName
+ */
+ public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
+
+ /**
* A convenience method to build voicemail URI specific to a source package by appending
* {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI.
*/
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index eb3d031..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;
@@ -132,6 +138,8 @@
public static final int KM_PURPOSE_DECRYPT = 1;
public static final int KM_PURPOSE_SIGN = 2;
public static final int KM_PURPOSE_VERIFY = 3;
+ public static final int KM_PURPOSE_DERIVE_KEY = 4;
+ public static final int KM_PURPOSE_WRAP_KEY = 5;
// Key formats.
public static final int KM_KEY_FORMAT_X509 = 0;
@@ -202,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;
@@ -247,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/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index 805d8e5..1e4f90d 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -15,8 +15,6 @@
*/
package android.service.autofill;
-import static android.service.voice.VoiceInteractionSession.KEY_FLAGS;
-import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
@@ -30,17 +28,21 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
import android.util.Log;
import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
-// TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the
-// life-cycle (and how state could be maintained on server-side) is well documented.
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+//TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the
+//life-cycle (and how state could be maintained on server-side) is well documented.
/**
* Top-level service of the current auto-fill service for a given user.
@@ -49,8 +51,11 @@
*/
public abstract class AutoFillService extends Service {
- static final String TAG = "AutoFillService";
- static final boolean DEBUG = true; // TODO: set to false once stable
+ private static final String TAG = "AutoFillService";
+ static final boolean DEBUG = true; // TODO(b/33197203): set to false once stable
+
+ // TODO(b/33197203): check for device's memory size instead of DEBUG?
+ static final boolean DEBUG_PENDING_CALLBACKS = DEBUG;
/**
* The {@link Intent} that must be declared as handled by the service.
@@ -77,42 +82,79 @@
// Internal bundle keys.
/** @hide */ public static final String KEY_CALLBACK = "callback";
/** @hide */ public static final String KEY_SAVABLE_IDS = "savable_ids";
+ /** @hide */ public static final String EXTRA_CRYPTO_OBJECT_ID = "crypto_object_id";
// Prefix for public bundle keys.
private static final String KEY_PREFIX = "android.service.autofill.extra.";
/**
* Key of the {@link Bundle} passed to methods such as
- * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
- * containing the extras set by
+ * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by
* {@link android.view.autofill.FillResponse.Builder#setExtras(Bundle)}.
*/
public static final String EXTRA_RESPONSE_EXTRAS = KEY_PREFIX + "RESPONSE_EXTRAS";
/**
* Key of the {@link Bundle} passed to methods such as
- * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
- * containing the extras set by
+ * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by
* {@link android.view.autofill.Dataset.Builder#setExtras(Bundle)}.
*/
public static final String EXTRA_DATASET_EXTRAS = KEY_PREFIX + "DATASET_EXTRAS";
+ /**
+ * Used to indicate the user selected an action that requires authentication.
+ */
+ public static final int FLAG_AUTHENTICATION_REQUESTED = 1 << 0;
+
+ /**
+ * Used to indicate the user authentication succeeded.
+ */
+ public static final int FLAG_AUTHENTICATION_SUCCESS = 1 << 1;
+
+ /**
+ * Used to indicate the user authentication failed.
+ */
+ public static final int FLAG_AUTHENTICATION_ERROR = 1 << 2;
+
+ /**
+ * Used when the service requested Fingerprint authentication but such option is not available.
+ */
+ public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 1 << 3;
+
// Handler messages.
private static final int MSG_CONNECT = 1;
- private static final int MSG_AUTO_FILL_ACTIVITY = 2;
- private static final int MSG_DISCONNECT = 3;
+ private static final int MSG_DISCONNECT = 2;
+ private static final int MSG_AUTO_FILL_ACTIVITY = 3;
+ private static final int MSG_AUTHENTICATE_FILL_RESPONSE = 4;
+ private static final int MSG_AUTHENTICATE_DATASET = 5;
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
public void autoFill(AssistStructure structure, IAutoFillServerCallback callback,
- Bundle extras, int flags) {
+ int flags) {
mHandlerCaller
- .obtainMessageIOOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, extras, callback)
+ .obtainMessageIOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, callback)
.sendToTarget();
}
@Override
+ public void authenticateFillResponse(Bundle extras, int flags) {
+ final Message msg = mHandlerCaller.obtainMessage(MSG_AUTHENTICATE_FILL_RESPONSE);
+ msg.arg1 = flags;
+ msg.obj = extras;
+ mHandlerCaller.sendMessage(msg);
+ }
+
+ @Override
+ public void authenticateDataset(Bundle extras, int flags) {
+ final Message msg = mHandlerCaller.obtainMessage(MSG_AUTHENTICATE_DATASET);
+ msg.arg1 = flags;
+ msg.obj = extras;
+ mHandlerCaller.sendMessage(msg);
+ }
+
+ @Override
public void onConnected() {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CONNECT));
}
@@ -135,9 +177,18 @@
final SomeArgs args = (SomeArgs) msg.obj;
final int flags = msg.arg1;
final AssistStructure structure = (AssistStructure) args.arg1;
- final Bundle extras = (Bundle) args.arg2;
- final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg3;
- requestAutoFill(callback, structure, extras, flags);
+ final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg2;
+ requestAutoFill(callback, structure, flags);
+ break;
+ } case MSG_AUTHENTICATE_FILL_RESPONSE: {
+ final int flags = msg.arg1;
+ final Bundle extras = (Bundle) msg.obj;
+ onFillResponseAuthenticationRequest(extras, flags);
+ break;
+ } case MSG_AUTHENTICATE_DATASET: {
+ final int flags = msg.arg1;
+ final Bundle extras = (Bundle) msg.obj;
+ onDatasetAuthenticationRequest(extras, flags);
break;
} case MSG_DISCONNECT: {
onDisconnected();
@@ -151,6 +202,10 @@
private HandlerCaller mHandlerCaller;
+ // User for debugging purposes
+ private final List<CallbackHelper.Dumpable> mPendingCallbacks =
+ DEBUG_PENDING_CALLBACKS ? new ArrayList<>() : null;
+
/**
* {@inheritDoc}
*
@@ -196,8 +251,8 @@
* @param cancellationSignal signal for observing cancel requests.
* @param callback object used to notify the result of the request.
*/
- public abstract void onFillRequest(AssistStructure structure,
- Bundle data, CancellationSignal cancellationSignal, FillCallback callback);
+ public abstract void onFillRequest(AssistStructure structure, Bundle data,
+ CancellationSignal cancellationSignal, FillCallback callback);
/**
* Called when user requests service to save the fields of an {@link Activity}.
@@ -209,28 +264,112 @@
* @param structure {@link Activity}'s view structure.
* @param data bundle containing additional arguments set by the Android system (currently none)
* or data passed by the service in the {@link FillResponse} that originated this call.
- * @param cancellationSignal signal for observing cancel requests.
* @param callback object used to notify the result of the request.
*/
- public abstract void onSaveRequest(AssistStructure structure,
- Bundle data, CancellationSignal cancellationSignal, SaveCallback callback);
+ public abstract void onSaveRequest(AssistStructure structure, Bundle data,
+ SaveCallback callback);
+
+ /**
+ * Called as result of the user action for a {@link FillResponse} that required authentication.
+ *
+ * <p>When the {@link FillResponse} required authentication through
+ * {@link android.view.autofill.FillResponse.Builder#requiresCustomAuthentication(Bundle, int)},
+ * this call indicates the user is requesting the service to authenticate him/her (and
+ * {@code flags} contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains
+ * the {@link Bundle} passed to that method.
+ *
+ * <p>When the {@link FillResponse} required authentication through
+ * {@link android.view.autofill.FillResponse.Builder#requiresFingerprintAuthentication(
+ * android.hardware.fingerprint.FingerprintManager.CryptoObject, Bundle, int)},
+ * {@code flags} this call contains the result of the fingerprint authentication (such as
+ * {@link #FLAG_AUTHENTICATION_SUCCESS}, {@link #FLAG_AUTHENTICATION_ERROR}, and
+ * {@link #FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}) and {@code extras} contains the
+ * {@link Bundle} passed to that method.
+ */
+ public void onFillResponseAuthenticationRequest(@SuppressWarnings("unused") Bundle extras,
+ int flags) {
+ if (DEBUG) Log.d(TAG, "onFillResponseAuthenticationRequest(): flags=" + flags);
+ }
+
+ /**
+ * Called as result of the user action for a {@link Dataset} that required authentication.
+ *
+ * <p>When the {@link Dataset} required authentication through
+ * {@link android.view.autofill.Dataset.Builder#requiresCustomAuthentication(Bundle, int)}, this
+ * call indicates the user is requesting the service to authenticate him/her (and {@code flags}
+ * contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains the
+ * {@link Bundle} passed to that method.
+ *
+ * <p>When the {@link Dataset} required authentication through
+ * {@link android.view.autofill.Dataset.Builder#requiresFingerprintAuthentication(
+ * android.hardware.fingerprint.FingerprintManager.CryptoObject, Bundle, int)},
+ * {@code flags} this call contains the result of the fingerprint authentication (such as
+ * {@link #FLAG_AUTHENTICATION_SUCCESS}, {@link #FLAG_AUTHENTICATION_ERROR}, and
+ * {@link #FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}) and {@code extras} contains the
+ * {@link Bundle} passed to that method.
+ */
+ public void onDatasetAuthenticationRequest(@SuppressWarnings("unused") Bundle extras,
+ int flags) {
+ if (DEBUG) Log.d(TAG, "onDatasetAuthenticationRequest(): flags=" + flags);
+ }
+
+ // TODO(b/33197203): make it final and create another method classes could extend so it's
+ // guaranteed to dump the pending callbacks?
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mPendingCallbacks != null) {
+ pw.print("Number of pending callbacks: "); pw.println(mPendingCallbacks.size());
+ final String prefix = " ";
+ for (int i = 0; i < mPendingCallbacks.size(); i++) {
+ final CallbackHelper.Dumpable cb = mPendingCallbacks.get(i);
+ pw.print('#'); pw.print(i + 1); pw.println(':');
+ cb.dump(prefix, pw);
+ }
+ pw.println();
+ } else {
+ pw.println("Dumping disabled");
+ }
+ }
private void requestAutoFill(IAutoFillServerCallback callback, AssistStructure structure,
- Bundle data, int flags) {
- switch (flags) {
- case AUTO_FILL_FLAG_TYPE_FILL:
- final FillCallback fillCallback = new FillCallback(callback);
- // TODO(b/33197203): hook up the cancelationSignal
- onFillRequest(structure, data, new CancellationSignal(), fillCallback);
- break;
- case AUTO_FILL_FLAG_TYPE_SAVE:
- final SaveCallback saveCallback = new SaveCallback(callback);
- // TODO(b/33197203): hook up the cancelationSignal
- onSaveRequest(structure, data, new CancellationSignal(), saveCallback);
- break;
- default:
- Log.w(TAG, "invalid flag on requestAutoFill(): " + flags);
+ int flags) {
+ if (DEBUG) Log.d(TAG, "requestAutoFill(): flags=" + flags);
+
+ if ((flags & AUTO_FILL_FLAG_TYPE_FILL) != 0) {
+ final FillCallback fillCallback = new FillCallback(callback);
+ if (DEBUG_PENDING_CALLBACKS) {
+ addPendingCallback(fillCallback);
+ }
+ // TODO(b/33197203): hook up the cancelationSignal
+ onFillRequest(structure, null, new CancellationSignal(), fillCallback);
+ return;
}
+ if ((flags & AUTO_FILL_FLAG_TYPE_SAVE) != 0) {
+ final SaveCallback saveCallback = new SaveCallback(callback);
+ if (DEBUG_PENDING_CALLBACKS) {
+ addPendingCallback(saveCallback);
+ }
+ onSaveRequest(structure, null, saveCallback);
+ return;
+ }
+
+ Log.w(TAG, "invalid flags on requestAutoFill(): " + flags);
+ }
+
+ private void addPendingCallback(CallbackHelper.Dumpable callback) {
+ if (mPendingCallbacks == null) {
+ // Shouldn't happend since call is controlled by DEBUG_PENDING_CALLBACKS guard.
+ Log.wtf(TAG, "addPendingCallback(): mPendingCallbacks not set");
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Adding pending callback: " + callback);
+
+ callback.setFinalizer(() -> {
+ if (DEBUG) Log.d(TAG, "Removing pending callback: " + callback);
+ mPendingCallbacks.remove(callback);
+ });
+ mPendingCallbacks.add(callback);
}
/**
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/autofill/CallbackHelper.java b/core/java/android/service/autofill/CallbackHelper.java
new file mode 100644
index 0000000..ded8f97
--- /dev/null
+++ b/core/java/android/service/autofill/CallbackHelper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import java.io.PrintWriter;
+
+final class CallbackHelper {
+
+ static interface Dumpable {
+ void dump(String prefix, PrintWriter pw);
+ void setFinalizer(Finalizer f);
+ }
+
+ static interface Finalizer {
+ void gone();
+ }
+}
diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java
index 5a9a9f6..7cab7ae 100644
--- a/core/java/android/service/autofill/FillCallback.java
+++ b/core/java/android/service/autofill/FillCallback.java
@@ -17,28 +17,49 @@
package android.service.autofill;
import static android.service.autofill.AutoFillService.DEBUG;
+import static android.util.DebugUtils.flagsToString;
import android.annotation.Nullable;
import android.app.Activity;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.RemoteException;
+import android.service.autofill.CallbackHelper.Dumpable;
+import android.service.autofill.CallbackHelper.Finalizer;
import android.util.Log;
+import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.io.PrintWriter;
+
/**
* Handles auto-fill requests from the {@link AutoFillService} into the {@link Activity} being
* auto-filled.
+ *
+ * <p>This class is thread safe.
*/
-public final class FillCallback {
+public final class FillCallback implements Dumpable {
private static final String TAG = "FillCallback";
+ // NOTE: constants below are public so they can be used by flagsToString()
+ /** @hide */ public static final int STATE_INITIAL = 1 << 0;
+ /** @hide */ public static final int STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE = 1 << 1;
+ /** @hide */ public static final int STATE_WAITING_DATASET_AUTH_RESPONSE = 1 << 2;
+ /** @hide */ public static final int STATE_FINISHED_OK = 1 << 3;
+ /** @hide */ public static final int STATE_FINISHED_FAILURE = 1 << 4;
+ /** @hide */ public static final int STATE_FINISHED_ERROR = 1 << 5;
+ /** @hide */ public static final int STATE_FINISHED_AUTHENTICATED = 1 << 6;
+
private final IAutoFillServerCallback mCallback;
- private boolean mReplied = false;
+ @GuardedBy("mCallback")
+ private int mState = STATE_INITIAL;
+
+ @GuardedBy("mCallback")
+ private Finalizer mFinalizer;
/** @hide */
FillCallback(IAutoFillServerCallback callback) {
@@ -47,51 +68,174 @@
/**
* Notifies the Android System that an
- * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, FillCallback)}
- * was successfully fulfilled by the service.
+ * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle,
+ * android.os.CancellationSignal, FillCallback)} was successfully fulfilled by the service.
*
* @param response auto-fill information for that activity, or {@code null} when the activity
- * cannot be auto-filled (for example, if it only contains read-only fields).
- *
- * @throws RuntimeException if an error occurred while calling the Android System.
+ * cannot be auto-filled (for example, if it only contains read-only fields). See
+ * {@link FillResponse} for examples.
*/
public void onSuccess(@Nullable FillResponse response) {
- if (DEBUG) Log.d(TAG, "onSuccess(): respose=" + response);
+ final boolean authRequired = response != null && response.isAuthRequired();
- checkNotRepliedYet();
+ if (DEBUG) Log.d(TAG, "onSuccess(): authReq= " + authRequired + ", resp=" + response);
- try {
- mCallback.showResponse(response);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ synchronized (mCallback) {
+ if (authRequired) {
+ assertOnStateLocked(STATE_INITIAL);
+ } else {
+ assertOnStateLocked(STATE_INITIAL | STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE
+ | STATE_WAITING_DATASET_AUTH_RESPONSE);
+ }
+
+ try {
+ mCallback.showResponse(response);
+ if (authRequired) {
+ mState = STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE;
+ } else {
+ // Check if at least one dataset requires authentication.
+ boolean waitingAuth = false;
+ if (response != null) {
+ for (Dataset dataset : response.getDatasets()) {
+ if (dataset.isAuthRequired()) {
+ waitingAuth = true;
+ break;
+ }
+ }
+ }
+ if (waitingAuth) {
+ mState = STATE_WAITING_DATASET_AUTH_RESPONSE;
+ } else {
+ setFinalStateLocked(STATE_FINISHED_OK);
+ }
+ }
+ } catch (RemoteException e) {
+ setFinalStateLocked(STATE_FINISHED_ERROR);
+ e.rethrowAsRuntimeException();
+ }
}
}
/**
* Notifies the Android System that an
- * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, FillCallback)}
+ * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, FillCallback)}
* could not be fulfilled by the service.
*
* @param message error message to be displayed to the user.
- *
- * @throws RuntimeException if an error occurred while calling the Android System.
*/
public void onFailure(CharSequence message) {
if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
- checkNotRepliedYet();
Preconditions.checkArgument(message != null, "message cannot be null");
- try {
- mCallback.showError(message.toString());
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ synchronized (mCallback) {
+ assertOnStateLocked(STATE_INITIAL | STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE
+ | STATE_WAITING_DATASET_AUTH_RESPONSE);
+
+ try {
+ mCallback.showError(message);
+ setFinalStateLocked(STATE_FINISHED_FAILURE);
+ } catch (RemoteException e) {
+ setFinalStateLocked(STATE_FINISHED_ERROR);
+ e.rethrowAsRuntimeException();
+ }
}
}
- // There can be only one!!
- private void checkNotRepliedYet() {
- Preconditions.checkState(!mReplied, "already replied");
- mReplied = true;
+ /**
+ * Notifies the Android System when the user authenticated a {@link FillResponse} previously
+ * passed to {@link #onSuccess(FillResponse)}.
+ *
+ * @param flags must contain either
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} or
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}.
+ */
+ public void onFillResponseAuthentication(int flags) {
+ if (DEBUG) Log.d(TAG, "onFillResponseAuthentication(): flags=" + flags);
+
+ synchronized (mCallback) {
+ assertOnStateLocked(STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE);
+
+ try {
+ mCallback.unlockFillResponse(flags);
+ setFinalStateLocked(STATE_FINISHED_AUTHENTICATED);
+ } catch (RemoteException e) {
+ setFinalStateLocked(STATE_FINISHED_ERROR);
+ e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ /**
+ * Notifies the Android System when the user authenticated a {@link Dataset} previously passed
+ * to {@link #onSuccess(FillResponse)}.
+ *
+ * @param dataset values to fill the activity with in case of successful authentication of a
+ * previously locked (and empty) dataset).
+ * @param flags must contain either
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} or
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}.
+ */
+ public void onDatasetAuthentication(@Nullable Dataset dataset, int flags) {
+ if (DEBUG) Log.d(TAG, "onDatasetAuthentication(): dataset=" + dataset + ", flags=" + flags);
+
+ synchronized (mCallback) {
+ assertOnStateLocked(STATE_WAITING_DATASET_AUTH_RESPONSE);
+
+ try {
+ mCallback.unlockDataset(dataset, flags);
+ setFinalStateLocked(STATE_FINISHED_AUTHENTICATED);
+ } catch (RemoteException e) {
+ setFinalStateLocked(STATE_FINISHED_ERROR);
+ e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ return "FillCallback: [mState = " + mState + "]";
+ }
+
+ /** @hide */
+ @Override
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("FillCallback: mState="); pw.println(mState);
+ }
+
+ /** @hide */
+ @Override
+ public void setFinalizer(Finalizer f) {
+ synchronized (mCallback) {
+ mFinalizer = f;
+ }
+ }
+
+ /**
+ * Sets a final state (where the callback cannot be used anymore) and notifies the
+ * {@link Finalizer} (if any).
+ */
+ private void setFinalStateLocked(int state) {
+ if (DEBUG) Log.d(TAG, "setFinalState(): " + state);
+ mState = state;
+
+ if (mFinalizer != null) {
+ mFinalizer.gone();
+ }
+ }
+
+ // TODO(b/33197203): move and/or re-add state check logic on server side to avoid malicious app
+ // calling the callback on wrong state.
+
+ // Make sure callback method is called during the proper lifecycle state.
+ private void assertOnStateLocked(int flags) {
+ if (DEBUG) Log.d(TAG, "assertOnState(): current=" + mState + ", required=" + flags);
+
+ Preconditions.checkState((flags & mState) != 0,
+ "invalid state: required " + flagsToString(FillCallback.class, "STATE_", flags)
+ + ", current is " + flagsToString(FillCallback.class, "STATE_", mState));
}
}
diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
index 629b1f0..8c3898a 100644
--- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
@@ -21,8 +21,14 @@
import android.view.autofill.Dataset;
/**
+ * Object running in the application process and responsible for auto-filling it.
+ *
* @hide
*/
+// TODO(b/33197203): rename IAutoFillAppSession
oneway interface IAutoFillAppCallback {
+ /**
+ * Auto-fills the activity with the contents of a dataset.
+ */
void autoFill(in Dataset dataset);
}
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index f8ae57b..ace5411 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -16,7 +16,9 @@
package android.service.autofill;
+import android.graphics.Rect;
import android.os.Bundle;
+import android.view.autofill.AutoFillId;
/**
* Mediator between apps being auto-filled and auto-fill service implementations.
@@ -24,5 +26,10 @@
* {@hide}
*/
oneway interface IAutoFillManagerService {
- void requestAutoFill(IBinder activityToken, int userId, in Bundle extras, int flags);
+
+ // Called by AutoFillManager (app).
+ void requestAutoFill(in AutoFillId id, in Rect bounds, int flags);
+
+ // Called by ShellCommand only.
+ void requestAutoFillForUser(int userId, int flags);
}
diff --git a/core/java/android/service/autofill/IAutoFillServerCallback.aidl b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
index 9d58c99..f7d5064 100644
--- a/core/java/android/service/autofill/IAutoFillServerCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
@@ -18,14 +18,22 @@
import java.util.List;
+import android.os.Bundle;
import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
/**
+ * Object running in the AutoFillService process and used to communicate back with system_server.
+ *
* @hide
*/
+// TODO(b/33197203): rename to IAutoFillServerSession
oneway interface IAutoFillServerCallback {
+ // TODO(b/33197203): document methods
void showResponse(in FillResponse response);
- void showError(String message);
+ void showError(CharSequence message);
void highlightSavedFields(in AutoFillId[] ids);
+ void unlockFillResponse(int flags);
+ void unlockDataset(in Dataset dataset, int flags);
}
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index a1f22bf..3e8087b 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -24,9 +24,12 @@
/**
* @hide
*/
+// TODO(b/33197203): document class and methods
oneway interface IAutoFillService {
- void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback,
- in Bundle extras, int flags);
+ // TODO(b/33197203): rename method to make them more consistent
+ void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback, int flags);
+ void authenticateFillResponse(in Bundle extras, int flags);
+ void authenticateDataset(in Bundle extras, int flags);
void onConnected();
void onDisconnected();
}
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 627d74c..e2fb588 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -21,25 +21,35 @@
import android.app.Activity;
import android.app.assist.AssistStructure.ViewNode;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.RemoteException;
+import android.service.autofill.CallbackHelper.Dumpable;
+import android.service.autofill.CallbackHelper.Finalizer;
import android.util.Log;
import android.view.autofill.AutoFillId;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.io.PrintWriter;
+
/**
* Handles save requests from the {@link AutoFillService} into the {@link Activity} being
* auto-filled.
+ *
+ * <p>This class is thread safe.
*/
-public final class SaveCallback {
+public final class SaveCallback implements Dumpable {
private static final String TAG = "SaveCallback";
private final IAutoFillServerCallback mCallback;
+ @GuardedBy("mCallback")
private boolean mReplied = false;
+ @GuardedBy("mCallback")
+ private Finalizer mFinalizer;
+
/** @hide */
SaveCallback(IAutoFillServerCallback callback) {
mCallback = callback;
@@ -47,8 +57,8 @@
/**
* Notifies the Android System that an
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, SaveCallback)}
- * was successfully fulfilled by the service.
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+ * SaveCallback)} was successfully fulfilled by the service.
*
* @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved.
*
@@ -58,21 +68,24 @@
if (DEBUG) Log.d(TAG, "onSuccess(): ids=" + ((ids == null) ? "null" : ids.length));
Preconditions.checkArgument(ids != null, "ids cannot be null");
- checkNotRepliedYet();
-
Preconditions.checkArgument(ids.length > 0, "ids cannot be empty");
- try {
- mCallback.highlightSavedFields(ids);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ synchronized (mCallback) {
+ checkNotRepliedYetLocked();
+ try {
+ mCallback.highlightSavedFields(ids);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ } finally {
+ setRepliedLocked();
+ }
}
}
/**
* Notifies the Android System that an
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, SaveCallback)}
- * could not be fulfilled by the service.
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+ * SaveCallback)} could not be fulfilled by the service.
*
* @param message error message to be displayed to the user.
*
@@ -82,18 +95,53 @@
if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
Preconditions.checkArgument(message != null, "message cannot be null");
- checkNotRepliedYet();
- try {
- mCallback.showError(message.toString());
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ synchronized (mCallback) {
+ checkNotRepliedYetLocked();
+
+ try {
+ mCallback.showError(message);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ } finally {
+ setRepliedLocked();
+ }
}
}
+ /** @hide */
+ @Override
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("SaveCallback: mReplied="); pw.println(mReplied);
+ }
+
+ /** @hide */
+ @Override
+ public void setFinalizer(Finalizer f) {
+ synchronized (mCallback) {
+ mFinalizer = f;
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ return "SaveCallback: [mReplied= " + mReplied + "]";
+ }
+
// There can be only one!!
- private void checkNotRepliedYet() {
+ private void checkNotRepliedYetLocked() {
Preconditions.checkState(!mReplied, "already replied");
+ }
+
+ private void setRepliedLocked() {
+ if (DEBUG) Log.d(TAG, "setReplied()");
+
mReplied = true;
+
+ if (mFinalizer != null) {
+ mFinalizer.gone();
+ }
}
}
diff --git a/core/java/android/service/gatekeeper/GateKeeperResponse.java b/core/java/android/service/gatekeeper/GateKeeperResponse.java
index a512957..287dc76 100644
--- a/core/java/android/service/gatekeeper/GateKeeperResponse.java
+++ b/core/java/android/service/gatekeeper/GateKeeperResponse.java
@@ -19,6 +19,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Response object for a GateKeeper verification request.
* @hide
@@ -35,12 +37,28 @@
private byte[] mPayload;
private boolean mShouldReEnroll;
+ /** Default constructor for response with generic response code **/
private GateKeeperResponse(int responseCode) {
mResponseCode = responseCode;
}
- private GateKeeperResponse(int responseCode, int timeout) {
- mResponseCode = responseCode;
+ @VisibleForTesting
+ public static GateKeeperResponse createGenericResponse(int responseCode) {
+ return new GateKeeperResponse(responseCode);
+ }
+
+ private static GateKeeperResponse createRetryResponse(int timeout) {
+ GateKeeperResponse response = new GateKeeperResponse(RESPONSE_RETRY);
+ response.mTimeout = timeout;
+ return response;
+ }
+
+ @VisibleForTesting
+ public static GateKeeperResponse createOkResponse(byte[] payload, boolean shouldReEnroll) {
+ GateKeeperResponse response = new GateKeeperResponse(RESPONSE_OK);
+ response.mPayload = payload;
+ response.mShouldReEnroll = shouldReEnroll;
+ return response;
}
@Override
@@ -53,17 +71,20 @@
@Override
public GateKeeperResponse createFromParcel(Parcel source) {
int responseCode = source.readInt();
- GateKeeperResponse response = new GateKeeperResponse(responseCode);
+ final GateKeeperResponse response;
if (responseCode == RESPONSE_RETRY) {
- response.setTimeout(source.readInt());
+ response = createRetryResponse(source.readInt());
} else if (responseCode == RESPONSE_OK) {
- response.setShouldReEnroll(source.readInt() == 1);
+ final boolean shouldReEnroll = source.readInt() == 1;
+ byte[] payload = null;
int size = source.readInt();
if (size > 0) {
- byte[] payload = new byte[size];
+ payload = new byte[size];
source.readByteArray(payload);
- response.setPayload(payload);
}
+ response = createOkResponse(payload, shouldReEnroll);
+ } else {
+ response = createGenericResponse(responseCode);
}
return response;
}
@@ -104,17 +125,4 @@
public int getResponseCode() {
return mResponseCode;
}
-
- private void setTimeout(int timeout) {
- mTimeout = timeout;
- }
-
- private void setShouldReEnroll(boolean shouldReEnroll) {
- mShouldReEnroll = shouldReEnroll;
- }
-
- private void setPayload(byte[] payload) {
- mPayload = payload;
- }
-
}
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..22ad83a 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.
*
@@ -937,7 +925,7 @@
}
/**
- * Request that the listener be rebound, after a previous call to (@link requestUnbind).
+ * Request that the listener be rebound, after a previous call to {@link #requestUnbind}.
*
* <p>This method will fail for listeners that have
* not been granted the permission by the user.
@@ -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/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl
index f07d0d0..22b4d09 100644
--- a/core/java/android/service/trust/ITrustAgentService.aidl
+++ b/core/java/android/service/trust/ITrustAgentService.aidl
@@ -24,6 +24,7 @@
*/
interface ITrustAgentService {
oneway void onUnlockAttempt(boolean successful);
+ oneway void onUnlockLockout(int timeoutMs);
oneway void onTrustTimeout();
oneway void onDeviceLocked();
oneway void onDeviceUnlocked();
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index 9d7ffad..0d5177d 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -123,6 +123,7 @@
private static final int MSG_TRUST_TIMEOUT = 3;
private static final int MSG_DEVICE_LOCKED = 4;
private static final int MSG_DEVICE_UNLOCKED = 5;
+ private static final int MSG_UNLOCK_LOCKOUT = 6;
/**
* Class containing raw data for a given configuration request.
@@ -151,6 +152,9 @@
case MSG_UNLOCK_ATTEMPT:
onUnlockAttempt(msg.arg1 != 0);
break;
+ case MSG_UNLOCK_LOCKOUT:
+ onDeviceUnlockLockout(msg.arg1);
+ break;
case MSG_CONFIGURE:
ConfigurationData data = (ConfigurationData) msg.obj;
boolean result = onConfigure(data.options);
@@ -226,6 +230,21 @@
public void onDeviceUnlocked() {
}
+ /**
+ * Called when the device enters a temporary unlock lockout.
+ *
+ * <p>This occurs when the user has consecutively failed to unlock the device too many times,
+ * and must wait until a timeout has passed to perform another attempt. The user may then only
+ * use strong authentication mechanisms (PIN, pattern or password) to unlock the device.
+ * Calls to {@link #grantTrust(CharSequence, long, int)} will be ignored until the user has
+ * unlocked the device and {@link #onDeviceUnlocked()} is called.
+ *
+ * @param timeoutMs The amount of time, in milliseconds, that needs to elapse before the user
+ * can attempt to unlock the device again.
+ */
+ public void onDeviceUnlockLockout(long timeoutMs) {
+ }
+
private void onError(String msg) {
Slog.v(TAG, "Remote exception while " + msg);
}
@@ -366,6 +385,11 @@
mHandler.obtainMessage(MSG_UNLOCK_ATTEMPT, successful ? 1 : 0, 0).sendToTarget();
}
+ @Override
+ public void onUnlockLockout(int timeoutMs) {
+ mHandler.obtainMessage(MSG_UNLOCK_LOCKOUT, timeoutMs, 0).sendToTarget();
+ }
+
@Override /* Binder API */
public void onTrustTimeout() {
mHandler.sendEmptyMessage(MSG_TRUST_TIMEOUT);
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/Editable.java b/core/java/android/text/Editable.java
index c0948a6..3396bce 100644
--- a/core/java/android/text/Editable.java
+++ b/core/java/android/text/Editable.java
@@ -94,10 +94,10 @@
public Editable append(char text);
/**
- * Convenience for replace(0, length(), "", 0, 0)
- * @see #replace(int, int, CharSequence, int, int)
+ * Convenience for replace(0, length(), "", 0, 0).
* Note that this clears the text, not the spans;
* use {@link #clearSpans} if you need that.
+ * @see #replace(int, int, CharSequence, int, int)
*/
public void clear();
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/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index 856dd0b..b0bff68 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -43,7 +43,7 @@
}
/**
- * @deprecated Use {@link #ImageSpan(Context, Bitmap, int) instead.
+ * @deprecated Use {@link #ImageSpan(Context, Bitmap, int)} instead.
*/
@Deprecated
public ImageSpan(Bitmap b, int verticalAlignment) {
diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java
index c5e5df0..d72a48d 100644
--- a/core/java/android/text/style/TtsSpan.java
+++ b/core/java/android/text/style/TtsSpan.java
@@ -798,7 +798,7 @@
/**
* Creates a TtsSpan of type {@link #TYPE_DECIMAL} and sets the
* {@link #ARG_INTEGER_PART} and {@link #ARG_FRACTIONAL_PART} arguments.
- * @see {@link #setArgumentsFromDouble(double, int, int)
+ * @see #setArgumentsFromDouble(double, int, int)
*/
public DecimalBuilder(double number,
int minimumFractionDigits,
@@ -1082,7 +1082,7 @@
* Sets the {@link #ARG_UNIT} argument.
* @param unit The unit of the measure.
* @return This instance.
- * @see {@link TtsSpan.ARG_UNIT}
+ * @see TtsSpan.ARG_UNIT
*/
public MeasureBuilder setUnit(String unit) {
return setStringArgument(TtsSpan.ARG_UNIT, unit);
@@ -1116,7 +1116,7 @@
* Sets the {@link #ARG_HOURS} argument.
* @param hours The value to be set for hours. See {@link #ARG_HOURS}.
* @return This instance.
- * @see {@link #ARG_HOURS}
+ * @see #ARG_HOURS
*/
public TimeBuilder setHours(int hours) {
return setIntArgument(TtsSpan.ARG_HOURS, hours);
@@ -1127,7 +1127,7 @@
* @param minutes The value to be set for minutes. See
* {@link #ARG_MINUTES}.
* @return This instance.
- * @see {@link #ARG_MINUTES}
+ * @see #ARG_MINUTES
*/
public TimeBuilder setMinutes(int minutes) {
return setIntArgument(TtsSpan.ARG_MINUTES, minutes);
@@ -1177,7 +1177,7 @@
* @param weekday The value to be set for weekday. See
* {@link #ARG_WEEKDAY}.
* @return This instance.
- * @see {@link #ARG_WEEKDAY}
+ * @see #ARG_WEEKDAY
*/
public DateBuilder setWeekday(int weekday) {
return setIntArgument(TtsSpan.ARG_WEEKDAY, weekday);
@@ -1187,7 +1187,7 @@
* Sets the {@link #ARG_DAY} argument.
* @param day The value to be set for day. See {@link #ARG_DAY}.
* @return This instance.
- * @see {@link #ARG_DAY}
+ * @see #ARG_DAY
*/
public DateBuilder setDay(int day) {
return setIntArgument(TtsSpan.ARG_DAY, day);
@@ -1197,7 +1197,7 @@
* Sets the {@link #ARG_MONTH} argument.
* @param month The value to be set for month. See {@link #ARG_MONTH}.
* @return This instance.
- * @see {@link #ARG_MONTH}
+ * @see #ARG_MONTH
*/
public DateBuilder setMonth(int month) {
return setIntArgument(TtsSpan.ARG_MONTH, month);
@@ -1207,7 +1207,7 @@
* Sets the {@link #ARG_YEAR} argument.
* @param year The value to be set for year. See {@link #ARG_YEAR}.
* @return This instance.
- * @see {@link #ARG_YEAR}
+ * @see #ARG_YEAR
*/
public DateBuilder setYear(int year) {
return setIntArgument(TtsSpan.ARG_YEAR, year);
diff --git a/core/java/android/util/ByteStringUtils.java b/core/java/android/util/ByteStringUtils.java
index 7103e6d..333208d 100644
--- a/core/java/android/util/ByteStringUtils.java
+++ b/core/java/android/util/ByteStringUtils.java
@@ -33,7 +33,7 @@
* @param bytes Byte array to encode.
* @return Hex encoded string representation of bytes.
*/
- public static String toString(byte[] bytes) {
+ public static String toHexString(byte[] bytes) {
if (bytes == null || bytes.length == 0 || bytes.length % 2 != 0) {
return null;
}
@@ -55,7 +55,7 @@
* @param str Hex encoded string to decode.
* @return Decoded byte array representation of str.
*/
- public static byte[] toByteArray(String str) {
+ public static byte[] fromHexToByteArray(String str) {
if (str == null || str.length() == 0 || str.length() % 2 != 0) {
return null;
}
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/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index f5d515d..da0b609 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -16,6 +16,8 @@
package android.util;
+import android.os.ParcelableException;
+
import java.io.IOException;
/**
@@ -24,19 +26,13 @@
* @hide
*/
public class ExceptionUtils {
- // TODO: longer term these should be replaced with first-class
- // Parcel.read/writeException() and AIDL support, but for now do this using
- // a nasty hack.
-
- private static final String PREFIX_IO = "\u2603";
-
public static RuntimeException wrap(IOException e) {
- throw new IllegalStateException(PREFIX_IO + e.getMessage());
+ throw new ParcelableException(e);
}
public static void maybeUnwrapIOException(RuntimeException e) throws IOException {
- if ((e instanceof IllegalStateException) && e.getMessage().startsWith(PREFIX_IO)) {
- throw new IOException(e.getMessage().substring(PREFIX_IO.length()));
+ if (e instanceof ParcelableException) {
+ ((ParcelableException) e).maybeRethrow(IOException.class);
}
}
diff --git a/core/java/android/util/KeyValueListParser.java b/core/java/android/util/KeyValueListParser.java
index e4c025d..be531ff 100644
--- a/core/java/android/util/KeyValueListParser.java
+++ b/core/java/android/util/KeyValueListParser.java
@@ -129,4 +129,22 @@
}
return def;
}
+
+ /**
+ * Get the value for key as a boolean.
+ * @param key The key to lookup.
+ * @param def The value to return if the key was not found.
+ * @return the string value associated with the key.
+ */
+ public boolean getBoolean(String key, boolean def) {
+ String value = mValues.get(key);
+ if (value != null) {
+ try {
+ return Boolean.parseBoolean(value);
+ } catch (NumberFormatException e) {
+ // fallthrough
+ }
+ }
+ return def;
+ }
}
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 3181979..0fe56f6 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -80,6 +80,6 @@
messageDigest.update(data);
- return ByteStringUtils.toString(messageDigest.digest());
+ return ByteStringUtils.toHexString(messageDigest.digest());
}
}
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 37d6757..0a294ab 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -19,7 +19,6 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.SystemClock;
-import android.text.format.DateUtils;
import com.android.internal.util.XmlUtils;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index b37ea8e..ebb9cf9 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -163,7 +163,7 @@
/**
* Display flag: Indicates that the display is private. Only the application that
- * owns the display can create windows on it.
+ * owns the display and apps that are already on the display can create windows on it.
*
* @see #getFlags
*/
@@ -194,6 +194,19 @@
public static final int FLAG_ROUND = 1 << 4;
/**
+ * Display flag: Indicates that the display can show its content when non-secure keyguard is
+ * shown.
+ * <p>
+ * This flag identifies secondary displays that won't show keyguard if it can be dismissed
+ * without entering credentials. Display content will be shown even if other displays are
+ * locked.
+ * </p>
+ *
+ * @see #getFlags
+ */
+ public static final int FLAG_SHOW_WITH_INSECURE_LOCKSCREEN = 1 << 5;
+
+ /**
* Display flag: Indicates that the contents of the display should not be scaled
* to fit the physical screen dimensions. Used for development only to emulate
* devices with smaller physicals screens while preserving density.
@@ -292,7 +305,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 +319,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 +771,8 @@
/**
* Returns the display's HDR capabilities.
+ *
+ * @see #isHdr()
*/
public HdrCapabilities getHdrCapabilities() {
synchronized (this) {
@@ -754,6 +782,29 @@
}
/**
+ * Returns whether this display supports any HDR type.
+ *
+ * @see #getHdrCapabilities()
+ * @see HdrCapabilities#getSupportedHdrTypes()
+ */
+ public boolean isHdr() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.isHdr();
+ }
+ }
+
+ /**
+ * Returns whether this display can be used to display wide color gamut content.
+ */
+ public boolean isWideColorGamut() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.isWideColorGamut();
+ }
+ }
+
+ /**
* Gets the supported color modes of this device.
* @hide
*/
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 1aef6ec..f6b94af 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -519,6 +519,20 @@
logicalHeight : logicalWidth;
}
+ public boolean isHdr() {
+ int[] types = hdrCapabilities != null ? hdrCapabilities.getSupportedHdrTypes() : null;
+ return types != null && types.length > 0;
+ }
+
+ public boolean isWideColorGamut() {
+ for (int colorMode : supportedColorModes) {
+ if (colorMode == Display.COLOR_MODE_DCI_P3 || colorMode > Display.COLOR_MODE_SRGB) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns true if the specified UID has access to this display.
*/
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/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 800a63f..92f0e8f 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -137,7 +137,7 @@
/**
* Identifiers for metrics available for each frame.
*
- * {@see {@link #getMetric(int)}}
+ * {@see #getMetric(int)}
* @hide
*/
@IntDef({
diff --git a/core/java/android/view/IDockedStackListener.aidl b/core/java/android/view/IDockedStackListener.aidl
index 36a81db..4cf7cf3e 100644
--- a/core/java/android/view/IDockedStackListener.aidl
+++ b/core/java/android/view/IDockedStackListener.aidl
@@ -40,8 +40,11 @@
*
* @param minimized Whether the docked stack is currently minimized.
* @param animDuration The duration of the animation for changing the minimized state.
+ * @param isHomeStackResizable If the home stack is resizable, a portion of the docked stack
+ * will be shown with the divider
*/
- void onDockedStackMinimizedChanged(boolean minimized, long animDuration);
+ void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
+ boolean isHomeStackResizable);
/**
* Called when window manager decides to adjust the divider for IME. Like the minimized state,
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 707300f..10b1e19 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -96,4 +96,9 @@
* Called when Keyboard Shortcuts are requested for the window.
*/
void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId);
+
+ /**
+ * Tell the window that it is either gaining or losing pointer capture.
+ */
+ void dispatchPointerCaptureChanged(boolean hasCapture);
}
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/InputDevice.java b/core/java/android/view/InputDevice.java
index 55f64d9..035d48f 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -237,6 +237,14 @@
public static final int SOURCE_TRACKBALL = 0x00010000 | SOURCE_CLASS_TRACKBALL;
/**
+ * The input source is a mouse device whose relative motions should be interpreted as
+ * navigation events.
+ *
+ * @see #SOURCE_CLASS_TRACKBALL
+ */
+ public static final int SOURCE_MOUSE_RELATIVE = 0x00020000 | SOURCE_CLASS_TRACKBALL;
+
+ /**
* The input source is a touch pad or digitizer tablet that is not
* associated with a display (unlike {@link #SOURCE_TOUCHSCREEN}).
*
@@ -975,6 +983,7 @@
appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE, "mouse");
appendSourceDescriptionIfApplicable(description, SOURCE_STYLUS, "stylus");
appendSourceDescriptionIfApplicable(description, SOURCE_TRACKBALL, "trackball");
+ appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE_RELATIVE, "mouse_relative");
appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad");
appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK, "joystick");
appendSourceDescriptionIfApplicable(description, SOURCE_GAMEPAD, "gamepad");
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 88f2d34..02202db 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -675,8 +675,8 @@
*
* @return The modifier behavior for this keyboard.
*
- * @see {@link #MODIFIER_BEHAVIOR_CHORDED}
- * @see {@link #MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED}
+ * @see #MODIFIER_BEHAVIOR_CHORDED
+ * @see #MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED
*/
public int getModifierBehavior() {
switch (getKeyboardType()) {
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index f3f3d40..7a5c65f 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -335,7 +335,7 @@
private ActionProvider itemActionProvider;
private CharSequence itemContentDescription;
- private CharSequence itemTooltip;
+ private CharSequence itemTooltipText;
private static final int defaultGroupId = NO_ID;
private static final int defaultItemId = NO_ID;
@@ -429,7 +429,7 @@
itemContentDescription =
a.getText(com.android.internal.R.styleable.MenuItem_contentDescription);
- itemTooltip = a.getText(com.android.internal.R.styleable.MenuItem_tooltip);
+ itemTooltipText = a.getText(com.android.internal.R.styleable.MenuItem_tooltipText);
a.recycle();
@@ -495,7 +495,7 @@
}
item.setContentDescription(itemContentDescription);
- item.setTooltip(itemTooltip);
+ item.setTooltipText(itemTooltipText);
}
public MenuItem addItem() {
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 5ced765..3f8d089 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -621,9 +621,9 @@
/**
* Change the tooltip text associated with this menu item.
*
- * @param tooltip The new tooltip text.
+ * @param tooltipText The new tooltip text.
*/
- default MenuItem setTooltip(CharSequence tooltip) {
+ default MenuItem setTooltipText(CharSequence tooltipText) {
return this;
}
@@ -632,7 +632,7 @@
*
* @return The tooltip text.
*/
- default CharSequence getTooltip() {
+ default CharSequence getTooltipText() {
return null;
}
}
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index fc66697..ea6e63c 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -139,9 +139,6 @@
RenderNode.class.getClassLoader(), nGetNativeFinalizer(), 1024);
}
- // Note: written by native when display lists are detached
- private boolean mValid;
-
// Do not access directly unless you are ThreadedRenderer
final long mNativeRenderNode;
private final View mOwningView;
@@ -233,7 +230,6 @@
long displayList = canvas.finishRecording();
nSetDisplayList(mNativeRenderNode, displayList);
canvas.recycle();
- mValid = true;
}
/**
@@ -242,10 +238,7 @@
* obsolete resources after related resources are gone.
*/
public void discardDisplayList() {
- if (!mValid) return;
-
nSetDisplayList(mNativeRenderNode, 0);
- mValid = false;
}
/**
@@ -254,10 +247,12 @@
*
* @return boolean true if the display list is able to be replayed, false otherwise.
*/
- public boolean isValid() { return mValid; }
+ public boolean isValid() {
+ return nIsValid(mNativeRenderNode);
+ }
long getNativeDisplayList() {
- if (!mValid) {
+ if (!isValid()) {
throw new IllegalStateException("The display list is not valid.");
}
return mNativeRenderNode;
@@ -827,8 +822,7 @@
// Regular JNI methods
///////////////////////////////////////////////////////////////////////////
- // Intentionally not static because it acquires a reference to 'this'
- private native long nCreate(String name);
+ private static native long nCreate(String name);
private static native long nGetNativeFinalizer();
private static native void nOutput(long renderNode);
@@ -853,6 +847,9 @@
// @CriticalNative methods
///////////////////////////////////////////////////////////////////////////
+ @CriticalNative
+ private static native boolean nIsValid(long renderNode);
+
// Matrix
@CriticalNative
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a12600a..5bb577f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -34,7 +34,7 @@
private static final String TAG = "SurfaceControl";
private static native long nativeCreate(SurfaceSession session, String name,
- int w, int h, int format, int flags)
+ int w, int h, int format, int flags, long parentObject)
throws OutOfResourcesException;
private static native void nativeRelease(long nativeObject);
private static native void nativeDestroy(long nativeObject);
@@ -287,6 +287,12 @@
public SurfaceControl(SurfaceSession session,
String name, int w, int h, int format, int flags)
throws OutOfResourcesException {
+ this(session, name, w, h, format, flags, null);
+ }
+
+ public SurfaceControl(SurfaceSession session,
+ String name, int w, int h, int format, int flags, SurfaceControl parent)
+ throws OutOfResourcesException {
if (session == null) {
throw new IllegalArgumentException("session must not be null");
}
@@ -304,7 +310,7 @@
}
mName = name;
- mNativeObject = nativeCreate(session, name, w, h, format, flags);
+ mNativeObject = nativeCreate(session, name, w, h, format, flags, parent != null ? parent.mNativeObject : 0);
if (mNativeObject == 0) {
throw new OutOfResourcesException(
"Couldn't allocate SurfaceControl native object");
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index f3ebcb4..4ceb236 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -496,15 +496,6 @@
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 37dfdb9..0657bef 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.
*/
@@ -3709,6 +3698,8 @@
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
+
+ OnCapturedPointerListener mOnCapturedPointerListener;
}
ListenerInfo mListenerInfo;
@@ -3718,7 +3709,7 @@
* Text to be displayed in a tooltip popup.
*/
@Nullable
- CharSequence mTooltip;
+ CharSequence mTooltipText;
/**
* View-relative position of the tooltip anchor point.
@@ -3807,11 +3798,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;
@@ -4053,9 +4039,9 @@
* input fields and tags (like {@code id}).
* </ul>
*/
- // TODO(b/33197203) (b/34078930): improve documentation: mention all cases, show examples, etc.
- // In particular, be more specific about webview restrictions
- public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x1;
+ // TODO(b/33197203): cannot conflict with flags defined on AutoFillManager until they're removed
+ // (when save is refactored).
+ public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x10000000;
/**
* Set when the user explicitly asked a {@link android.service.autofill.AutoFillService} to save
@@ -4065,7 +4051,9 @@
* (Personally Identifiable Information). For example, the text of password fields should be
* included since that's what's typically saved.
*/
- public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x2;
+ // TODO(b/33197203): cannot conflict with flags defined on AutoFillManager until they're removed
+ // (when save is refactored).
+ public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x20000000;
/**
* Set to true when drawing cache is enabled and cannot be created.
@@ -4169,7 +4157,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 +4343,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 +4459,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 +4614,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;
@@ -4761,19 +4750,14 @@
forceHasOverlappingRendering(a.getBoolean(attr, true));
}
break;
- case R.styleable.View_tooltip:
- setTooltip(a.getText(attr));
+ case R.styleable.View_tooltipText:
+ setTooltipText(a.getText(attr));
break;
case R.styleable.View_keyboardNavigationCluster:
if (a.peekValue(attr) != null) {
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 +5031,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' : '.');
@@ -6953,7 +6937,6 @@
// The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
// reuse the accessibility id to save space.
structure.setAutoFillId(getAccessibilityViewId());
-
structure.setAutoFillType(getAutoFillType());
}
@@ -7083,7 +7066,7 @@
}
/**
- * Describes the auto-fill type that should be used on callas to
+ * Describes the auto-fill type that should be used on calls to
* {@link #autoFill(AutoFillValue)} and
* {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}.
*
@@ -8043,28 +8026,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 +8477,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 +9099,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 +9181,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 +9202,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 +9241,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 +9390,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 +9673,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;
}
@@ -10697,6 +10644,25 @@
}
/**
+ * Pass a captured pointer event down to the focused view.
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ public boolean dispatchCapturedPointerEvent(MotionEvent event) {
+ if (!hasPointerCapture()) {
+ return false;
+ }
+ //noinspection SimplifiableIfStatement
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnCapturedPointerListener != null
+ && li.mOnCapturedPointerListener.onCapturedPointer(this, event)) {
+ return true;
+ }
+ return onCapturedPointerEvent(event);
+ }
+
+ /**
* Dispatch a generic motion event.
* <p>
* Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
@@ -12081,14 +12047,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 +12210,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()) {
@@ -13585,7 +13564,7 @@
* Any previously attached StateListAnimator will be detached.
*
* @param stateListAnimator The StateListAnimator to update the view
- * @see {@link android.animation.StateListAnimator}
+ * @see android.animation.StateListAnimator
*/
public void setStateListAnimator(StateListAnimator stateListAnimator) {
if (mStateListAnimator == stateListAnimator) {
@@ -16636,11 +16615,12 @@
*/
@CallSuper
protected void destroyHardwareResources() {
- // Although the Layer will be destroyed by RenderNode, we want to release
- // the staging display list, which is also a signal to RenderNode that it's
- // 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();
+ }
}
/**
@@ -16811,11 +16791,8 @@
}
private void resetDisplayList() {
- if (mRenderNode.isValid()) {
- mRenderNode.discardDisplayList();
- }
-
- if (mBackgroundRenderNode != null && mBackgroundRenderNode.isValid()) {
+ mRenderNode.discardDisplayList();
+ if (mBackgroundRenderNode != null) {
mBackgroundRenderNode.discardDisplayList();
}
}
@@ -18156,7 +18133,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++;
}
@@ -22711,7 +22688,110 @@
return mPointerIcon;
}
- //
+ /**
+ * Checks pointer capture status.
+ *
+ * @return true if the view has pointer capture.
+ * @see #requestPointerCapture()
+ * @see #hasPointerCapture()
+ */
+ public boolean hasPointerCapture() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ return false;
+ }
+ return viewRootImpl.hasPointerCapture();
+ }
+
+ /**
+ * Requests pointer capture mode.
+ * <p>
+ * When the window has pointer capture, the mouse pointer icon will disappear and will not
+ * change its position. Further mouse will be dispatched with the source
+ * {@link InputDevice#SOURCE_MOUSE_RELATIVE}, and relative position changes will be available
+ * through {@link MotionEvent#getX} and {@link MotionEvent#getY}. Non-mouse events
+ * (touchscreens, or stylus) will not be affected.
+ * <p>
+ * If the window already has pointer capture, this call does nothing.
+ * <p>
+ * The capture may be released through {@link #releasePointerCapture()}, or will be lost
+ * automatically when the window loses focus.
+ *
+ * @see #releasePointerCapture()
+ * @see #hasPointerCapture()
+ */
+ public void requestPointerCapture() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.requestPointerCapture(true);
+ }
+ }
+
+
+ /**
+ * Releases the pointer capture.
+ * <p>
+ * If the window does not have pointer capture, this call will do nothing.
+ * @see #requestPointerCapture()
+ * @see #hasPointerCapture()
+ */
+ public void releasePointerCapture() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.requestPointerCapture(false);
+ }
+ }
+
+ /**
+ * Called when the window has just acquired or lost pointer capture.
+ *
+ * @param hasCapture True if the view now has pointerCapture, false otherwise.
+ */
+ @CallSuper
+ public void onPointerCaptureChange(boolean hasCapture) {
+ }
+
+ /**
+ * @see #onPointerCaptureChange
+ */
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ onPointerCaptureChange(hasCapture);
+ }
+
+ /**
+ * Implement this method to handle captured pointer events
+ *
+ * @param event The captured pointer event.
+ * @return True if the event was handled, false otherwise.
+ * @see #requestPointerCapture()
+ */
+ public boolean onCapturedPointerEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a captured pointer event
+ * is being dispatched this view. The callback will be invoked before the event is
+ * given to the view.
+ */
+ public interface OnCapturedPointerListener {
+ /**
+ * Called when a captured pointer event is dispatched to a view.
+ * @param view The view this event has been dispatched to.
+ * @param event The captured event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onCapturedPointer(View view, MotionEvent event);
+ }
+
+ /**
+ * Set a listener to receive callbacks when the pointer capture state of a view changes.
+ * @param l The {@link OnCapturedPointerListener} to receive callbacks.
+ */
+ public void setOnCapturedPointerListener(OnCapturedPointerListener l) {
+ getListenerInfo().mOnCapturedPointerListener = l;
+ }
+
// Properties
//
/**
@@ -24638,10 +24718,10 @@
* menu). </li>
* <li>On hover, after a brief delay since the pointer has stopped moving </li>
*
- * @param tooltip the tooltip text, or null if no tooltip is required
+ * @param tooltipText the tooltip text, or null if no tooltip is required
*/
- public final void setTooltip(@Nullable CharSequence tooltip) {
- if (TextUtils.isEmpty(tooltip)) {
+ public final void setTooltipText(@Nullable CharSequence tooltipText) {
+ if (TextUtils.isEmpty(tooltipText)) {
setFlags(0, TOOLTIP);
hideTooltip();
mTooltipInfo = null;
@@ -24652,9 +24732,9 @@
mTooltipInfo.mShowTooltipRunnable = this::showHoverTooltip;
mTooltipInfo.mHideTooltipRunnable = this::hideTooltip;
}
- mTooltipInfo.mTooltip = tooltip;
+ mTooltipInfo.mTooltipText = tooltipText;
if (mTooltipInfo.mTooltipPopup != null && mTooltipInfo.mTooltipPopup.isShowing()) {
- mTooltipInfo.mTooltipPopup.updateContent(mTooltipInfo.mTooltip);
+ mTooltipInfo.mTooltipPopup.updateContent(mTooltipInfo.mTooltipText);
}
}
}
@@ -24665,8 +24745,8 @@
* @return the tooltip text
*/
@Nullable
- public final CharSequence getTooltip() {
- return mTooltipInfo != null ? mTooltipInfo.mTooltip : null;
+ public final CharSequence getTooltipText() {
+ return mTooltipInfo != null ? mTooltipInfo.mTooltipText : null;
}
private boolean showTooltip(int x, int y, boolean fromLongClick) {
@@ -24676,7 +24756,7 @@
if ((mViewFlags & ENABLED_MASK) != ENABLED) {
return false;
}
- final CharSequence tooltipText = getTooltip();
+ final CharSequence tooltipText = getTooltipText();
if (TextUtils.isEmpty(tooltipText)) {
return false;
}
@@ -24781,6 +24861,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 a479bb3..af39eb3 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);
}
}
}
@@ -1772,6 +1756,34 @@
}
@Override
+ public boolean dispatchCapturedPointerEvent(MotionEvent event) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+ if (super.dispatchCapturedPointerEvent(event)) {
+ return true;
+ }
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
+ if (mFocused.dispatchCapturedPointerEvent(event)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ exitHoverTargets();
+
+ super.dispatchPointerCaptureChanged(hasCapture);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchPointerCaptureChanged(hasCapture);
+ }
+ }
+
+ @Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
@@ -3072,8 +3084,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;
}
@@ -4614,6 +4625,16 @@
clearCachedLayoutMode();
}
+ /** @hide */
+ @Override
+ protected void destroyHardwareResources() {
+ super.destroyHardwareResources();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).destroyHardwareResources();
+ }
+ }
+
/**
* Adds a view during layout. This is useful if in your onLayout() method,
* you need to add more views (as does the list view for example).
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..c9b9d5f 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;
@@ -195,8 +192,8 @@
View mAccessibilityFocusedHost;
AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
- // The view which captures mouse input, or null when no one is capturing.
- View mCapturingView;
+ // True if the window currently has pointer capture enabled.
+ boolean mPointerCapture;
int mViewVisibility;
boolean mAppVisible = true;
@@ -3203,6 +3200,27 @@
}
}
+ boolean hasPointerCapture() {
+ return mPointerCapture;
+ }
+
+ void requestPointerCapture(boolean enabled) {
+ if (mPointerCapture == enabled) {
+ return;
+ }
+ InputManager.getInstance().requestPointerCapture(mAttachInfo.mWindowToken, enabled);
+ }
+
+ private void handlePointerCaptureChanged(boolean hasCapture) {
+ if (mPointerCapture == hasCapture) {
+ return;
+ }
+ mPointerCapture = hasCapture;
+ if (mView != null) {
+ mView.dispatchPointerCaptureChanged(hasCapture);
+ }
+ }
+
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
@@ -3396,6 +3414,7 @@
private final static int MSG_DISPATCH_WINDOW_SHOWN = 25;
private final static int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
private final static int MSG_UPDATE_POINTER_ICON = 27;
+ private final static int MSG_POINTER_CAPTURE_CHANGED = 28;
final class ViewRootHandler extends Handler {
@Override
@@ -3445,6 +3464,8 @@
return "MSG_DISPATCH_WINDOW_SHOWN";
case MSG_UPDATE_POINTER_ICON:
return "MSG_UPDATE_POINTER_ICON";
+ case MSG_POINTER_CAPTURE_CHANGED:
+ return "MSG_POINTER_CAPTURE_CHANGED";
}
return super.getMessageName(message);
}
@@ -3616,6 +3637,10 @@
.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
mHasHadWindowFocus = true;
+ } else {
+ if (mPointerCapture) {
+ handlePointerCaptureChanged(false);
+ }
}
}
} break;
@@ -3694,6 +3719,10 @@
MotionEvent event = (MotionEvent) msg.obj;
resetPointerIcon(event);
} break;
+ case MSG_POINTER_CAPTURE_CHANGED: {
+ final boolean hasCapture = msg.arg1 != 0;
+ handlePointerCaptureChanged(hasCapture);
+ } break;
}
}
}
@@ -4397,14 +4426,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 +4452,13 @@
}
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;
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
+ if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
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;
+ } else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
+ KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
groupNavigationDirection = View.FOCUS_BACKWARD;
}
}
@@ -4479,7 +4488,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 {
@@ -4495,11 +4504,8 @@
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
- final View eventTarget =
- (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
- mCapturingView : mView;
mAttachInfo.mHandlingPointerEvent = true;
- boolean handled = eventTarget.dispatchPointerEvent(event);
+ boolean handled = mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
@@ -4533,6 +4539,12 @@
private int processTrackballEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
+ if (!hasPointerCapture() || mView.dispatchCapturedPointerEvent(event)) {
+ return FINISH_HANDLED;
+ }
+ }
+
if (mView.dispatchTrackballEvent(event)) {
return FINISH_HANDLED;
}
@@ -5910,11 +5922,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() {
@@ -6724,6 +6735,14 @@
MSG_REQUEST_KEYBOARD_SHORTCUTS, deviceId, 0, receiver).sendToTarget();
}
+ public void dispatchPointerCaptureChanged(boolean on) {
+ final int what = MSG_POINTER_CAPTURE_CHANGED;
+ mHandler.removeMessages(what);
+ Message msg = mHandler.obtainMessage(what);
+ msg.arg1 = on ? 1 : 0;
+ mHandler.sendMessage(msg);
+ }
+
/**
* Post a callback to send a
* {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
@@ -7304,6 +7323,15 @@
viewAncestor.dispatchRequestKeyboardShortcuts(receiver, deviceId);
}
}
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchPointerCaptureChanged(hasCapture);
+ }
+ }
+
}
public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8bc988d..c424086 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;
@@ -573,6 +574,13 @@
*/
default public void onProvideKeyboardShortcuts(
List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) { };
+
+ /**
+ * Called when pointer capture is enabled or disabled for the current window.
+ *
+ * @param hasCapture True if the window has pointer capture.
+ */
+ default public void onPointerCaptureChanged(boolean hasCapture) { };
}
/** @hide */
@@ -1128,6 +1136,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/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index 8f2d2e1..02c8945 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -158,5 +158,10 @@
List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
mWrapped.onProvideKeyboardShortcuts(data, menu, deviceId);
}
+
+ @Override
+ public void onPointerCaptureChanged(boolean hasCapture) {
+ mWrapped.onPointerCaptureChanged(hasCapture);
+ }
}
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/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 3748134..bc4ae6d 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -750,13 +750,14 @@
* @param windowFlags Window layout flags.
* @param overrideConfig override configuration to consider when generating
* context to for resources.
+ * @param displayId Id of the display to show the splash screen at.
*
* @return The starting surface.
*
*/
public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
- int logo, int windowFlags, Configuration overrideConfig);
+ int logo, int windowFlags, Configuration overrideConfig, int displayId);
/**
* Prepare for a window being added to the window manager. You can throw an
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index f0bf7e5..56d45b0 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3998,8 +3998,10 @@
* Obtains a pooled instance.
*
* @param type The type of the range.
- * @param min The min value.
- * @param max The max value.
+ * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no
+ * minimum.
+ * @param max The maximum value. Use {@code Float.POSITIVE_INFINITY} if the range has no
+ * maximum.
* @param current The current value.
*/
public static RangeInfo obtain(int type, float min, float max, float current) {
@@ -4019,8 +4021,10 @@
* Creates a new range.
*
* @param type The type of the range.
- * @param min The min value.
- * @param max The max value.
+ * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no
+ * minimum.
+ * @param max The maximum value. Use {@code Float.POSITIVE_INFINITY} if the range has no
+ * maximum.
* @param current The current value.
*/
private RangeInfo(int type, float min, float max, float current) {
@@ -4044,18 +4048,18 @@
}
/**
- * Gets the min value.
+ * Gets the minimum value.
*
- * @return The min value.
+ * @return The minimum value, or {@code Float.NEGATIVE_INFINITY} if no minimum exists.
*/
public float getMin() {
return mMin;
}
/**
- * Gets the max value.
+ * Gets the maximum value.
*
- * @return The max value.
+ * @return The maximum value, or {@code Float.POSITIVE_INFINITY} if no maximum exists.
*/
public float getMax() {
return mMax;
diff --git a/core/java/android/view/animation/LayoutAnimationController.java b/core/java/android/view/animation/LayoutAnimationController.java
index df2f18c..7fa49c1 100644
--- a/core/java/android/view/animation/LayoutAnimationController.java
+++ b/core/java/android/view/animation/LayoutAnimationController.java
@@ -150,7 +150,7 @@
* Returns the order used to compute the delay of each child's animation.
*
* @return one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or
- * {@link #ORDER_RANDOM)
+ * {@link #ORDER_RANDOM}
*
* @attr ref android.R.styleable#LayoutAnimation_animationOrder
*/
diff --git a/core/java/android/view/autofill/AutoFillId.java b/core/java/android/view/autofill/AutoFillId.java
index b7b694d..e9c1c3b 100644
--- a/core/java/android/view/autofill/AutoFillId.java
+++ b/core/java/android/view/autofill/AutoFillId.java
@@ -43,6 +43,13 @@
}
/** @hide */
+ public AutoFillId(int parentId, int virtualChildId) {
+ mVirtual = true;
+ mViewId = parentId;
+ mVirtualId = virtualChildId;
+ }
+
+ /** @hide */
public int getViewId() {
return mViewId;
}
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
new file mode 100644
index 0000000..cf56e0e
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.service.autofill.IAutoFillManagerService;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * App entry point to the AutoFill Framework.
+ */
+// TODO(b/33197203): improve this javadoc
+public final class AutoFillManager {
+
+ private static final String TAG = "AutoFillManager";
+ private static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable
+
+ /**
+ * Flag used to show the auto-fill UI affordance for a view.
+ */
+ // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
+ // save is refactored).
+ public static final int FLAG_UPDATE_UI_SHOW = 0x1;
+
+ /**
+ * Flag used to hide the auto-fill UI affordance for a view.
+ */
+ // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
+ // save is refactored).
+ public static final int FLAG_UPDATE_UI_HIDE = 0x2;
+
+ private final IAutoFillManagerService mService;
+
+ /**
+ * @hide
+ */
+ public AutoFillManager(@SuppressWarnings("unused") Context context,
+ IAutoFillManagerService service) {
+ mService = service;
+ }
+
+ /**
+ * Updates the auto-fill bar for a given {@link View}.
+ *
+ * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
+ * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
+ *
+ * @param view view to be updated.
+ * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
+ * {@link #FLAG_UPDATE_UI_HIDE}.
+ */
+ public void updateAutoFillInput(View view, int flags) {
+ final Rect bounds = new Rect();
+ view.getBoundsOnScreen(bounds);
+
+ requestAutoFill(new AutoFillId(view.getAccessibilityViewId()), bounds, flags);
+ }
+
+ /**
+ * Updates the auto-fill bar for a virtual child of a given {@link View}.
+ *
+ * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
+ * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
+ *
+ * @param parent parent view.
+ * @param childId id identifying the virtual child inside the parent view.
+ * @param bounds absolute boundaries of the child in the window (could be {@code null} when
+ * flag is {@link #FLAG_UPDATE_UI_HIDE}.
+ * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
+ * {@link #FLAG_UPDATE_UI_HIDE}.
+ */
+ public void updateAutoFillInput(View parent, int childId, @Nullable Rect bounds,
+ int flags) {
+ requestAutoFill(new AutoFillId(parent.getAccessibilityViewId(), childId), bounds, flags);
+ }
+
+ private void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
+ if (DEBUG) {
+ Log.v(TAG, "requestAutoFill(): id=" + id + ", bounds=" + bounds + ", flags=" + flags);
+ }
+ try {
+ mService.requestAutoFill(id, bounds, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/view/autofill/AutoFillSession.java b/core/java/android/view/autofill/AutoFillSession.java
new file mode 100644
index 0000000..eec7a82
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillSession.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+
+import android.app.Activity;
+import android.os.RemoteException;
+import android.service.autofill.IAutoFillAppCallback;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * An auto-fill session associated with an activity.
+ *
+ * @hide
+ */
+public final class AutoFillSession {
+
+ private static final String TAG = "AutoFillSession";
+
+ private final IAutoFillAppCallback mCallback = new IAutoFillAppCallback.Stub() {
+ @Override
+ public void autoFill(Dataset dataset) throws RemoteException {
+ final Activity activity = mActivity.get();
+ if (activity == null) {
+ if (DEBUG) Log.d(TAG, "autoFill(): activity already GCed");
+ return;
+ }
+ // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
+ // dataset.extras to service
+ activity.runOnUiThread(() -> {
+ final View root = activity.getWindow().getDecorView().getRootView();
+ for (DatasetField field : dataset.getFields()) {
+ final AutoFillId id = field.getId();
+ if (id == null) {
+ Log.w(TAG, "autoFill(): null id on " + field);
+ continue;
+ }
+ final int viewId = id.getViewId();
+ final View view = root.findViewByAccessibilityIdTraversal(viewId);
+ if (view == null) {
+ Log.w(TAG, "autoFill(): no View with id " + viewId);
+ continue;
+ }
+
+ // TODO(b/33197203): handle protected value (like credit card)
+ if (id.isVirtual()) {
+ // Delegate virtual fields.
+ setAutoFillDelegateCallback();
+ final VirtualViewDelegate delegate = view
+ .getAutoFillVirtualViewDelegate(
+ mAutoFillDelegateCallback);
+ if (delegate == null) {
+ Log.w(TAG, "autoFill(): cannot fill virtual " + id
+ + "; no VirtualViewDelegate for view "
+ + view.getClass());
+ continue;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "autoFill(): delegating " + id
+ + " to VirtualViewDelegate " + delegate);
+ }
+ delegate.autoFill(id.getVirtualChildId(), field.getValue());
+ } else {
+ // Handle non-virtual fields itself.
+ view.autoFill(field.getValue());
+ }
+ }
+ });
+ }
+ };
+
+ private final WeakReference<Activity> mActivity;
+
+ @GuardedBy("this")
+ private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+
+ public AutoFillSession(Activity activity) {
+ mActivity = new WeakReference<>(activity);
+ }
+
+ public IAutoFillAppCallback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Lazily sets the {@link #mAutoFillDelegateCallback}.
+ */
+ private void setAutoFillDelegateCallback() {
+ synchronized (this) {
+ if (mAutoFillDelegateCallback == null) {
+ mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
+ // TODO(b/33197203): implement
+ };
+ }
+ }
+ }
+
+}
diff --git a/core/java/android/view/autofill/Dataset.java b/core/java/android/view/autofill/Dataset.java
index a73eb774..18a08f9 100644
--- a/core/java/android/view/autofill/Dataset.java
+++ b/core/java/android/view/autofill/Dataset.java
@@ -19,12 +19,13 @@
import static android.view.autofill.Helper.DEBUG;
import static android.view.autofill.Helper.append;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.assist.AssistStructure.ViewNode;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.service.autofill.AutoFillService;
import com.android.internal.util.Preconditions;
@@ -50,12 +51,20 @@
private final CharSequence mName;
private final ArrayList<DatasetField> mFields;
private final Bundle mExtras;
+ private final int mFlags;
+ private final boolean mRequiresAuth;
+ private final boolean mHasCryptoObject;
+ private final long mCryptoOpId;
private Dataset(Dataset.Builder builder) {
mName = builder.mName;
// TODO(b/33197203): make an immutable copy of mFields?
mFields = builder.mFields;
mExtras = builder.mExtras;
+ mFlags = builder.mFlags;
+ mRequiresAuth = builder.mRequiresAuth;
+ mHasCryptoObject = builder.mHasCryptoObject;
+ mCryptoOpId = builder.mCryptoOpId;
}
/** @hide */
@@ -73,13 +82,41 @@
return mExtras;
}
+ /** @hide */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /** @hide */
+ public boolean isAuthRequired() {
+ return mRequiresAuth;
+ }
+
+ /** @hide */
+ public boolean isEmpty() {
+ return mFields.isEmpty();
+ }
+
+ /** @hide */
+ public boolean hasCryptoObject() {
+ return mHasCryptoObject;
+ }
+
+ /** @hide */
+ public long getCryptoObjectOpId() {
+ return mCryptoOpId;
+ }
+
@Override
public String toString() {
if (!DEBUG) return super.toString();
final StringBuilder builder = new StringBuilder("Dataset [name=").append(mName)
.append(", fields=").append(mFields).append(", extras=");
- append(builder, mExtras);
+ append(builder, mExtras)
+ .append(", flags=").append(mFlags)
+ .append(", requiresAuth: ").append(mRequiresAuth)
+ .append(", hasCrypto: ").append(mHasCryptoObject);
return builder.append(']').toString();
}
@@ -90,6 +127,10 @@
private CharSequence mName;
private final ArrayList<DatasetField> mFields = new ArrayList<>();
private Bundle mExtras;
+ private int mFlags;
+ private boolean mRequiresAuth;
+ private boolean mHasCryptoObject;
+ private long mCryptoOpId;
/**
* Creates a new builder.
@@ -103,6 +144,125 @@
}
/**
+ * Requires dataset authentication through the {@link
+ * android.service.autofill.AutoFillService} before auto-filling the activity with this
+ * dataset.
+ *
+ * <p>This method is typically called when the device (or the service) does not support
+ * fingerprint authentication (and hence it cannot use {@link
+ * #requiresFingerprintAuthentication(CryptoObject, Bundle, int)}) or when the service needs
+ * to use a custom authentication UI for the dataset. For example, when a dataset contains
+ * credit card information (such as number, expiration date, and verification code), the
+ * service displays an authentication dialog asking for the verification code to unlock the
+ * rest of the data).
+ *
+ * <p>Since the dataset is "locked" until the user authenticates it, typically this dataset
+ * name is masked (for example, "VISA....1234").
+ *
+ * <p>When the user selects this dataset, the Android System calls {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+ * passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_REQUESTED} in
+ * the flags and the same {@code extras} passed to this method. The service can then
+ * displays its custom authentication UI, and then call the proper method on {@link
+ * android.service.autofill.FillCallback} depending on the authentication result and whether
+ * this dataset already contains the fields needed to auto-fill the activity:
+ *
+ * <ul>
+ * <li>If authentication failed, call
+ * {@link android.service.autofill.FillCallback#onDatasetAuthentication(Dataset,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the flags.
+ * <li>If authentication succeeded and this datast is empty (no fields), call {@link
+ * android.service.autofill.FillCallback#onSuccess(FillResponse)} with a new dataset
+ * (with the proper fields).
+ * <li>If authentication succeeded and this response is not empty, call {@link
+ * android.service.autofill.FillCallback#onDatasetAuthentication(Dataset, int)}
+ * passing
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS} in the
+ * {@code flags} and {@code null} as the {@code dataset}.
+ * </ul>
+ *
+ * @param extras when set, will be passed back in the {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle,
+ * int)}, call so it could be used by the service to handle state.
+ * @param flags optional parameters, currently ignored.
+ */
+ public Builder requiresCustomAuthentication(@Nullable Bundle extras, int flags) {
+ return requiresAuthentication(null, extras, flags);
+ }
+
+ /**
+ * Requires dataset authentication through the Fingerprint sensor before auto-filling the
+ * activity with this dataset.
+ *
+ * <p>This method is typically called when the dataset contains sensitive information (for
+ * example, credit card information) and the provider requires the user to re-authenticate
+ * before using it.
+ *
+ * <p>Since the dataset is "locked" until the user authenticates it, typically this dataset
+ * name is masked (for example, "VISA....1234").
+ *
+ * <p>When the user selects this dataset, the Android System displays an UI affordance
+ * asking the user to use the fingerprint sensor unlock the dataset, and what happens after
+ * a successful fingerprint authentication depends on whether the dataset is empty (no
+ * fields, only the masked name) or not:
+ *
+ * <ul>
+ * <li>If it's empty, the Android System will call {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}} in the
+ * flags.
+ * <li>If it's not empty, the activity will be auto-filled with its data.
+ * </ul>
+ *
+ * <p>If the fingerprint authentication fails, the Android System will call {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+ * passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the
+ * flags.
+ *
+ * <p><strong>NOTE: </note> the {@link android.service.autofill.AutoFillService} should use
+ * the {@link android.hardware.fingerprint.FingerprintManager} to check if fingerpint
+ * authentication is available before using this method, and use other alternatives (such as
+ * {@link #requiresCustomAuthentication(Bundle, int)}) if it is not: if this method is
+ * called when fingerprint is not available, Android System will call {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+ * passing {@link
+ * android.service.autofill.AutoFillService#FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}
+ * in the flags, but it would be wasting system resources (and worsening the user
+ * experience) in the process.
+ *
+ * @param crypto object that will be authenticated.
+ * @param extras when set, will be passed back in the {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+ * call so it could be used by the service to handle state.
+ * @param flags optional parameters, currently ignored.
+ */
+ public Builder requiresFingerprintAuthentication(CryptoObject crypto,
+ @Nullable Bundle extras, int flags) {
+ // TODO(b/33197203): should we allow crypto to be null?
+ Preconditions.checkArgument(crypto != null, "must pass a CryptoObject");
+ return requiresAuthentication(crypto, extras, flags);
+ }
+
+ private Builder requiresAuthentication(CryptoObject cryptoObject, Bundle extras,
+ int flags) {
+ // There can be only one!
+ Preconditions.checkState(!mRequiresAuth,
+ "requires-authentication methods already called");
+ // TODO(b/33197203): make sure that either this method or setExtras() is called, but
+ // not both
+ mExtras = extras;
+ mFlags = flags;
+ mRequiresAuth = true;
+ if (cryptoObject != null) {
+ mHasCryptoObject = true;
+ mCryptoOpId = cryptoObject.getOpId();
+ }
+ return this;
+ }
+
+ /**
* Sets the value of a field.
*
* @param id id returned by {@link ViewNode#getAutoFillId()}.
@@ -121,15 +281,17 @@
}
/**
- * Sets a {@link Bundle} that will be passed to subsequent calls to {@link AutoFillService}
- * methods such as
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
- * {@link AutoFillService#EXTRA_DATASET_EXTRAS} as the key.
+ * Sets a {@link Bundle} that will be passed to subsequent calls to
+ * {@link android.service.autofill.AutoFillService} methods such as
+ * {@link android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
+ * Bundle, android.service.autofill.SaveCallback)}, using
+ * {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} as the key.
*
* <p>It can be used to keep service state in between calls.
*/
public Builder setExtras(Bundle extras) {
+ // TODO(b/33197203): make sure that either this method or the requires-Authentication
+ // ones are called, but not both
mExtras = Objects.requireNonNull(extras, "extras cannot be null");
return this;
}
@@ -158,6 +320,12 @@
parcel.writeCharSequence(mName);
parcel.writeList(mFields);
parcel.writeBundle(mExtras);
+ parcel.writeInt(mFlags);
+ parcel.writeInt(mRequiresAuth ? 1 : 0);
+ parcel.writeInt(mHasCryptoObject ? 1 : 0);
+ if (mHasCryptoObject) {
+ parcel.writeLong(mCryptoOpId);
+ }
}
@SuppressWarnings("unchecked")
@@ -165,6 +333,10 @@
mName = parcel.readCharSequence();
mFields = parcel.readArrayList(null);
mExtras = parcel.readBundle();
+ mFlags = parcel.readInt();
+ mRequiresAuth = parcel.readInt() == 1;
+ mHasCryptoObject = parcel.readInt() == 1;
+ mCryptoOpId = mHasCryptoObject ? parcel.readLong() : 0;
}
public static final Parcelable.Creator<Dataset> CREATOR = new Parcelable.Creator<Dataset>() {
diff --git a/core/java/android/view/autofill/FillResponse.java b/core/java/android/view/autofill/FillResponse.java
index 3a14767..48dbb84 100644
--- a/core/java/android/view/autofill/FillResponse.java
+++ b/core/java/android/view/autofill/FillResponse.java
@@ -18,11 +18,13 @@
import static android.view.autofill.Helper.DEBUG;
import static android.view.autofill.Helper.append;
+import android.annotation.Nullable;
import android.app.Activity;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.service.autofill.AutoFillService;
+import android.service.autofill.FillCallback;
import com.android.internal.util.Preconditions;
@@ -34,14 +36,16 @@
import java.util.Set;
/**
- * Response for a
- * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.FillCallback)}
- * request.
+ * Response for a {@link
+ * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} request.
*
* <p>The response typically contains one or more {@link Dataset}s, each representing a set of
- * fields that can be auto-filled together. For example, for a login page with username/password
- * where the user only have one account in the service, the response could be:
+ * fields that can be auto-filled together, and the Android System displays a dataset picker UI
+ * affordance that the user must use before the {@link Activity} is filled with the dataset.
+ *
+ * <p>For example, for a login page with username/password where the user only has one account in
+ * the service, the response could be:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
@@ -67,9 +71,9 @@
* .build();
* </pre>
*
- * <p>If the user does not have any data associated with this {@link Activity} but the service
- * wants to offer the user the option to save the data that was entered, then the service could
- * populate the response with {@code savableIds} instead of {@link Dataset}s:
+ * <p>If the user does not have any data associated with this {@link Activity} but the service wants
+ * to offer the user the option to save the data that was entered, then the service could populate
+ * the response with {@code savableIds} instead of {@link Dataset}s:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
@@ -79,8 +83,8 @@
*
* <p>Similarly, there might be cases where the user data on the service is enough to populate some
* fields but not all, and the service would still be interested on saving the other fields. In this
- * scenario, the service could populate the response with both {@link Dataset}s and
- * {@code savableIds}:
+ * scenario, the service could populate the response with both {@link Dataset}s and {@code
+ * savableIds}:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
@@ -101,9 +105,9 @@
* <p>If the service has multiple {@link Dataset}s with multiple options for some fields on each
* dataset (for example, multiple accounts with both a home and work address), then it should
* "partition" the {@link Activity} in sections and populate the response with just a subset of the
- * data that would fulfill the first section; then once the user fills the first section and taps
- * a field from the next section, the Android system would issue another request for that section,
- * and so on. For example, the first response could be:
+ * data that would fulfill the first section; then once the user fills the first section and taps a
+ * field from the next section, the Android system would issue another request for that section, and
+ * so on. For example, the first response could be:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
@@ -134,19 +138,31 @@
* .build();
* </pre>
*
- * <p>Finally, the service can use the {@link FillResponse.Builder#setExtras(Bundle)} and/or
- * {@link Dataset.Builder#setExtras(Bundle)} methods to pass
- * a {@link Bundle} with service-specific data use to identify this response on future calls (like
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.SaveCallback)}) - such bundle will be
- * available as the {@link AutoFillService#EXTRA_RESPONSE_EXTRAS} extra in
- * that method's {@code extras} argument.
+ * <p>The service could require user authentication, either at the {@link FillResponse} or {@link
+ * Dataset} levels, prior to auto-filling the activity - see {@link
+ * FillResponse.Builder#requiresFingerprintAuthentication(CryptoObject, Bundle, int)}, {@link
+ * FillResponse.Builder#requiresCustomAuthentication(Bundle, int)}, {@link
+ * Dataset.Builder#requiresFingerprintAuthentication(CryptoObject, Bundle, int)}, and {@link
+ * Dataset.Builder#requiresCustomAuthentication(Bundle, int)} for details.
+ *
+ * <p>Finally, the service can use the {@link FillResponse.Builder#setExtras(Bundle)} and/or {@link
+ * Dataset.Builder#setExtras(Bundle)} methods to pass {@link Bundle}s with service-specific data use
+ * to identify this response on future calls (like {@link
+ * android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
+ * Bundle, android.service.autofill.SaveCallback)}) - such bundles will be available as the
+ * {@link android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} and
+ * {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} extras in that method's
+ * {@code extras} argument.
*/
public final class FillResponse implements Parcelable {
private final List<Dataset> mDatasets;
private final AutoFillId[] mSavableIds;
private final Bundle mExtras;
+ private final int mFlags;
+ private final boolean mRequiresAuth;
+ private final boolean mHasCryptoObject;
+ private final long mCryptoOpId;
private FillResponse(Builder builder) {
// TODO(b/33197203): make it immutable?
@@ -158,6 +174,10 @@
mSavableIds[i++] = id;
}
mExtras = builder.mExtras;
+ mFlags = builder.mFlags;
+ mRequiresAuth = builder.mRequiresAuth;
+ mHasCryptoObject = builder.mHasCryptoObject;
+ mCryptoOpId = builder.mCryptoOpId;
}
/** @hide */
@@ -175,13 +195,161 @@
return mExtras;
}
+ /** @hide */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /** @hide */
+ public boolean isAuthRequired() {
+ return mRequiresAuth;
+ }
+
+ /** @hide */
+ public boolean hasCryptoObject() {
+ return mHasCryptoObject;
+ }
+
+ /** @hide */
+ public long getCryptoObjectOpId() {
+ return mCryptoOpId;
+ }
+
/**
* Builder for {@link FillResponse} objects.
*/
public static final class Builder {
private final List<Dataset> mDatasets = new ArrayList<>();
private final Set<AutoFillId> mSavableIds = new HashSet<>();
- private Bundle mExtras;
+ private Bundle mExtras;
+ private int mFlags;
+ private boolean mRequiresAuth;
+ private boolean mHasCryptoObject;
+ private long mCryptoOpId;
+
+ /**
+ * Requires user authentication through the {@link android.service.autofill.AutoFillService}
+ * before handling an auto-fill request.
+ *
+ * <p>This method is typically called when the device (or the service) does not support
+ * fingerprint authentication (and hence it cannot use {@link
+ * #requiresFingerprintAuthentication(CryptoObject, Bundle, int)}) or when the service needs
+ * to use a custom authentication UI and is used in 2 scenarios:
+ *
+ * <ol>
+ * <li>When the user data is encrypted and the service must authenticate an object that
+ * will be used to decrypt it.
+ * <li>When the service already acquired the user data but wants to confirm the user's
+ * identity before the activity is filled with it.
+ * </ol>
+ *
+ * <p>When this method is called, the Android System displays an UI affordance asking the
+ * user to tap it to auto-fill the activity; if the user taps it, the Android System calls
+ * {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_REQUESTED} in the flags and
+ * the same {@code extras} passed to this method. The service can then displays its custom
+ * authentication UI, and then call the proper method on {@link FillCallback} depending on
+ * the authentication result and whether this response already contains the {@link Dataset}s
+ * need to auto-fill the activity:
+ *
+ * <ul>
+ * <li>If authentication failed, call {@link
+ * FillCallback#onFillResponseAuthentication(int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the flags.
+ * <li>If authentication succeeded and this response is empty (no datasets), call {@link
+ * FillCallback#onSuccess(FillResponse)} with a new dataset (that does not require
+ * authentication).
+ * <li>If authentication succeeded and this response is not empty, call {@link
+ * FillCallback#onFillResponseAuthentication(int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS} in the flags.
+ * </ul>
+ *
+ * @param extras when set, will be passed back in the {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} call so it could be used by the service to handle state.
+ * @param flags optional parameters, currently ignored.
+ */
+ public Builder requiresCustomAuthentication(@Nullable Bundle extras, int flags) {
+ return requiresAuthentication(null, extras, flags);
+ }
+
+ /**
+ * Requires user authentication through the Fingerprint sensor before handling an auto-fill
+ * request.
+ *
+ * <p>The {@link android.service.autofill.AutoFillService} typically uses this method in 2
+ * situations:
+ *
+ * <ol>
+ * <li>When the user data is encrypted and the service must authenticate an object that
+ * will be used to decrypt it.
+ * <li>When the service already acquired the user data but wants to confirm the user's
+ * identity before the activity is filled with it.
+ * </ol>
+ *
+ * <p>When this method is called, the Android System displays an UI affordance asking the
+ * user to use the fingerprint sensor to auto-fill the activity, and what happens after a
+ * successful fingerprint authentication depends on the number of {@link Dataset}s included
+ * in this response:
+ *
+ * <ul>
+ * <li>If it's empty (scenario #1 above), the Android System will call {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}} in the
+ * flags.
+ * <li>If it contains one dataset, the activity will be auto-filled right away.
+ * <li>If it contains many datasets, the Android System will show dataset picker UI, and
+ * then auto-fill the activity once the user select the proper datased.
+ * </ul>
+ *
+ * <p>If the fingerprint authentication fails, the Android System will call {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR}
+ * in the flags.
+ *
+ * <p><strong>NOTE: </note> the {@link android.service.autofill.AutoFillService} should use
+ * the {@link android.hardware.fingerprint.FingerprintManager} to check if fingerpint
+ * authentication is available before using this method, and use other alternatives (such as
+ * {@link #requiresCustomAuthentication(Bundle, int)}) if it is not: if this method is
+ * called when fingerprint is not available, Android System will call {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}
+ * in the flags, but it would be wasting system resources (and worsening the user
+ * experience) in the process.
+ *
+ * @param crypto object that will be authenticated.
+ * @param extras when set, will be passed back in the {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} call so it could be used by the service to handle state.
+ * @param flags optional parameters, currently ignored.
+ */
+ public Builder requiresFingerprintAuthentication(CryptoObject crypto,
+ @Nullable Bundle extras, int flags) {
+ // TODO(b/33197203): should we allow crypto to be null?
+ Preconditions.checkArgument(crypto != null, "must pass a CryptoObject");
+ return requiresAuthentication(crypto, extras, flags);
+ }
+
+ private Builder requiresAuthentication(CryptoObject cryptoObject, Bundle extras,
+ int flags) {
+ // There can be only one!
+ Preconditions.checkState(!mRequiresAuth,
+ "requires-authentication methods already called");
+ // TODO(b/33197203): make sure that either this method or setExtras() is called, but
+ // not both
+ mExtras = extras;
+ mFlags = flags;
+ mRequiresAuth = true;
+ if (cryptoObject != null) {
+ mHasCryptoObject = true;
+ mCryptoOpId = cryptoObject.getOpId();
+ }
+ return this;
+ }
/**
* Adds a new {@link Dataset} to this response.
@@ -191,7 +359,6 @@
public Builder addDataset(Dataset dataset) {
Preconditions.checkNotNull(dataset, "dataset cannot be null");
// TODO(b/33197203): check if name already exists
- // TODO(b/33197203): check if authId already exists (and update javadoc)
mDatasets.add(dataset);
for (DatasetField field : dataset.getFields()) {
mSavableIds.add(field.getId());
@@ -201,13 +368,13 @@
/**
* Adds ids of additional fields that the service would be interested to save (through
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.SaveCallback)}) but were not
- * indirectly set through {@link #addDataset(Dataset)}.
+ * {@link android.service.autofill.AutoFillService#onSaveRequest(
+ * android.app.assist.AssistStructure, Bundle, android.service.autofill.SaveCallback)})
+ * but were not indirectly set through {@link #addDataset(Dataset)}.
*
* <p>See {@link FillResponse} for examples.
*/
- public Builder addSavableFields(AutoFillId...ids) {
+ public Builder addSavableFields(AutoFillId... ids) {
for (AutoFillId id : ids) {
mSavableIds.add(id);
}
@@ -215,15 +382,18 @@
}
/**
- * Sets a {@link Bundle} that will be passed to subsequent calls to {@link AutoFillService}
- * methods such as
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
- * {@link AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key.
+ * Sets a {@link Bundle} that will be passed to subsequent calls to {@link
+ * android.service.autofill.AutoFillService} methods such as {@link
+ * android.service.autofill.AutoFillService#onSaveRequest(
+ * android.app.assist.AssistStructure, Bundle, android.service.autofill.SaveCallback)},
+ * using {@link
+ * android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key.
*
* <p>It can be used when to keep service state in between calls.
*/
public Builder setExtras(Bundle extras) {
+ // TODO(b/33197203): make sure that either this method or the requires-Authentication
+ // ones are called, but not both
mExtras = Objects.requireNonNull(extras, "extras cannot be null");
return this;
}
@@ -246,7 +416,10 @@
final StringBuilder builder = new StringBuilder("FillResponse: [datasets=")
.append(mDatasets).append(", savableIds=").append(Arrays.toString(mSavableIds))
.append(", extras=");
- append(builder, mExtras);
+ append(builder, mExtras)
+ .append(", flags=").append(mFlags)
+ .append(", requiresAuth: ").append(mRequiresAuth)
+ .append(", hasCrypto: ").append(mHasCryptoObject);
return builder.append(']').toString();
}
@@ -264,6 +437,12 @@
parcel.writeList(mDatasets);
parcel.writeParcelableArray(mSavableIds, 0);
parcel.writeBundle(mExtras);
+ parcel.writeInt(mFlags);
+ parcel.writeInt(mRequiresAuth ? 1 : 0);
+ parcel.writeInt(mHasCryptoObject ? 1 : 0);
+ if (mHasCryptoObject) {
+ parcel.writeLong(mCryptoOpId);
+ }
}
private FillResponse(Parcel parcel) {
@@ -271,6 +450,10 @@
parcel.readList(mDatasets, null);
mSavableIds = parcel.readParcelableArray(null, AutoFillId.class);
mExtras = parcel.readBundle();
+ mFlags = parcel.readInt();
+ mRequiresAuth = parcel.readInt() == 1;
+ mHasCryptoObject = parcel.readInt() == 1;
+ mCryptoOpId = mHasCryptoObject ? parcel.readLong() : 0;
}
public static final Parcelable.Creator<FillResponse> CREATOR =
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
index 772710e..14cf9e8 100644
--- a/core/java/android/view/autofill/Helper.java
+++ b/core/java/android/view/autofill/Helper.java
@@ -18,28 +18,33 @@
import android.os.Bundle;
+import java.util.Arrays;
+import java.util.Objects;
import java.util.Set;
/** @hide */
public final class Helper {
- // TODO(b/33197203): set to false when stable
- static final boolean DEBUG = true;
+ static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
static final String REDACTED = "[REDACTED]";
- static void append(StringBuilder builder, Bundle bundle) {
+ static StringBuilder append(StringBuilder builder, Bundle bundle) {
if (bundle == null) {
builder.append("N/A");
} else if (!DEBUG) {
builder.append(REDACTED);
} else {
final Set<String> keySet = bundle.keySet();
- builder.append("[bundle with ").append(keySet.size()).append(" extras:");
+ builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
for (String key : keySet) {
- builder.append(' ').append(key).append('=').append(bundle.get(key)).append(',');
+ final Object value = bundle.get(key);
+ builder.append(' ').append(key).append('=');
+ builder.append((value instanceof Object[])
+ ? Arrays.toString((Objects[]) value) : value);
}
builder.append(']');
}
+ return builder;
}
private Helper() {
diff --git a/core/java/android/view/autofill/VirtualViewDelegate.java b/core/java/android/view/autofill/VirtualViewDelegate.java
index a19b4e5..278bf4f 100644
--- a/core/java/android/view/autofill/VirtualViewDelegate.java
+++ b/core/java/android/view/autofill/VirtualViewDelegate.java
@@ -15,6 +15,8 @@
*/
package android.view.autofill;
+import android.annotation.Nullable;
+import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.ViewStructure;
@@ -71,20 +73,18 @@
public abstract static class Callback {
/**
- * Sent when the focus inside the hierarchy changed.
+ * Sent when the auto-fill bar for a child must be updated.
*
- * <p>Typically callled twice - for the nodes that lost and gained focus.
- *
- * <p>This method should only be called when the change was not caused by the AutoFill
- * Framework itselft (i.e, through {@link VirtualViewDelegate#autoFill(int, AutoFillValue)},
- * but by external causes (for example, when the user changed the value through the view's
- * UI).
- *
- * @param virtualId id of the node whose focus changed.
- * @param hasFocus {@code true} when focus was gained, {@code false} when it was lost.
+ * See {@link AutoFillManager#updateAutoFillInput(View, int, android.graphics.Rect, int)}
+ * for more details.
*/
- public void onFocusChanged(int virtualId, boolean hasFocus) {
- if (DEBUG) Log.d(TAG, "onFocusChanged() for " + virtualId + ": " + hasFocus);
+ // TODO(b/33197203): do we really need it, or should the parent view just call
+ // AutoFillManager.updateAutoFillInput() directly?
+ public void onAutoFillInputUpdated(int virtualId, @Nullable Rect boundaries, int flags) {
+ if (DEBUG) {
+ Log.v(TAG, "onAutoFillInputUpdated(): virtualId=" + virtualId + ", boundaries="
+ + boundaries + ", flags=" + flags);
+ }
}
/**
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..0657067
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.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 android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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.icu.text.BreakIterator;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.SmartSelection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.method.WordIterator;
+import android.text.style.ClickableSpan;
+import android.text.util.Linkify;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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));
+ return createClassificationResult(type, classified);
+ }
+ }
+ } 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(CharSequence text, int linkMask) {
+ Preconditions.checkArgument(text != null);
+ try {
+ return LinksInfoFactory.create(
+ mContext, getSmartSelection(), text.toString(), linkMask);
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG, "Error getting links info.", t);
+ }
+ // Getting here means something went wrong, return a NO_OP result.
+ 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(String type, CharSequence text) {
+ final Intent intent = IntentFactory.create(type, text.toString());
+ if (intent == null) {
+ return TextClassificationResult.EMPTY;
+ }
+
+ final TextClassificationResult.Builder builder = new TextClassificationResult.Builder()
+ .setText(text.toString())
+ .setEntityType(type, 1.0f /* confidence */)
+ .setIntent(intent)
+ .setOnClickListener(TextClassificationResult.createStartActivityOnClick(
+ mContext, intent))
+ .setLabel(IntentFactory.getLabel(mContext, type));
+ final PackageManager pm = mContext.getPackageManager();
+ final 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);
+ }
+
+ /**
+ * Detects and creates links for specified text.
+ */
+ private static final class LinksInfoFactory {
+
+ private LinksInfoFactory() {}
+
+ public static LinksInfo create(
+ Context context, SmartSelection smartSelection, String text, int linkMask) {
+ final WordIterator wordIterator = new WordIterator();
+ wordIterator.setCharSequence(text, 0, text.length());
+ final List<SpanSpec> spans = new ArrayList<>();
+ int start = 0;
+ int end;
+ while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
+ final String token = text.substring(start, end);
+ if (TextUtils.isEmpty(token)) {
+ continue;
+ }
+
+ final int[] selection = smartSelection.suggest(text, start, end);
+ final int selectionStart = selection[0];
+ final int selectionEnd = selection[1];
+ if (selectionStart >= 0 && selectionEnd <= text.length()
+ && selectionStart <= selectionEnd) {
+ final String type =
+ smartSelection.classifyText(text, selectionStart, selectionEnd);
+ if (matches(type, linkMask)) {
+ final ClickableSpan span = createSpan(
+ context, type, text.substring(selectionStart, selectionEnd));
+ spans.add(new SpanSpec(selectionStart, selectionEnd, span));
+ }
+ }
+ start = end;
+ }
+ return new LinksInfoImpl(text, avoidOverlaps(spans, text));
+ }
+
+ /**
+ * Returns true if the classification type matches the specified linkMask.
+ */
+ private static boolean matches(String type, int linkMask) {
+ if ((linkMask & Linkify.PHONE_NUMBERS) != 0
+ && TextClassifier.TYPE_PHONE.equals(type)) {
+ return true;
+ }
+ if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
+ && TextClassifier.TYPE_EMAIL.equals(type)) {
+ return true;
+ }
+ if ((linkMask & Linkify.MAP_ADDRESSES) != 0
+ && TextClassifier.TYPE_ADDRESS.equals(type)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Trim the number of spans so that no two spans overlap.
+ *
+ * This algorithm first ensures that there is only one span per start index, then it
+ * makes sure that no two spans overlap.
+ */
+ private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
+ Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
+ // Group spans by start index. Take the longest span.
+ final Map<Integer, SpanSpec> reps = new LinkedHashMap<>(); // order matters.
+ final int size = spans.size();
+ for (int i = 0; i < size; i++) {
+ final SpanSpec span = spans.get(i);
+ final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
+ if (rep == null || rep.mEnd < span.mEnd) {
+ reps.put(span.mStart, span);
+ }
+ }
+ // Avoid span intersections. Take the longer span.
+ final LinkedList<SpanSpec> result = new LinkedList<>();
+ for (SpanSpec rep : reps.values()) {
+ if (result.isEmpty()) {
+ result.add(rep);
+ continue;
+ }
+
+ final SpanSpec last = result.getLast();
+ if (rep.mStart < last.mEnd) {
+ // Spans intersect. Use the one with characters.
+ if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
+ result.set(result.size() - 1, rep);
+ }
+ } else {
+ result.add(rep);
+ }
+ }
+ return result;
+ }
+
+ private static ClickableSpan createSpan(
+ final Context context, final String type, final String text) {
+ return new ClickableSpan() {
+ // TODO: Style this span.
+ @Override
+ public void onClick(View widget) {
+ context.startActivity(IntentFactory.create(type, text));
+ }
+ };
+ }
+
+ /**
+ * Implementation of LinksInfo that adds ClickableSpans to the specified text.
+ */
+ private static final class LinksInfoImpl implements LinksInfo {
+
+ private final CharSequence mOriginalText;
+ private final List<SpanSpec> mSpans;
+
+ LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
+ mOriginalText = originalText;
+ mSpans = spans;
+ }
+
+ @Override
+ public boolean apply(@NonNull CharSequence text) {
+ Preconditions.checkArgument(text != null);
+ if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
+ Spannable spannable = (Spannable) text;
+ final int size = mSpans.size();
+ for (int i = 0; i < size; i++) {
+ final SpanSpec span = mSpans.get(i);
+ spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Span plus its start and end index.
+ */
+ private static final class SpanSpec {
+
+ private final int mStart;
+ private final int mEnd;
+ private final ClickableSpan mSpan;
+
+ SpanSpec(int start, int end, ClickableSpan span) {
+ mStart = start;
+ mEnd = end;
+ mSpan = span;
+ }
+ }
+ }
+
+ /**
+ * Creates intents based on the classification type.
+ */
+ private static final class IntentFactory {
+
+ private IntentFactory() {}
+
+ @Nullable
+ public static Intent create(String type, String text) {
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ return new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("mailto:%s", text)));
+ case TextClassifier.TYPE_PHONE:
+ return new Intent(Intent.ACTION_DIAL)
+ .setData(Uri.parse(String.format("tel:%s", text)));
+ case TextClassifier.TYPE_ADDRESS:
+ return new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+ default:
+ return null;
+ // TODO: Add other classification types.
+ }
+ }
+
+ @Nullable
+ public static String getLabel(Context context, String type) {
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ return context.getString(com.android.internal.R.string.email);
+ case TextClassifier.TYPE_PHONE:
+ return context.getString(com.android.internal.R.string.dial);
+ case TextClassifier.TYPE_ADDRESS:
+ return context.getString(com.android.internal.R.string.map);
+ default:
+ return null;
+ // TODO: Add other classification types.
+ }
+ }
+ }
+}
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/RenderProcessGoneDetail.java b/core/java/android/webkit/RenderProcessGoneDetail.java
index 77d8596..1c79399 100644
--- a/core/java/android/webkit/RenderProcessGoneDetail.java
+++ b/core/java/android/webkit/RenderProcessGoneDetail.java
@@ -32,4 +32,15 @@
* system.
**/
public abstract boolean didCrash();
+
+ /**
+ * Returns the renderer priority that was set at the time that the
+ * renderer exited. This may be greater than the priority that
+ * any individual {@link WebView} requested using
+ * {@link WebView#setRendererPriorityPolicy}.
+ *
+ * @return the priority of the renderer at exit.
+ **/
+ @WebView.RendererPriority
+ public abstract int rendererPriorityAtExit();
}
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/WebView.java b/core/java/android/webkit/WebView.java
index f98c099..1e7cddf 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.Widget;
@@ -60,6 +61,8 @@
import java.io.BufferedWriter;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Map;
/**
@@ -2151,6 +2154,94 @@
return mProvider.findHierarchyView(className, hashCode);
}
+ /** @hide */
+ @IntDef({
+ RENDERER_PRIORITY_WAIVED,
+ RENDERER_PRIORITY_BOUND,
+ RENDERER_PRIORITY_IMPORTANT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RendererPriority {}
+
+ /**
+ * The renderer associated with this WebView is bound with
+ * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
+ * {@link WebView} renderers will be strong targets for out of memory
+ * killing.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_WAIVED = 0;
+ /**
+ * The renderer associated with this WebView is bound with
+ * the default priority for services.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_BOUND = 1;
+ /**
+ * The renderer associated with this WebView is bound with
+ * {@link Context#BIND_IMPORTANT}.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_IMPORTANT = 2;
+
+ /**
+ * Set the renderer priority policy for this {@link WebView}. The
+ * priority policy will be used to determine whether an out of
+ * process renderer should be considered to be a target for OOM
+ * killing.
+ *
+ * Because a renderer can be associated with more than one
+ * WebView, the final priority it is computed as the maximum of
+ * any attached WebViews. When a WebView is destroyed it will
+ * cease to be considerered when calculating the renderer
+ * priority. Once no WebViews remain associated with the renderer,
+ * the priority of the renderer will be reduced to
+ * {@link #RENDERER_PRIORITY_WAIVED}.
+ *
+ * The default policy is to set the priority to
+ * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
+ * and this should not be changed unless the caller also handles
+ * renderer crashes with
+ * {@link WebViewClient#onRenderProcessGone}. Any other setting
+ * will result in WebView renderers being killed by the system
+ * more aggressively than the application.
+ *
+ * @param rendererRequestedPriority the minimum priority at which
+ * this WebView desires the renderer process to be bound.
+ * @param waivedWhenNotVisible if true, this flag specifies that
+ * when this WebView is not visible, it will be treated as
+ * if it had requested a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED}.
+ */
+ public void setRendererPriorityPolicy(
+ @RendererPriority int rendererRequestedPriority,
+ boolean waivedWhenNotVisible) {
+ mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible);
+ }
+
+ /**
+ * Get the requested renderer priority for this WebView.
+ *
+ * @return the requested renderer priority policy.
+ */
+ @RendererPriority
+ public int getRendererRequestedPriority() {
+ return mProvider.getRendererRequestedPriority();
+ }
+
+ /**
+ * Return whether this WebView requests a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+ *
+ * @return whether this WebView requests a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+ */
+ public boolean getRendererPriorityWaivedWhenNotVisible() {
+ return mProvider.getRendererPriorityWaivedWhenNotVisible();
+ }
//-------------------------------------------------------------------------
// Interface for WebView providers
//-------------------------------------------------------------------------
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/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index dd1b0d2..ffc18b1 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -269,6 +269,12 @@
public View findHierarchyView(String className, int hashCode);
+ public void setRendererPriorityPolicy(int rendererRequestedPriority, boolean waivedWhenNotVisible);
+
+ public int getRendererRequestedPriority();
+
+ public boolean getRendererPriorityWaivedWhenNotVisible();
+
//-------------------------------------------------------------------------
// Provider internal methods
//-------------------------------------------------------------------------
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index af5c842..6706790 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -42,6 +42,8 @@
* <p>See the <a href="{@docRoot}guide/topics/ui/controls/text.html">Text Fields</a>
* guide.</p>
* <p>
+ * This widget does not support auto-sizing text.
+ * <p>
* <b>XML attributes</b>
* <p>
* See {@link android.R.styleable#EditText EditText Attributes},
@@ -167,8 +169,6 @@
Log.w(VIEW_LOG_TAG, "EditText.autoFill(): no text on AutoFillValue");
return;
}
- // TODO(b/33197203): once auto-fill is triggered by the IME, we'll need a new setText()
- // or setAutoFillText() method on TextView to avoid re-triggering it.
setText(text);
}
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/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index eb27533..59bbc3b 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.MenuRes;
+import android.annotation.TestApi;
import android.content.Context;
import android.view.Gravity;
import android.view.Menu;
@@ -281,4 +282,19 @@
*/
void onDismiss(PopupMenu menu);
}
+
+ /**
+ * Returns the {@link ListView} representing the list of menu items in the currently showing
+ * menu.
+ *
+ * @return The view representing the list of menu items.
+ * @hide
+ */
+ @TestApi
+ public ListView getMenuListView() {
+ if (!mPopup.isShowing()) {
+ return null;
+ }
+ return mPopup.getPopup().getListView();
+ }
}
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/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index eb46b9f..359d04e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -3302,7 +3302,7 @@
* Applies the views asynchronously, moving as much of the task on the background
* thread as possible.
*
- * @see {@link #apply(Context, ViewGroup)}
+ * @see #apply(Context, ViewGroup)
* @param context Default context to use
* @param parent Parent that the resulting view hierarchy will be attached to. This method
* does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
@@ -3457,7 +3457,7 @@
* Applies all the actions to the provided view, moving as much of the task on the background
* thread as possible.
*
- * @see {@link #reapply(Context, View)}
+ * @see #reapply(Context, View)
* @param context Default context to use
* @param v The view to apply the actions to. This should be the result of
* the {@link #apply(Context,ViewGroup)} call.
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 b6693c7..c0bec69 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -22,6 +22,7 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
@@ -76,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;
@@ -137,6 +136,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
+import android.view.autofill.AutoFillManager;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
@@ -145,6 +145,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;
@@ -158,6 +160,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Locale;
@@ -252,11 +256,16 @@
* @attr ref android.R.styleable#TextView_fontFeatureSettings
* @attr ref android.R.styleable#TextView_breakStrategy
* @attr ref android.R.styleable#TextView_hyphenationFrequency
+ * @attr ref android.R.styleable#TextView_autoSizeText
+ * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
+ * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
+ * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
static final String LOG_TAG = "TextView";
static final boolean DEBUG_EXTRACT = false;
+ static final boolean DEBUG_AUTOFILL = false;
// Enum for the "typeface" XML parameter.
// TODO: How can we get this from the XML instead of hardcoding it here?
@@ -345,6 +354,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
@@ -670,17 +681,31 @@
*/
private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
- // The TextView does not auto-size text.
- public static final int AUTO_SIZE_TYPE_NONE = 0;
+ // The TextView does not auto-size text (default).
+ public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
// The TextView performs uniform horizontal and vertical text size scaling to fit within the
// container.
- public static final int AUTO_SIZE_TYPE_XY = 1;
- // Auto-size type.
- private int mAutoSizeType = AUTO_SIZE_TYPE_NONE;
- // Specify if auto-size is needed.
- private boolean mNeedsAutoSize = false;
+ public static final int AUTO_SIZE_TEXT_TYPE_XY = 1;
+ /** @hide */
+ @IntDef({AUTO_SIZE_TEXT_TYPE_NONE, AUTO_SIZE_TEXT_TYPE_XY})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutoSizeTextType {}
+ // Default minimum size for auto-sizing text in scaled pixels. {@see #setAutoSizeMinTextSize}.
+ private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
+ // Default maximum size for auto-sizing text in scaled pixels. {@see #setAutoSizeMaxTextSize}.
+ private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
// Default value for the step size in pixels.
private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
+ // Auto-size text type.
+ private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
+ // Specify if auto-size text is needed.
+ private boolean mNeedsAutoSizeText = false;
+ // Step size for auto-sizing in pixels.
+ private int mAutoSizeStepGranularityInPx = 0;
+ // Minimum text size for auto-sizing in pixels.
+ private int mAutoSizeMinTextSizeInPx = 0;
+ // Maximum text size for auto-sizing in pixels.
+ private int mAutoSizeMaxTextSizeInPx = 0;
// Contains the sorted set of desired text sizes in pixels to pick from when auto-sizing text.
private int[] mAutoSizeTextSizesInPx;
@@ -887,13 +912,6 @@
CharSequence hint = null;
boolean password = false;
int inputType = EditorInfo.TYPE_NULL;
- int autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
- int autoSizeMinTextSize = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics());
- int autoSizeMaxTextSize = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP, 112, getResources().getDisplayMetrics());
-
-
a = theme.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
@@ -1249,20 +1267,19 @@
break;
case com.android.internal.R.styleable.TextView_autoSizeText:
- mAutoSizeType = a.getInt(attr, AUTO_SIZE_TYPE_NONE);
+ mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
break;
case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
- autoSizeStepGranularityInPx = a.getDimensionPixelSize(
- attr, DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
+ mAutoSizeStepGranularityInPx = a.getDimensionPixelSize(attr, 0);
break;
case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
- autoSizeMinTextSize = a.getDimensionPixelSize(attr, autoSizeMinTextSize);
+ mAutoSizeMinTextSizeInPx = a.getDimensionPixelSize(attr, 0);
break;
case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
- autoSizeMaxTextSize = a.getDimensionPixelSize(attr, autoSizeMaxTextSize);
+ mAutoSizeMaxTextSizeInPx = a.getDimensionPixelSize(attr, 0);
break;
}
}
@@ -1500,26 +1517,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;
@@ -1531,7 +1544,6 @@
}
a.recycle();
- setFocusable(focusable);
setClickable(clickable);
setLongClickable(longClickable);
@@ -1542,39 +1554,204 @@
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- // Setup auto-size.
+ setupAutoSizeTextXY();
+ }
+
+ /**
+ * Specify whether this widget should automatically scale the text to try to perfectly fit
+ * within the layout bounds by taking into account the auto-size configuration.
+ *
+ * @param autoSizeTextType the type of auto-size. Must be one of
+ * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
+ * {@link TextView#AUTO_SIZE_TEXT_TYPE_XY}
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeText
+ *
+ * @see #getAutoSizeTextType()
+ */
+ public void setAutoSizeTextType(@AutoSizeTextType int autoSizeTextType) {
if (supportsAutoSizeText()) {
- switch (mAutoSizeType) {
- case AUTO_SIZE_TYPE_NONE:
- // Nothing to do.
+ switch (autoSizeTextType) {
+ case AUTO_SIZE_TEXT_TYPE_NONE:
+ if (mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE) {
+ // Clear all auto-size configuration
+ mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
+ mAutoSizeMinTextSizeInPx = 0;
+ mAutoSizeMaxTextSizeInPx = 0;
+ mAutoSizeStepGranularityInPx = 0;
+ mAutoSizeTextSizesInPx = null;
+ mNeedsAutoSizeText = false;
+ }
break;
- case AUTO_SIZE_TYPE_XY:
- if (autoSizeMaxTextSize <= autoSizeMinTextSize) {
- throw new IllegalStateException("Maximum text size is less then minimum "
- + "text size");
+ case AUTO_SIZE_TEXT_TYPE_XY:
+ if (mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_XY) {
+ mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_XY;
+ setupAutoSizeTextXY();
}
-
- if (autoSizeStepGranularityInPx <= 0) {
- throw new IllegalStateException("Unexpected zero or negative value for auto"
- + " size step granularity in pixels");
- }
-
- final int autoSizeValuesLength = (autoSizeMaxTextSize - autoSizeMinTextSize)
- / autoSizeStepGranularityInPx;
- mAutoSizeTextSizesInPx = new int[autoSizeValuesLength];
- int sizeToAdd = autoSizeMinTextSize;
- for (int i = 0; i < autoSizeValuesLength; i++) {
- mAutoSizeTextSizesInPx[i] = sizeToAdd;
- sizeToAdd += autoSizeStepGranularityInPx;
- }
-
- mNeedsAutoSize = true;
break;
default:
throw new IllegalArgumentException(
- "Unknown autoSizeText type: " + mAutoSizeType);
+ "Unknown auto-size text type: " + autoSizeTextType);
+ }
+ }
+ }
+
+ /**
+ * Returns the type of auto-size set for this widget.
+ *
+ * @return an {@code int} corresponding to one of the auto-size types:
+ * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
+ * {@link TextView#AUTO_SIZE_TEXT_TYPE_XY}
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeText
+ *
+ * @see #setAutoSizeTextType(int)
+ */
+ @AutoSizeTextType
+ public int getAutoSizeTextType() {
+ return mAutoSizeTextType;
+ }
+
+ /**
+ * Sets the auto-size step granularity. It is used in conjunction with auto-size minimum
+ * and maximum text size in order to build the set of text sizes the system uses to choose
+ * from when auto-sizing.
+ *
+ * @param unit the desired dimension unit. See {@link TypedValue} for the possible
+ * dimension units
+ * @param size the desired size in the given units
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
+ *
+ * @see #getAutoSizeStepGranularity()
+ * @see #setAutoSizeMinTextSize(int, float)
+ * @see #setAutoSizeMaxTextSize(int, float)
+ */
+ public void setAutoSizeStepGranularity(int unit, float size) {
+ if (supportsAutoSizeText()) {
+ mAutoSizeStepGranularityInPx = (int) TypedValue.applyDimension(
+ unit, size, getResources().getDisplayMetrics());
+ setupAutoSizeTextXY();
+ }
+ }
+
+ /**
+ * @return the current auto-size step granularity in pixels.
+ *
+ * @see #setAutoSizeStepGranularity(int, float)
+ */
+ public int getAutoSizeStepGranularity() {
+ return mAutoSizeStepGranularityInPx;
+ }
+
+ /**
+ * Sets the minimum text size to be used in conjunction with auto-size maximum text size and
+ * auto-size step granularity in order to build the set of text sizes the system uses to choose
+ * from when auto-sizing.
+ *
+ * @param unit the desired dimension unit. See {@link TypedValue} for the possible
+ * dimension units
+ * @param size the desired size in the given units
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
+ *
+ * @see #getAutoSizeMinTextSize()
+ * @see #setAutoSizeMaxTextSize(int, float)
+ * @see #setAutoSizeStepGranularity(int, float)
+ */
+ public void setAutoSizeMinTextSize(int unit, float size) {
+ if (supportsAutoSizeText()) {
+ mAutoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
+ unit, size, getResources().getDisplayMetrics());
+ setupAutoSizeTextXY();
+ }
+ }
+
+ /**
+ * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
+ * if auto-size has not been configured this function returns {@code 0}.
+ *
+ * @see #setAutoSizeMinTextSize(int, float)
+ */
+ public int getAutoSizeMinTextSize() {
+ return mAutoSizeMinTextSizeInPx;
+ }
+
+ /**
+ * Sets the maximum text size to be used in conjunction with auto-size minimum text size and
+ * auto-size step granularity in order to build the set of text sizes the system uses to choose
+ * from when auto-sizing.
+ *
+ * @param unit the desired dimension unit. See {@link TypedValue} for the possible
+ * dimension units
+ * @param size the desired size in the given units
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
+ *
+ * @see #getAutoSizeMaxTextSize()
+ * @see #setAutoSizeMinTextSize(int, float)
+ * @see #setAutoSizeStepGranularity(int, float)
+ */
+ public void setAutoSizeMaxTextSize(int unit, float size) {
+ if (supportsAutoSizeText()) {
+ mAutoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
+ unit, size, getResources().getDisplayMetrics());
+ setupAutoSizeTextXY();
+ }
+ }
+
+ /**
+ * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
+ * if auto-size has not been configured this function returns {@code 0}.
+ *
+ * @see #setAutoSizeMaxTextSize(int, float)
+ */
+ public int getAutoSizeMaxTextSize() {
+ return mAutoSizeMaxTextSizeInPx;
+ }
+
+ private void setupAutoSizeTextXY() {
+ if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_XY) {
+ // Set valid defaults.
+ if (mAutoSizeMinTextSizeInPx <= 0) {
+ mAutoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
+ getResources().getDisplayMetrics());
}
+ if (mAutoSizeMaxTextSizeInPx <= 0) {
+ mAutoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
+ getResources().getDisplayMetrics());
+ }
+
+ if (mAutoSizeStepGranularityInPx <= 0) {
+ mAutoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
+ }
+
+ // Validate.
+ if (mAutoSizeMaxTextSizeInPx <= mAutoSizeMinTextSizeInPx) {
+ throw new IllegalStateException("Maximum auto-size text size ("
+ + mAutoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
+ + "text size (" + mAutoSizeMinTextSizeInPx + "px)");
+ }
+
+ // Calculate sizes to choose from based on the current auto-size configuration.
+ final int autoSizeValuesLength = (int) Math.ceil(
+ (mAutoSizeMaxTextSizeInPx - mAutoSizeMinTextSizeInPx)
+ / mAutoSizeStepGranularityInPx);
+
+ mAutoSizeTextSizesInPx = new int[autoSizeValuesLength];
+ int sizeToAdd = mAutoSizeMinTextSizeInPx;
+ for (int i = 0; i < autoSizeValuesLength; i++) {
+ mAutoSizeTextSizesInPx[i] = sizeToAdd;
+ sizeToAdd += mAutoSizeStepGranularityInPx;
+ }
+
+ mNeedsAutoSizeText = true;
+ autoSizeText();
}
}
@@ -1975,11 +2152,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);
}
@@ -3030,9 +3207,6 @@
/**
* @return the size (in pixels) of the default text size in this TextView.
- *
- * <p>Note: if this TextView has mAutoSizeType set to {@link TextView#AUTO_SIZE_TYPE_XY} than
- * this function returns the maximum text size for auto-sizing.
*/
@ViewDebug.ExportedProperty(category = "text")
public float getTextSize() {
@@ -3077,7 +3251,7 @@
}
/**
- * Set the default text size to a given unit and value. See {@link
+ * Set the default text size to a given unit and value. See {@link
* TypedValue} for the possible dimension units.
*
* <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
@@ -3113,7 +3287,7 @@
if (mLayout != null) {
// Do not auto-size right after setting the text size.
- mNeedsAutoSize = false;
+ mNeedsAutoSizeText = false;
nullLayouts();
requestLayout();
invalidate();
@@ -3257,20 +3431,6 @@
}
/**
- * Returns the font variation settings.
- *
- * @return the currently set font variation settings. Returns null if no variation is
- * specified.
- *
- * @see #setFontVariationSettings(String)
- * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
- */
- @Nullable
- public String getFontVariationSettings() {
- return mTextPaint.getFontVariationSettings();
- }
-
- /**
* Sets the break strategy for breaking paragraphs into lines. The default value for
* TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
* EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
@@ -3377,41 +3537,6 @@
/**
- * Sets TrueType or OpenType font variation settings. The settings string is constructed from
- * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
- * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
- * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
- * are invalid. If a specified axis name is not defined in the font, the settings will be
- * ignored.
- *
- * <pre>
- * textView.setFontVariationSettings("'wdth' 1.0");
- * textView.setFontVariationSettings("'AX ' 1.8, 'FB ' 2.0");
- * </pre>
- *
- * @param fontVariationSettings font variation settings. You can pass null or empty string as
- * no variation settings.
- *
- * @see #getFontVariationSettings()
- * @see Paint#getFontVariationSettings() Paint.getFontVariationSettings()
- */
- public void setFontVariationSettings(@Nullable String fontVariationSettings) {
- final String existingSettings = mTextPaint.getFontVariationSettings();
- if (fontVariationSettings == existingSettings
- || (fontVariationSettings != null
- && fontVariationSettings.equals(existingSettings))) {
- return;
- }
- mTextPaint.setFontVariationSettings(fontVariationSettings);
-
- if (mLayout != null) {
- nullLayouts();
- requestLayout();
- invalidate();
- }
- }
-
- /**
* Sets the text color for all the states (normal, selected,
* focused) to be this color.
*
@@ -5998,7 +6123,7 @@
mEditor.mTextIsSelectable = selectable;
setFocusableInTouchMode(selectable);
- setFocusable(selectable);
+ setFocusable(FOCUSABLE_AUTO);
setClickable(selectable);
setLongClickable(selectable);
@@ -7612,13 +7737,13 @@
}
if (isAutoSizeEnabled()) {
- if (mNeedsAutoSize) {
+ if (mNeedsAutoSizeText) {
// Call auto-size after the width and height have been calculated.
autoSizeText();
}
// Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
// after the next measuring round should set this to false.
- mNeedsAutoSize = true;
+ mNeedsAutoSizeText = true;
}
setMeasuredDimension(width, height);
@@ -8901,6 +9026,14 @@
Spannable sp = (Spannable) mText;
MetaKeyKeyListener.resetMetaState(sp);
}
+ } else {
+ final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
+ if (afm != null) {
+ if (DEBUG_AUTOFILL) {
+ Log.v(LOG_TAG, "onFocusChanged(false): id=" + getAccessibilityViewId());
+ }
+ afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_HIDE);
+ }
}
startStopMarquee(focused);
@@ -9377,7 +9510,7 @@
* auto-size.
*/
private boolean isAutoSizeEnabled() {
- return supportsAutoSizeText() && mAutoSizeType != AUTO_SIZE_TYPE_NONE;
+ return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
}
/**
@@ -9764,7 +9897,7 @@
Selection.setSelection((Spannable) text, start, end);
// Make sure selection mode is engaged.
if (mEditor != null) {
- mEditor.startSelectionActionMode();
+ mEditor.startSelectionActionMode(null);
}
return true;
}
@@ -9908,6 +10041,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
@@ -10132,33 +10266,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;
}
/**
@@ -10481,6 +10612,14 @@
* @hide
*/
protected void viewClicked(InputMethodManager imm) {
+ final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
+ if (afm != null) {
+ if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAccessibilityViewId());
+
+ // TODO(b/33197203): integrate with onFocus and/or move to view?
+ afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_SHOW);
+ }
+
if (imm != null) {
imm.viewClicked(this);
}
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/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index d9ab47e..096fcb8 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -343,53 +343,42 @@
class LogisticRegressionAppRanker {
private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
private static final String BIAS_PREF_KEY = "bias";
- private static final float LEARNING_RATE = 0.02f;
- private static final float REGULARIZER_PARAM = 0.1f;
+ private static final String VERSION_PREF_KEY = "version";
+
+ // parameters for a pre-trained model, to initialize the app ranker. When updating the
+ // pre-trained model, please update these params, as well as initModel().
+ private static final int CURRENT_VERSION = 1;
+ private static final float LEARNING_RATE = 0.0001f;
+ private static final float REGULARIZER_PARAM = 0.0001f;
+
private SharedPreferences mParamSharedPref;
private ArrayMap<String, Float> mFeatureWeights;
private float mBias;
public LogisticRegressionAppRanker(Context context) {
mParamSharedPref = getParamSharedPref(context);
+ initModel();
}
public float predict(ArrayMap<String, Float> target) {
- if (target == null || mParamSharedPref == null) {
+ if (target == null) {
return 0.0f;
}
final int featureSize = target.size();
- if (featureSize == 0) {
- return 0.0f;
- }
float sum = 0.0f;
- if (mFeatureWeights == null) {
- mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f);
- mFeatureWeights = new ArrayMap<>(featureSize);
- for (int i = 0; i < featureSize; i++) {
- String featureName = target.keyAt(i);
- float weight = mParamSharedPref.getFloat(featureName, 0.0f);
- sum += weight * target.valueAt(i);
- mFeatureWeights.put(featureName, weight);
- }
- } else {
- for (int i = 0; i < featureSize; i++) {
- String featureName = target.keyAt(i);
- float weight = mFeatureWeights.getOrDefault(featureName, 0.0f);
- sum += weight * target.valueAt(i);
- }
+ for (int i = 0; i < featureSize; i++) {
+ String featureName = target.keyAt(i);
+ float weight = mFeatureWeights.getOrDefault(featureName, 0.0f);
+ sum += weight * target.valueAt(i);
}
return (float) (1.0 / (1.0 + Math.exp(-mBias - sum)));
}
public void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
- if (target == null || target.size() == 0) {
+ if (target == null) {
return;
}
final int featureSize = target.size();
- if (mFeatureWeights == null) {
- mBias = 0.0f;
- mFeatureWeights = new ArrayMap<>(featureSize);
- }
float error = isSelected ? 1.0f - predict : -predict;
for (int i = 0; i < featureSize; i++) {
String featureName = target.keyAt(i);
@@ -405,15 +394,13 @@
}
public void commitUpdate() {
- if (mFeatureWeights == null || mFeatureWeights.size() == 0) {
- return;
- }
SharedPreferences.Editor editor = mParamSharedPref.edit();
editor.putFloat(BIAS_PREF_KEY, mBias);
final int size = mFeatureWeights.size();
for (int i = 0; i < size; i++) {
editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i));
}
+ editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION);
editor.apply();
}
@@ -431,5 +418,27 @@
PARAM_SHARED_PREF_NAME + ".xml");
return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
}
+
+ private void initModel() {
+ mFeatureWeights = new ArrayMap<>(4);
+ if (mParamSharedPref == null ||
+ mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) {
+ // Initializing the app ranker to a pre-trained model. When updating the pre-trained
+ // model, please increment CURRENT_VERSION, and update LEARNING_RATE and
+ // REGULARIZER_PARAM.
+ mBias = -1.6568f;
+ mFeatureWeights.put(LAUNCH_SCORE, 2.5543f);
+ mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f);
+ mFeatureWeights.put(RECENCY_SCORE, 0.269f);
+ mFeatureWeights.put(CHOOSER_SCORE, 4.2222f);
+ } else {
+ mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f);
+ mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f));
+ mFeatureWeights.put(
+ TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f));
+ mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f));
+ mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f));
+ }
+ }
}
}
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index f88f6f9..4071ff4 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
+ public ResolveInfo getLastChosen() throws RemoteException {
+ return AppGlobals.getPackageManager().getLastChosenActivity(
+ mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.MATCH_DEFAULT_ONLY);
+ }
+
+ @VisibleForTesting
+ public 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/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index a1eac36..81db93d 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -69,5 +69,6 @@
boolean isBoundWidgetPackage(String packageName, int userId);
boolean requestPinAppWidget(String packageName, in ComponentName providerComponent,
in IntentSender resultIntent);
+ boolean isRequestPinAppWidgetSupported();
}
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..4d19564 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/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
index c828d11..e8919ed 100644
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -120,7 +120,7 @@
sb.append(" s=");
TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb);
sb.append(" p=").append(powerDeltaMaUs / 1000).append("mAms");
- Slog.wtf(TAG, sb.toString());
+ Slog.e(TAG, sb.toString());
userTimeDeltaUs = 0;
systemTimeDeltaUs = 0;
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 8eb75c0..a3b066a 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -25,7 +25,6 @@
import android.icu.text.DecimalFormatSymbols;
import android.icu.util.ULocale;
import android.net.LocalServerSocket;
-import android.opengl.EGL14;
import android.os.IInstalld;
import android.os.Process;
import android.os.RemoteException;
@@ -125,9 +124,6 @@
bootTimingsTraceLog.traceBegin("PreloadResources");
preloadResources();
bootTimingsTraceLog.traceEnd(); // PreloadResources
- bootTimingsTraceLog.traceBegin("PreloadOpenGL");
- preloadOpenGL();
- bootTimingsTraceLog.traceEnd(); // PreloadOpenGL
preloadSharedLibraries();
preloadTextResources();
// Ask the WebViewFactory to do any initialization that must run in the zygote process,
@@ -177,12 +173,6 @@
System.loadLibrary("jnigraphics");
}
- private static void preloadOpenGL() {
- if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false)) {
- EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
- }
- }
-
private static void preloadTextResources() {
Hyphenator.init();
TextView.preloadFontCache();
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 789e9d4..7ff115b 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -2243,6 +2243,14 @@
}
@Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ super.dispatchPointerCaptureChanged(hasCapture);
+ if (!mWindow.isDestroyed() && mWindow.getCallback() != null) {
+ mWindow.getCallback().onPointerCaptureChanged(hasCapture);
+ }
+ }
+
+ @Override
public String toString() {
return "DecorView@" + Integer.toHexString(this.hashCode()) + "["
+ getTitleSuffix(mWindow.getAttributes()) + "]";
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index cf14471..11e7102 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -53,6 +53,11 @@
*/
private static final int SNAP_ONLY_1_1 = 2;
+ /**
+ * 1 snap target: minimized height, (1 - minimized height)
+ */
+ private static final int SNAP_MODE_MINIMIZED = 3;
+
private final float mMinFlingVelocityPxPerSecond;
private final float mMinDismissVelocityPxPerSecond;
private final int mDisplayWidth;
@@ -62,6 +67,7 @@
private final Rect mInsets = new Rect();
private final int mSnapMode;
private final int mMinimalSizeResizableTask;
+ private final int mTaskHeightInMinimizedMode;
private final float mFixedRatio;
private boolean mIsHorizontalDivision;
@@ -93,6 +99,11 @@
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
boolean isHorizontalDivision, Rect insets) {
+ this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, false);
+ }
+
+ public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+ boolean isHorizontalDivision, Rect insets, boolean isMinimizedMode) {
mMinFlingVelocityPxPerSecond =
MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
mMinDismissVelocityPxPerSecond =
@@ -102,12 +113,14 @@
mDisplayHeight = displayHeight;
mIsHorizontalDivision = isHorizontalDivision;
mInsets.set(insets);
- mSnapMode = res.getInteger(
- com.android.internal.R.integer.config_dockedStackDividerSnapMode);
+ mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
+ res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
mFixedRatio = res.getFraction(
com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
mMinimalSizeResizableTask = res.getDimensionPixelSize(
com.android.internal.R.dimen.default_minimal_size_resizable_task);
+ mTaskHeightInMinimizedMode = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.task_height_of_minimized_mode);
calculateTargets(isHorizontalDivision);
mFirstSplitTarget = mTargets.get(1);
mLastSplitTarget = mTargets.get(mTargets.size() - 2);
@@ -246,6 +259,7 @@
int dividerMax = isHorizontalDivision
? mDisplayHeight
: mDisplayWidth;
+ int navBarSize = isHorizontalDivision ? mInsets.bottom : mInsets.right;
mTargets.add(new SnapTarget(-mDividerSize, -mDividerSize, SnapTarget.FLAG_DISMISS_START,
0.35f));
switch (mSnapMode) {
@@ -258,8 +272,10 @@
case SNAP_ONLY_1_1:
addMiddleTarget(isHorizontalDivision);
break;
+ case SNAP_MODE_MINIMIZED:
+ addMinimizedTarget(isHorizontalDivision);
+ break;
}
- int navBarSize = isHorizontalDivision ? mInsets.bottom : mInsets.right;
mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
SnapTarget.FLAG_DISMISS_END, 0.35f));
}
@@ -315,6 +331,12 @@
mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
}
+ private void addMinimizedTarget(boolean isHorizontalDivision) {
+ int position = mTaskHeightInMinimizedMode;
+ position += isHorizontalDivision ? mInsets.top : mInsets.left;
+ mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ }
+
public SnapTarget getMiddleTarget() {
return mMiddleTarget;
}
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/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
index 4dd3360..ec92aa9 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -39,20 +39,27 @@
private static final int SNAP_MODE_CORNERS_AND_SIDES = 1;
// Allows snapping to anywhere along the edge of the screen
private static final int SNAP_MODE_EDGE = 2;
+ // Allows snapping to four corners on a fling towards a corner or slow move near a corner
+ // snaps anywhere along the edge of screen otherwise
+ private static final int SNAP_MODE_CORNERS_AND_EDGES = 3;
// The friction multiplier to control how slippery the PIP is when flung
private static final float SCROLL_FRICTION_MULTIPLIER = 8f;
+ // Threshold to magnet to a corner
+ private static final float CORNER_MAGNET_THRESHOLD = 0.3f;
+
private final Context mContext;
private final ArrayList<Integer> mSnapGravities = new ArrayList<>();
- private final int mDefaultSnapMode = SNAP_MODE_CORNERS_ONLY;
+ private final int mDefaultSnapMode = SNAP_MODE_CORNERS_AND_EDGES;
private int mSnapMode = mDefaultSnapMode;
private Scroller mScroller;
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
private final int mMinimizedVisibleSize;
+ private boolean mIsMinimized;
public PipSnapAlgorithm(Context context) {
mContext = context;
@@ -70,6 +77,13 @@
}
/**
+ * Sets the PIP's minimized state.
+ */
+ public void setMinimized(boolean isMinimized) {
+ mIsMinimized = isMinimized;
+ }
+
+ /**
* Enables snapping to the closest edge.
*/
public void setSnapToEdge(boolean snapToEdge) {
@@ -107,7 +121,24 @@
movementBounds.right + stackBounds.width(),
movementBounds.bottom + stackBounds.height());
final Rect newBounds = new Rect(stackBounds);
- if (mSnapMode == SNAP_MODE_EDGE) {
+ if (mSnapMode == SNAP_MODE_CORNERS_AND_EDGES) {
+ final Rect tmpBounds = new Rect();
+ final Point[] snapTargets = new Point[mSnapGravities.size()];
+ for (int i = 0; i < mSnapGravities.size(); i++) {
+ Gravity.apply(mSnapGravities.get(i), stackBounds.width(), stackBounds.height(),
+ pipBounds, 0, 0, tmpBounds);
+ snapTargets[i] = new Point(tmpBounds.left, tmpBounds.top);
+ }
+ Point snapTarget = findClosestPoint(stackBounds.left, stackBounds.top, snapTargets);
+ float distance = distanceToPoint(snapTarget, stackBounds.left, stackBounds.top);
+ final float thresh = stackBounds.width() * CORNER_MAGNET_THRESHOLD;
+ if (distance < thresh) {
+ newBounds.offsetTo(snapTarget.x, snapTarget.y);
+ } else {
+ // Otherwise we snap to the edge
+ snapRectToClosestEdge(stackBounds, movementBounds, newBounds);
+ }
+ } else if (mSnapMode == SNAP_MODE_EDGE) {
// Find the closest edge to the given stack bounds and snap to it
snapRectToClosestEdge(stackBounds, movementBounds, newBounds);
} else {
@@ -128,11 +159,14 @@
/**
* Applies the offset to the {@param stackBounds} to adjust it to a minimized state.
*/
- public void applyMinimizedOffset(Rect stackBounds, Rect movementBounds, Point displaySize) {
+ public void applyMinimizedOffset(Rect stackBounds, Rect movementBounds, Point displaySize,
+ Rect stableInsets) {
if (stackBounds.left <= movementBounds.centerX()) {
- stackBounds.offsetTo(-stackBounds.width() + mMinimizedVisibleSize, stackBounds.top);
+ stackBounds.offsetTo(stableInsets.left + mMinimizedVisibleSize - stackBounds.width(),
+ stackBounds.top);
} else {
- stackBounds.offsetTo(displaySize.x - mMinimizedVisibleSize, stackBounds.top);
+ stackBounds.offsetTo(displaySize.x - stableInsets.right - mMinimizedVisibleSize,
+ stackBounds.top);
}
}
@@ -225,8 +259,7 @@
final int boundedTop = Math.max(movementBounds.top, Math.min(movementBounds.bottom,
stackBounds.top));
boundsOut.set(stackBounds);
- if (stackBounds.left < movementBounds.left ||
- stackBounds.left > movementBounds.right) {
+ if (mIsMinimized) {
boundsOut.offsetTo(boundedLeft, boundsOut.top);
return;
}
@@ -270,6 +303,7 @@
}
// Fall through
case SNAP_MODE_CORNERS_ONLY:
+ case SNAP_MODE_CORNERS_AND_EDGES:
mSnapGravities.add(Gravity.TOP | Gravity.LEFT);
mSnapGravities.add(Gravity.TOP | Gravity.RIGHT);
mSnapGravities.add(Gravity.BOTTOM | Gravity.LEFT);
diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java
index 087383d..0fe580a 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/NotificationColorUtil.java
@@ -33,6 +33,8 @@
import android.graphics.drawable.VectorDrawable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import android.util.Pair;
@@ -184,8 +186,24 @@
SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
for (Object span : spans) {
Object resultSpan = span;
- if (span instanceof TextAppearanceSpan) {
- resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span);
+ if (resultSpan instanceof CharacterStyle) {
+ resultSpan = ((CharacterStyle) span).getUnderlying();
+ }
+ if (resultSpan instanceof TextAppearanceSpan) {
+ TextAppearanceSpan processedSpan = processTextAppearanceSpan(
+ (TextAppearanceSpan) span);
+ if (processedSpan != resultSpan) {
+ resultSpan = processedSpan;
+ } else {
+ // we need to still take the orgininal for wrapped spans
+ resultSpan = span;
+ }
+ } else if (resultSpan instanceof ForegroundColorSpan) {
+ ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
+ int foregroundColor = originalSpan.getForegroundColor();
+ resultSpan = new ForegroundColorSpan(processColor(foregroundColor));
+ } else {
+ resultSpan = span;
}
builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
ss.getSpanFlags(span));
@@ -416,6 +434,48 @@
return color;
}
+ public static int resolvePrimaryColor(Context context, int backgroundColor) {
+ boolean useDark = shouldUseDark(backgroundColor);
+ if (useDark) {
+ return context.getColor(
+ com.android.internal.R.color.notification_primary_text_color_light);
+ } else {
+ return context.getColor(
+ com.android.internal.R.color.notification_primary_text_color_dark);
+ }
+ }
+
+ public static int resolveSecondaryColor(Context context, int backgroundColor) {
+ boolean useDark = shouldUseDark(backgroundColor);
+ if (useDark) {
+ return context.getColor(
+ com.android.internal.R.color.notification_secondary_text_color_light);
+ } else {
+ return context.getColor(
+ com.android.internal.R.color.notification_secondary_text_color_dark);
+ }
+ }
+
+ public static int resolveActionBarColor(int backgroundColor) {
+ boolean useDark = shouldUseDark(backgroundColor);
+ final double[] result = ColorUtilsFromCompat.getTempDouble3Array();
+ ColorUtilsFromCompat.colorToLAB(backgroundColor, result);
+ if (useDark && result[0] < 97 || !useDark && result[0] < 4) {
+ result[0] = Math.min(100, result[0] + 7);
+ } else {
+ result[0] = Math.max(0, result[0] - 7);
+ }
+ return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]);
+ }
+
+ private static boolean shouldUseDark(int backgroundColor) {
+ boolean useDark = backgroundColor == Notification.COLOR_DEFAULT;
+ if (!useDark) {
+ useDark = ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5;
+ }
+ return useDark;
+ }
+
/**
* Framework copy of functions needed from android.support.v4.graphics.ColorUtils.
*/
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index c9c8292..dd91d2f 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -122,4 +122,8 @@
@Override
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
}
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index c8697dd..1fce0b7 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -48,7 +48,7 @@
private MenuItem.OnMenuItemClickListener mClickListener;
private CharSequence mContentDescription;
- private CharSequence mTooltip;
+ private CharSequence mTooltipText;
private static final int NO_ICON = 0;
@@ -290,13 +290,13 @@
}
@Override
- public MenuItem setTooltip(CharSequence tooltip) {
- mTooltip = tooltip;
+ public MenuItem setTooltipText(CharSequence tooltipText) {
+ mTooltipText = tooltipText;
return this;
}
@Override
- public CharSequence getTooltip() {
- return mTooltip;
+ public CharSequence getTooltipText() {
+ return mTooltipText;
}
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 4ee5993..92e1d80 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -193,12 +193,12 @@
setContentDescription(contentDescription);
}
- final CharSequence tooltip = mItemData.getTooltip();
- if (TextUtils.isEmpty(tooltip)) {
+ final CharSequence tooltipText = mItemData.getTooltipText();
+ if (TextUtils.isEmpty(tooltipText)) {
// Use the uncondensed title for tooltip, but only if the title is not shown already.
- setTooltip(visible ? null : mItemData.getTitle());
+ setTooltipText(visible ? null : mItemData.getTitle());
} else {
- setTooltip(tooltip);
+ setTooltipText(tooltipText);
}
}
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index f9ebdbc..6c8f330 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -113,7 +113,7 @@
} else {
setContentDescription(contentDescription);
}
- setTooltip(mItemData.getTooltip());
+ setTooltipText(mItemData.getTooltipText());
}
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 7c9f709..43005e6 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -115,7 +115,7 @@
setEnabled(itemData.isEnabled());
setSubMenuArrowVisible(itemData.hasSubMenu());
setContentDescription(itemData.getContentDescription());
- setTooltip(itemData.getTooltip());
+ setTooltipText(itemData.getTooltipText());
}
public void setForceShowIcon(boolean forceShow) {
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index cad0276..342943f 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -94,7 +94,7 @@
private ContextMenuInfo mMenuInfo;
private CharSequence mContentDescription;
- private CharSequence mTooltip;
+ private CharSequence mTooltipText;
private static String sLanguage;
private static String sPrependShortcutLabel;
@@ -689,8 +689,8 @@
}
@Override
- public MenuItem setTooltip(CharSequence tooltip) {
- mTooltip = tooltip;
+ public MenuItem setTooltipText(CharSequence tooltipText) {
+ mTooltipText = tooltipText;
mMenu.onItemsChanged(false);
@@ -698,7 +698,7 @@
}
@Override
- public CharSequence getTooltip() {
- return mTooltip;
+ public CharSequence getTooltipText() {
+ return mTooltipText;
}
}
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/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 04e09a8..58e694a 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -1591,7 +1591,7 @@
} else {
menuButton.setContentDescription(contentDescription);
}
- menuButton.setTooltip(menuItem.getTooltip());
+ menuButton.setTooltipText(menuItem.getTooltipText());
menuButton.setMinimumWidth(minimumWidth);
}
return menuButton;
@@ -1642,17 +1642,17 @@
((ImageButton) menuItemButton
.findViewById(R.id.floating_toolbar_menu_item_image_button))
.setImageDrawable(menuItem.getIcon());
- final CharSequence tooltip = menuItem.getTooltip();
- if (TextUtils.isEmpty(tooltip)) {
- menuItemButton.setTooltip(menuItem.getTitle());
+ final CharSequence tooltipText = menuItem.getTooltipText();
+ if (TextUtils.isEmpty(tooltipText)) {
+ menuItemButton.setTooltipText(menuItem.getTitle());
} else {
- menuItemButton.setTooltip(tooltip);
+ menuItemButton.setTooltipText(tooltipText);
}
} else {
menuItemButton = LayoutInflater.from(context)
.inflate(R.layout.floating_popup_menu_button, null);
((Button) menuItemButton).setText(menuItem.getTitle());
- menuItemButton.setTooltip(menuItem.getTooltip());
+ menuItemButton.setTooltipText(menuItem.getTooltipText());
}
final CharSequence contentDescription = menuItem.getContentDescription();
if (TextUtils.isEmpty(contentDescription)) {
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 a43f3a7..58fb145 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -43,6 +43,7 @@
import android.util.Log;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.google.android.collect.Lists;
import libcore.util.HexEncoding;
@@ -239,7 +240,8 @@
mHandler = looper != null ? new Handler(looper) : null;
}
- private ILockSettings getLockSettings() {
+ @VisibleForTesting
+ public ILockSettings getLockSettings() {
if (mLockSettingsService == null) {
ILockSettings service = ILockSettings.Stub.asInterface(
ServiceManager.getService("lock_settings"));
@@ -298,6 +300,10 @@
getTrustManager().reportUnlockAttempt(true /* authenticated */, userId);
}
+ public void reportPasswordLockout(int timeoutMs, int userId) {
+ getTrustManager().reportUnlockLockout(timeoutMs, userId);
+ }
+
public int getCurrentFailedPasswordAttempts(int userId) {
return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
}
@@ -1310,7 +1316,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/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index 4466575..311bfac 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -471,7 +471,7 @@
if (mIconView != null) {
mIconView.setContentDescription(tab.getContentDescription());
}
- setTooltip(hasText? null : tab.getContentDescription());
+ setTooltipText(hasText? null : tab.getContentDescription());
}
}
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/java/com/android/server/NetworkManagementSocketTagger.java b/core/java/com/android/server/NetworkManagementSocketTagger.java
index 06ef4c9..03f2bc1 100644
--- a/core/java/com/android/server/NetworkManagementSocketTagger.java
+++ b/core/java/com/android/server/NetworkManagementSocketTagger.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.os.StrictMode;
import android.os.SystemProperties;
import android.util.Log;
import android.util.Slog;
@@ -50,16 +51,20 @@
SocketTagger.set(new NetworkManagementSocketTagger());
}
- public static void setThreadSocketStatsTag(int tag) {
+ public static int setThreadSocketStatsTag(int tag) {
+ final int old = threadSocketTags.get().statsTag;
threadSocketTags.get().statsTag = tag;
+ return old;
}
public static int getThreadSocketStatsTag() {
return threadSocketTags.get().statsTag;
}
- public static void setThreadSocketStatsUid(int uid) {
+ public static int setThreadSocketStatsUid(int uid) {
+ final int old = threadSocketTags.get().statsUid;
threadSocketTags.get().statsUid = uid;
+ return old;
}
@Override
@@ -69,6 +74,9 @@
Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
+ Integer.toHexString(options.statsTag) + ", statsUid=" + options.statsUid);
}
+ if (options.statsTag == -1 && StrictMode.vmUntaggedSocketEnabled()) {
+ StrictMode.onUntaggedSocket();
+ }
// TODO: skip tagging when options would be no-op
tagSocketFd(fd, options.statsTag, options.statsUid);
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 168da5f..fcb4c7b 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -109,6 +109,10 @@
// background while in data-usage save mode, as read from the configuration files.
final ArraySet<String> mAllowInDataUsageSave = new ArraySet<>();
+ // These are the action strings of broadcasts which are whitelisted to
+ // be delivered anonymously even to apps which target O+.
+ final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
+
// These are the package names of apps which should be in the 'always'
// URL-handling state upon factory reset.
final ArraySet<String> mLinkedApps = new ArraySet<>();
@@ -162,6 +166,10 @@
return mPermissions;
}
+ public ArraySet<String> getAllowImplicitBroadcasts() {
+ return mAllowImplicitBroadcasts;
+ }
+
public ArraySet<String> getAllowInPowerSaveExceptIdle() {
return mAllowInPowerSaveExceptIdle;
}
@@ -438,6 +446,17 @@
XmlUtils.skipCurrentTag(parser);
continue;
+ } else if ("allow-implicit-broadcast".equals(name) && allowAll) {
+ String action = parser.getAttributeValue(null, "action");
+ if (action == null) {
+ Slog.w(TAG, "<allow-implicit-broadcast> without action in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mAllowImplicitBroadcasts.add(action);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+
} else if ("app-link".equals(name) && allowAppConfigs) {
String pkgname = parser.getAttributeValue(null, "package");
if (pkgname == null) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 6a9ed8e..327f142 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -118,7 +118,6 @@
android/graphics/ColorFilter.cpp \
android/graphics/DrawFilter.cpp \
android/graphics/FontFamily.cpp \
- android/graphics/FontUtils.cpp \
android/graphics/CreateJavaOutputStreamAdaptor.cpp \
android/graphics/GIFMovie.cpp \
android/graphics/GraphicBuffer.cpp \
@@ -159,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 685c93d..0c863fd 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -30,7 +30,6 @@
#include <android_runtime/android_util_AssetManager.h>
#include <androidfw/AssetManager.h>
#include "Utils.h"
-#include "FontUtils.h"
#include <hwui/MinikinSkia.h>
#include <hwui/Typeface.h>
@@ -69,6 +68,12 @@
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();
@@ -144,6 +149,16 @@
return true;
}
+static struct {
+ jmethodID mGet;
+ jmethodID mSize;
+} gListClassInfo;
+
+static struct {
+ jfieldID mTag;
+ jfieldID mStyleValue;
+} gAxisClassInfo;
+
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);
@@ -152,22 +167,20 @@
std::unique_ptr<SkFontMgr::FontParameters::Axis[]> skiaAxes;
int skiaAxesLength = 0;
if (listOfAxis) {
- ListHelper list(env, listOfAxis);
- jint listSize = list.size();
+ jint listSize = env->CallIntMethod(listOfAxis, gListClassInfo.mSize);
skiaAxes.reset(new SkFontMgr::FontParameters::Axis[listSize]);
skiaAxesLength = listSize;
for (jint i = 0; i < listSize; ++i) {
- jobject axisObject = list.get(i);
+ jobject axisObject = env->CallObjectMethod(listOfAxis, gListClassInfo.mGet, i);
if (!axisObject) {
skiaAxes[i].fTag = 0;
skiaAxes[i].fStyleValue = 0;
continue;
}
- AxisHelper axis(env, axisObject);
- jint tag = axis.getTag();
- jfloat stylevalue = axis.getStyleValue();
+ jint tag = env->GetIntField(axisObject, gAxisClassInfo.mTag);
+ jfloat stylevalue = env->GetFloatField(axisObject, gAxisClassInfo.mStyleValue);
skiaAxes[i].fTag = tag;
skiaAxes[i].fStyleValue = SkFloatToScalar(stylevalue);
}
@@ -211,8 +224,8 @@
delete static_cast<Asset*>(context);
}
-static jboolean FontFamily_addFontFromAsset(JNIEnv* env, jobject, jlong builderPtr,
- jobject jassetMgr, jstring jpath) {
+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);
@@ -222,7 +235,18 @@
}
ScopedUtfChars str(env, jpath);
- Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
+ if (str.c_str() == nullptr) {
+ return false;
+ }
+
+ Asset* asset;
+ if (isAsset) {
+ asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
+ } else {
+ asset = cookie ? mgr->openNonAsset(static_cast<int32_t>(cookie), str.c_str(),
+ Asset::ACCESS_BUFFER) : mgr->openNonAsset(str.c_str(), Asset::ACCESS_BUFFER);
+ }
+
if (NULL == asset) {
return false;
}
@@ -253,12 +277,13 @@
static const JNINativeMethod gFontFamilyMethods[] = {
{ "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",
(void*)FontFamily_addFontWeightStyle },
- { "nAddFontFromAsset", "(JLandroid/content/res/AssetManager;Ljava/lang/String;)Z",
- (void*)FontFamily_addFontFromAsset },
+ { "nAddFontFromAssetManager", "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZ)Z",
+ (void*)FontFamily_addFontFromAssetManager },
};
int register_android_graphics_FontFamily(JNIEnv* env)
@@ -266,7 +291,14 @@
int err = RegisterMethodsOrDie(env, "android/graphics/FontFamily", gFontFamilyMethods,
NELEM(gFontFamilyMethods));
- init_FontUtils(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");
+
return err;
}
diff --git a/core/jni/android/graphics/FontUtils.cpp b/core/jni/android/graphics/FontUtils.cpp
index 11c2d29..91fec2a 100644
--- a/core/jni/android/graphics/FontUtils.cpp
+++ b/core/jni/android/graphics/FontUtils.cpp
@@ -55,9 +55,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");
}
} // namespace android
diff --git a/core/jni/android/graphics/FontUtils.h b/core/jni/android/graphics/FontUtils.h
deleted file mode 100644
index 6fbd5e3..0000000
--- a/core/jni/android/graphics/FontUtils.h
+++ /dev/null
@@ -1,54 +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.
- */
-
-#ifndef _ANDROID_GRAPHICS_FONT_UTILS_H_
-#define _ANDROID_GRAPHICS_FONT_UTILS_H_
-
-#include <jni.h>
-
-namespace android {
-
-// Utility wrapper for java.util.List
-class ListHelper {
-public:
- ListHelper(JNIEnv* env, jobject list) : mEnv(env), mList(list) {}
-
- jint size() const;
- jobject get(jint index) const;
-
-private:
- JNIEnv* mEnv;
- jobject mList;
-};
-
-// Utility wrapper for android.graphics.FontListParser$Axis
-class AxisHelper {
-public:
- AxisHelper(JNIEnv* env, jobject axis) : mEnv(env), mAxis(axis) {}
-
- jint getTag() const;
- jfloat getStyleValue() const;
-
-private:
- JNIEnv* mEnv;
- jobject mAxis;
-};
-
-void init_FontUtils(JNIEnv* env);
-
-}; // namespace android
-
-#endif // _ANDROID_GRAPHICS_FONT_UTILS_H_
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 0a0fce3e..c920b8d 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -17,14 +17,12 @@
#include "jni.h"
#include "core_jni_helpers.h"
-#include "FontUtils.h"
#include "GraphicsJNI.h"
#include "ScopedPrimitiveArray.h"
#include "SkTypeface.h"
#include <android_runtime/android_util_AssetManager.h>
#include <androidfw/AssetManager.h>
#include <hwui/Typeface.h>
-#include <minikin/FontFamily.h>
using namespace android;
@@ -42,23 +40,6 @@
return reinterpret_cast<jlong>(face);
}
-static jlong Typeface_createFromTypefaceWithVariation(JNIEnv* env, jobject, jlong familyHandle,
- jobject listOfAxis) {
- std::vector<minikin::FontVariation> variations;
- ListHelper list(env, listOfAxis);
- for (jint i = 0; i < list.size(); i++) {
- jobject axisObject = list.get(i);
- if (axisObject == nullptr) {
- continue;
- }
- AxisHelper axis(env, axisObject);
- variations.push_back(minikin::FontVariation(axis.getTag(), axis.getStyleValue()));
- }
- Typeface* baseTypeface = reinterpret_cast<Typeface*>(familyHandle);
- Typeface* result = Typeface::createFromTypefaceWithVariation(baseTypeface, variations);
- return reinterpret_cast<jlong>(result);
-}
-
static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) {
Typeface* family = reinterpret_cast<Typeface*>(familyHandle);
Typeface* face = Typeface::createWeightAlias(family, weight);
@@ -96,8 +77,6 @@
static const JNINativeMethod gTypefaceMethods[] = {
{ "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
- { "nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
- (void*)Typeface_createFromTypefaceWithVariation },
{ "nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias },
{ "nativeUnref", "(J)V", (void*)Typeface_unref },
{ "nativeGetStyle", "(J)I", (void*)Typeface_getStyle },
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..fadf8a4
--- /dev/null
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -0,0 +1,350 @@
+/*
+ * 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 HAL_PIXEL_FORMAT_RGBA_8888:
+ return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ return AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM;
+ case HAL_PIXEL_FORMAT_RGB_565:
+ return AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM;
+ case HAL_PIXEL_FORMAT_RGB_888:
+ return AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
+ case HAL_PIXEL_FORMAT_RGBA_FP16:
+ return AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT;
+ case HAL_PIXEL_FORMAT_BLOB:
+ return AHARDWAREBUFFER_FORMAT_BLOB;
+ 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 HAL_PIXEL_FORMAT_RGBA_8888;
+ case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
+ return HAL_PIXEL_FORMAT_RGBX_8888;
+ case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
+ return HAL_PIXEL_FORMAT_RGB_565;
+ case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
+ return HAL_PIXEL_FORMAT_RGB_888;
+ case AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT:
+ return HAL_PIXEL_FORMAT_RGBA_FP16;
+ case AHARDWAREBUFFER_FORMAT_BLOB:
+ return HAL_PIXEL_FORMAT_BLOB;
+ 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_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 793d132..0c7f5a1 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -509,7 +509,7 @@
sp<MemoryDealer> memoryDealer;
sp<IMemory> memory;
size_t size;
- sound_model_handle_t handle;
+ sound_model_handle_t handle = 0;
jobject jUuid;
jstring jUuidString;
const char *nUuidString;
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 86c4df7..b2c8168 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -972,6 +972,18 @@
}
// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_flags(JNIEnv *env, jobject thiz) {
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+
+ if (lpTrack == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Unable to retrieve AudioTrack pointer for getFlags()");
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ return (jint)lpTrack->getFlags();
+}
+
+// ----------------------------------------------------------------------------
static jint android_media_AudioTrack_get_timestamp(JNIEnv *env, jobject thiz, jlongArray jTimestamp) {
sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
@@ -1212,6 +1224,7 @@
{"native_get_position", "()I", (void *)android_media_AudioTrack_get_position},
{"native_get_latency", "()I", (void *)android_media_AudioTrack_get_latency},
{"native_get_underrun_count", "()I", (void *)android_media_AudioTrack_get_underrun_count},
+ {"native_get_flags", "()I", (void *)android_media_AudioTrack_get_flags},
{"native_get_timestamp", "([J)I", (void *)android_media_AudioTrack_get_timestamp},
{"native_set_loop", "(III)I", (void *)android_media_AudioTrack_set_loop},
{"native_reload_static", "()I", (void *)android_media_AudioTrack_reload},
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index d382f24..723dce6 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -681,7 +681,7 @@
jint smallestScreenWidthDp,
jint screenWidthDp, jint screenHeightDp,
jint screenLayout, jint uiMode,
- jint sdkVersion)
+ jint colorMode, jint sdkVersion)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
@@ -712,6 +712,7 @@
config.screenHeightDp = (uint16_t)screenHeightDp;
config.screenLayout = (uint8_t)screenLayout;
config.uiMode = (uint8_t)uiMode;
+ config.colorMode = (uint8_t)colorMode;
config.sdkVersion = (uint16_t)sdkVersion;
config.minorVersion = 0;
@@ -1691,7 +1692,7 @@
{ "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
(void*) android_content_AssetManager_getSizeConfigurations },
// @FastNative
- { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIII)V",
+ { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIIII)V",
(void*) android_content_AssetManager_setConfiguration },
// @FastNative
{ "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
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 d75d5c1..f221392 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -43,57 +43,6 @@
? (reinterpret_cast<RenderNode*>(renderNodePtr)->setPropertyFieldsDirty(dirtyFlag), true) \
: false)
-static JNIEnv* getenv(JavaVM* vm) {
- JNIEnv* env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
- }
- return env;
-}
-
-static jfieldID gRenderNode_validFieldID;
-
-class RenderNodeContext : public VirtualLightRefBase {
-public:
- RenderNodeContext(JNIEnv* env, jobject jobjRef) {
- env->GetJavaVM(&mVm);
- // This holds a weak ref because otherwise there's a cyclic global ref
- // with this holding a strong global ref to the view which holds
- // a strong ref to RenderNode which holds a strong ref to this.
- mWeakRef = env->NewWeakGlobalRef(jobjRef);
- }
-
- virtual ~RenderNodeContext() {
- JNIEnv* env = getenv(mVm);
- env->DeleteWeakGlobalRef(mWeakRef);
- }
-
- jobject acquireLocalRef(JNIEnv* env) {
- return env->NewLocalRef(mWeakRef);
- }
-
-private:
- JavaVM* mVm;
- jweak mWeakRef;
-};
-
-// Called by ThreadedRenderer's JNI layer
-void onRenderNodeRemoved(JNIEnv* env, RenderNode* node) {
- auto context = reinterpret_cast<RenderNodeContext*>(node->getUserContext());
- if (!context) return;
- jobject jnode = context->acquireLocalRef(env);
- if (!jnode) {
- // The owning node has been GC'd, release the context
- node->setUserContext(nullptr);
- return;
- }
-
- // Update the valid field, since native has already removed
- // the staging DisplayList
- env->SetBooleanField(jnode, gRenderNode_validFieldID, false);
- env->DeleteLocalRef(jnode);
-}
-
// ----------------------------------------------------------------------------
// DisplayList view properties
// ----------------------------------------------------------------------------
@@ -108,8 +57,7 @@
return renderNode->getDebugSize();
}
-static jlong android_view_RenderNode_create(JNIEnv* env, jobject thiz,
- jstring name) {
+static jlong android_view_RenderNode_create(JNIEnv* env, jobject, jstring name) {
RenderNode* renderNode = new RenderNode();
renderNode->incStrong(0);
if (name != NULL) {
@@ -117,7 +65,6 @@
renderNode->setName(textArray);
env->ReleaseStringUTFChars(name, textArray);
}
- renderNode->setUserContext(new RenderNodeContext(env, thiz));
return reinterpret_cast<jlong>(renderNode);
}
@@ -132,22 +79,13 @@
static void android_view_RenderNode_setDisplayList(JNIEnv* env,
jobject clazz, jlong renderNodePtr, jlong displayListPtr) {
- class RemovedObserver : public TreeObserver {
- public:
- virtual void onMaybeRemovedFromTree(RenderNode* node) override {
- maybeRemovedNodes.insert(sp<RenderNode>(node));
- }
- std::set< sp<RenderNode> > maybeRemovedNodes;
- };
-
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
DisplayList* newData = reinterpret_cast<DisplayList*>(displayListPtr);
- RemovedObserver observer;
- renderNode->setStagingDisplayList(newData, &observer);
- for (auto& node : observer.maybeRemovedNodes) {
- if (node->hasParents()) continue;
- onRenderNodeRemoved(env, node.get());
- }
+ renderNode->setStagingDisplayList(newData);
+}
+
+static jboolean android_view_RenderNode_isValid(jlong renderNodePtr) {
+ return reinterpret_cast<RenderNode*>(renderNodePtr)->isValid();
}
// ----------------------------------------------------------------------------
@@ -621,6 +559,7 @@
// ----------------------------------------------------------------------------
// Critical JNI via @CriticalNative annotation in RenderNode.java
// ----------------------------------------------------------------------------
+ { "nIsValid", "(J)Z", (void*) android_view_RenderNode_isValid },
{ "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType },
{ "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint },
{ "nSetStaticMatrix", "(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix },
@@ -691,8 +630,6 @@
"updateWindowPosition_renderWorker", "(JIIII)V");
gSurfaceViewPositionLostMethod = GetMethodIDOrDie(env, clazz,
"windowPositionLost_uiRtSync", "(J)V");
- clazz = FindClassOrDie(env, "android/view/RenderNode");
- gRenderNode_validFieldID = GetFieldIDOrDie(env, clazz, "mValid", "Z");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 96e6f81..bc5f847 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -532,7 +532,7 @@
UiFrameInfoBuilder(proxy->frameInfo())
.setVsync(vsync, vsync)
.addFlag(FrameInfoFlags::SurfaceCanvas);
- proxy->syncAndDrawFrame(nullptr);
+ proxy->syncAndDrawFrame();
}
static void destroy(JNIEnv* env, jclass clazz, jlong rendererPtr) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a3fef27..ab3e311 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -98,11 +98,12 @@
// ----------------------------------------------------------------------------
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
- jstring nameStr, jint w, jint h, jint format, jint flags) {
+ jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject) {
ScopedUtfChars name(env, nameStr);
sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
+ SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
sp<SurfaceControl> surface = client->createSurface(
- String8(name.c_str()), w, h, format, flags);
+ String8(name.c_str()), w, h, format, flags, parent);
if (surface == NULL) {
jniThrowException(env, OutOfResourcesException, NULL);
return 0;
@@ -147,8 +148,8 @@
}
Rect sourceCrop = rectFromObj(env, sourceCropObj);
if (allLayers) {
- minLayer = 0;
- maxLayer = -1;
+ minLayer = INT32_MIN;
+ maxLayer = INT32_MAX;
}
sp<GraphicBuffer> buffer;
status_t res = ScreenshotClient::captureToBuffer(displayToken,
@@ -181,8 +182,8 @@
std::unique_ptr<ScreenshotClient> screenshot(new ScreenshotClient());
status_t res;
if (allLayers) {
- minLayer = 0;
- maxLayer = -1;
+ minLayer = INT32_MIN;
+ maxLayer = INT32_MAX;
}
res = screenshot->update(displayToken, sourceCrop, width, height,
@@ -254,8 +255,8 @@
Rect sourceCrop(left, top, right, bottom);
if (allLayers) {
- minLayer = 0;
- maxLayer = -1;
+ minLayer = INT32_MIN;
+ maxLayer = INT32_MAX;
}
ScreenshotClient::capture(displayToken,
consumer->getIGraphicBufferProducer(), sourceCrop,
@@ -741,7 +742,7 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod sSurfaceControlMethods[] = {
- {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIII)J",
+ {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJ)J",
(void*)nativeCreate },
{"nativeRelease", "(J)V",
(void*)nativeRelease },
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index c00bcd4..d37f96a 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -71,31 +71,6 @@
return env;
}
-// TODO: Clean this up, it's a bit odd to need to call over to
-// rendernode's jni layer. Probably means RootRenderNode should be pulled
-// into HWUI with appropriate callbacks for the various JNI hooks so
-// that RenderNode's JNI layer can handle its own thing
-void onRenderNodeRemoved(JNIEnv* env, RenderNode* node);
-
-class ScopedRemovedRenderNodeObserver : public TreeObserver {
-public:
- explicit ScopedRemovedRenderNodeObserver(JNIEnv* env) : mEnv(env) {}
- ~ScopedRemovedRenderNodeObserver() {
- for (auto& node : mMaybeRemovedNodes) {
- if (node->hasParents()) continue;
- onRenderNodeRemoved(mEnv, node.get());
- }
- }
-
- virtual void onMaybeRemovedFromTree(RenderNode* node) override {
- mMaybeRemovedNodes.insert(sp<RenderNode>(node));
- }
-
-private:
- JNIEnv* mEnv;
- std::set< sp<RenderNode> > mMaybeRemovedNodes;
-};
-
class OnFinishedEvent {
public:
OnFinishedEvent(BaseRenderNodeAnimator* animator, AnimationListener* listener)
@@ -715,18 +690,16 @@
"Mismatched size expectations, given %d expected %d",
frameInfoSize, UI_THREAD_FRAME_INFO_SIZE);
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- ScopedRemovedRenderNodeObserver observer(env);
env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
- return proxy->syncAndDrawFrame(&observer);
+ return proxy->syncAndDrawFrame();
}
static void android_view_ThreadedRenderer_destroy(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong rootNodePtr) {
- ScopedRemovedRenderNodeObserver observer(env);
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr);
rootRenderNode->destroy();
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->destroy(&observer);
+ proxy->destroy();
}
static void android_view_ThreadedRenderer_registerAnimatingRenderNode(JNIEnv* env, jobject clazz,
@@ -758,10 +731,9 @@
static void android_view_ThreadedRenderer_buildLayer(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong nodePtr) {
- ScopedRemovedRenderNodeObserver observer(env);
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
RenderNode* node = reinterpret_cast<RenderNode*>(nodePtr);
- proxy->buildLayer(node, &observer);
+ proxy->buildLayer(node);
}
static jboolean android_view_ThreadedRenderer_copyLayerInto(JNIEnv* env, jobject clazz,
@@ -796,9 +768,8 @@
static void android_view_ThreadedRenderer_destroyHardwareResources(JNIEnv* env, jobject clazz,
jlong proxyPtr) {
- ScopedRemovedRenderNodeObserver observer(env);
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->destroyHardwareResources(&observer);
+ proxy->destroyHardwareResources();
}
static void android_view_ThreadedRenderer_trimMemory(JNIEnv* env, jobject clazz,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 070a2d9..5c65241 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -247,6 +247,11 @@
static void DropCapabilitiesBoundingSet(JNIEnv* env) {
for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
+ // Keep CAP_SYS_PTRACE in our bounding set so crash_dump can gain it.
+ if (i == CAP_SYS_PTRACE) {
+ continue;
+ }
+
int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
if (rc == -1) {
if (errno == EINVAL) {
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/proto/android/service/diskstats.proto b/core/proto/android/service/diskstats.proto
new file mode 100644
index 0000000..4d86526
--- /dev/null
+++ b/core/proto/android/service/diskstats.proto
@@ -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.
+ */
+
+syntax = "proto3";
+
+package android.service.diskstats;
+
+option java_multiple_files = true;
+option java_outer_classname = "DiskStatsServiceProto";
+
+message DiskStatsServiceDumpProto {
+ enum EncryptionType {
+ // Unknown encryption type
+ ENCRYPTION_UNKNOWN = 0;
+ // No encryption
+ ENCRYPTION_NONE = 1;
+ // Full disk encryption
+ ENCRYPTION_FULL_DISK = 2;
+ // File-based encryption
+ ENCRYPTION_FILE_BASED = 3;
+ }
+ // Whether the latency test resulted in an error
+ bool has_test_error = 1;
+ // If the test errored, error message is contained here
+ string error_message = 2;
+ // 512B write latency in milliseconds, if the test was successful
+ int32 write_512b_latency_millis = 3;
+ // Free Space in the major partitions
+ repeated DiskStatsFreeSpaceProto partitions_free_space = 4;
+ // Is the device using file-based encryption, full disk encryption or other
+ EncryptionType encryption = 5;
+ // Cached values of folder sizes, etc.
+ DiskStatsCachedValuesProto cached_folder_sizes = 6;
+}
+
+message DiskStatsCachedValuesProto {
+ // Total app data size, in kilobytes
+ int64 agg_apps_size = 1;
+ // Total app cache size, in kilobytes
+ int64 agg_apps_cache_size = 2;
+ // Size of image files, in kilobytes
+ int64 photos_size = 3;
+ // Size of video files, in kilobytes
+ int64 videos_size = 4;
+ // Size of audio files, in kilobytes
+ int64 audio_size = 5;
+ // Size of downloads, in kilobytes
+ int64 downloads_size = 6;
+ // Size of system directory, in kilobytes
+ int64 system_size = 7;
+ // Size of other files, in kilobytes
+ int64 other_size = 8;
+ // Sizes of individual packages
+ repeated DiskStatsAppSizesProto app_sizes = 9;
+}
+
+message DiskStatsAppSizesProto {
+ // Name of the package
+ string package_name = 1;
+ // App's data size in kilobytes
+ int64 app_size = 2;
+ // App's cache size in kilobytes
+ int64 cache_size = 3;
+}
+
+message DiskStatsFreeSpaceProto {
+ enum Folder {
+ // Data folder
+ FOLDER_DATA = 0;
+ // Cache folder
+ FOLDER_CACHE = 1;
+ // System folder
+ FOLDER_SYSTEM = 2;
+ }
+ // Which folder?
+ Folder folder = 1;
+ // Available space, in kilobytes
+ int64 available_space = 2;
+ // Total space, in kilobytes
+ int64 total_space = 3;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 84b03d2..2b6c0ba 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 -->
@@ -801,6 +803,16 @@
android:description="@string/permdesc_callPhone"
android:protectionLevel="dangerous" />
+ <!-- Allows an application to manage its own calls, but rely on the system to route focus to the
+ currently active call.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.MANAGE_OWN_CALLS"
+ android:permissionGroup="android.permission-group.PHONE"
+ android:label="@string/permlab_manageOwnCalls"
+ android:description="@string/permdesc_manageOwnCalls"
+ android:protectionLevel="dangerous" />
+
<!-- Allows an application to access the IMS call service: making and
modifying a call
<p>Protection level: signature|privileged
@@ -1259,7 +1271,7 @@
recommendations and scores from the NetworkScoreService.
<p>Not for use by third-party applications. @hide -->
<permission android:name="android.permission.REQUEST_NETWORK_SCORES"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|setup" />
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
@@ -1597,6 +1609,16 @@
<permission android:name="android.permission.RECEIVE_STK_COMMANDS"
android:protectionLevel="signature|privileged" />
+ <!-- Must be required by an ImsService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature|privileged
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_IMS_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
@@ -1953,7 +1975,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
@@ -2470,13 +2492,23 @@
android:protectionLevel="signature" />
<!-- Allows an application to request installing packages. Apps
- targeting APIs greater than 22 must hold this permission in
+ targeting APIs greater than 25 must hold this permission in
order to use {@link android.content.Intent#ACTION_INSTALL_PACKAGE}.
- <p>Protection level: normal
+ <p>Protection level: signature
-->
<permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
android:label="@string/permlab_requestInstallPackages"
android:description="@string/permdesc_requestInstallPackages"
+ android:protectionLevel="signature|appop" />
+
+ <!-- Allows an application to request deleting packages. Apps
+ targeting APIs greater than 25 must hold this permission in
+ order to use {@link android.content.Intent#ACTION_UNINSTALL_PACKAGE}.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REQUEST_DELETE_PACKAGES"
+ android:label="@string/permlab_requestDeletePackages"
+ android:description="@string/permdesc_requestDeletePackages"
android:protectionLevel="normal" />
<!-- @SystemApi Allows an application to install packages.
@@ -2625,6 +2657,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
@@ -3117,7 +3164,7 @@
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
- <!-- Allows the holder to access the ephemeral applications on the device.
+ <!-- Allows the holder to access the instant applications on the device.
@hide -->
<permission android:name="android.permission.ACCESS_EPHEMERAL_APPS"
android:protectionLevel="signature" />
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/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index 516f252..182ba24 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2016 The Android Open Source Project
+Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,24 +14,27 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="512dp"
- android:height="512dp"
+ android:width="480dp"
+ android:height="480dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:fillColor="#FFc7d4b6"
- android:pathData="M32.0,12.5l0.0,28.0l12.0,-5.0l0.0,-28.0z"/>
+ android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
+ android:fillAlpha="0.066"
+ android:fillColor="#000000"/>
<path
- android:fillColor="#FFfbd3cb"
- android:pathData="M4.0,40.5l12.0,-5.0l0.0,-11.0l-12.0,-12.0z"/>
+ android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
+ android:fillColor="#FFC107"/>
<path
- android:fillColor="#40000000"
- android:pathData="M44.0,35.5l-12.0,-12.0l0.0,-4.0z"/>
+ android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
+ android:fillColor="#FE9F00"/>
<path
- android:fillColor="#40000000"
- android:pathData="M4.0,12.5l12.0,12.0l0.0,4.0z"/>
+ android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
+ android:fillColor="#FED44F"/>
<path
- android:fillColor="#FFe0e0d6"
- android:pathData="M32.0,23.5l-16.0,-16.0l-12.0,5.0l0.0,0.0l12.0,12.0l16.0,16.0l12.0,-5.0l0.0,0.0z"/>
+ android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
+ android:fillColor="#FFC107"/>
+ <path
+ android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
+ android:fillColor="#FFFFFF"/>
</vector>
-
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 5043cba..89e42e6 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2016 The Android Open Source Project
+Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,21 +16,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
<path
- android:fillColor="#A0FFFFFF"
- android:pathData="M32.0,12.5l0.0,28.0l12.0,-5.0l0.0,-28.0z"/>
+ android:fillColor="#FF000000"
+ android:pathData="M12.0,12.0m-10.0,0.0a10.0,10.0,0,1,1,20.0,0.0a10.0,10.0,0,1,1,-20.0,0.0"
+ android:fillAlpha="0.25"/>
<path
- android:fillColor="#A0FFFFFF"
- android:pathData="M4.0,40.5l12.0,-5.0l0.0,-11.0l-12.0,-12.0z"/>
+ android:fillColor="#FF000000"
+ android:pathData="M12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 Z M12,18.5 C15.5898509,18.5 18.5,15.5898509 18.5,12 C18.5,8.41014913 15.5898509,5.5 12,5.5 C8.41014913,5.5 5.5,8.41014913 5.5,12 C5.5,15.5898509 8.41014913,18.5 12,18.5 Z"/>
<path
- android:fillColor="#40000000"
- android:pathData="M44.0,35.5l-12.0,-12.0l0.0,-4.0z"/>
- <path
- android:fillColor="#40000000"
- android:pathData="M4.0,12.5l12.0,12.0l0.0,4.0z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M32.0,23.5l-16.0,-16.0l-12.0,5.0l0.0,0.0l12.0,12.0l16.0,16.0l12.0,-5.0l0.0,0.0z"/>
+ android:fillColor="#FF000000"
+ android:pathData="M12,18.5 C8.41014913,18.5 5.5,15.5898509 5.5,12 C5.5,8.41014913 8.41014913,5.5 12,5.5 C15.5898509,5.5 18.5,8.41014913 18.5,12 C18.5,15.5898509 15.5898509,18.5 12,18.5 Z M12,15 C13.6568542,15 15,13.6568542 15,12 C15,10.3431458 13.6568542,9 12,9 C10.3431458,9 9,10.3431458 9,12 C9,13.6568542 10.3431458,15 12,15 Z"
+ android:fillAlpha="0.25"/>
</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
index bd1eb41..606b686 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
index aedb12c..a4c5bc2 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
index 6f07cb5..9c27833 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
index c41a8ca..6d693f1 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
index ec0a52f..c48fa36 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4k.xml b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
index 78bd0a0..0868845 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_hd.xml b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
index 78085c2f..657f5ed 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_ld.xml b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
index f660ab7..e2971aa 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_sd.xml b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
index 43b8653..b073be3 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_wifi_signal_0.xml b/core/res/res/drawable/ic_wifi_signal_0.xml
new file mode 100644
index 0000000..e732a8d
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_0.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_wifi_signal_1.xml b/core/res/res/drawable/ic_wifi_signal_1.xml
new file mode 100644
index 0000000..3d006953
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_1.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.1,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.5,6.5L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0z"/>
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.1,22.0l5.5,-6.8c-0.2,-0.2 -2.3,-1.9 -5.5,-1.9s-5.3,1.8 -5.5,1.9L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_wifi_signal_2.xml b/core/res/res/drawable/ic_wifi_signal_2.xml
new file mode 100644
index 0000000..2cce9e9
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_2.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0l7.6,-9.4C20.3,12.4 17.4,10.0 13.0,10.0s-7.3,2.4 -7.6,2.7L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_wifi_signal_3.xml b/core/res/res/drawable/ic_wifi_signal_3.xml
new file mode 100644
index 0000000..d3b3d3a
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_3.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0l9.2,-11.4c-0.4,-0.3 -3.9,-3.2 -9.2,-3.2s-8.9,3.0 -9.2,3.2L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_wifi_signal_4.xml b/core/res/res/drawable/ic_wifi_signal_4.xml
new file mode 100644
index 0000000..aca4551
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_4.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 1f71a18..0dfeb62 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -20,10 +20,11 @@
android:id="@+id/notification_header"
android:orientation="horizontal"
android:layout_width="wrap_content"
- android:layout_height="53dp"
+ android:layout_height="48dp"
android:clipChildren="false"
android:paddingTop="10dp"
- android:paddingBottom="16dp"
+ android:paddingBottom="11dp"
+ android:layout_marginBottom="5dp"
android:paddingStart="@dimen/notification_content_margin_start"
android:paddingEnd="16dp">
<com.android.internal.widget.CachingIconView
diff --git a/core/res/res/layout/preference_widget_seekbar.xml b/core/res/res/layout/preference_widget_seekbar.xml
index 05daa1a..0c6dbf1 100644
--- a/core/res/res/layout/preference_widget_seekbar.xml
+++ b/core/res/res/layout/preference_widget_seekbar.xml
@@ -50,7 +50,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
diff --git a/core/res/res/layout/preference_widget_seekbar_material.xml b/core/res/res/layout/preference_widget_seekbar_material.xml
index f70a472..6c72c9e 100644
--- a/core/res/res/layout/preference_widget_seekbar_material.xml
+++ b/core/res/res/layout/preference_widget_seekbar_material.xml
@@ -51,7 +51,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
@@ -60,7 +60,7 @@
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_alignStart="@android:id/title"
- android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="4" />
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 a5c1a94..11a96f7 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
@@ -2497,13 +2500,13 @@
<!-- Sets the id of a view before which this one is visited in accessibility traversal.
A screen-reader must visit the content of this view before the content of the one
it precedes.
- @see android.view.View#setAccessibilityTraversalBefore(int)} -->
+ {@see android.view.View#setAccessibilityTraversalBefore(int)} -->
<attr name="accessibilityTraversalBefore" format="integer" />
<!-- Sets the id of a view after which this one is visited in accessibility traversal.
A screen-reader must visit the content of the other view before the content of
this one.
- @see android.view.View#setAccessibilityTraversalAfter(int)} -->
+ {@see android.view.View#setAccessibilityTraversalAfter(int)} -->
<attr name="accessibilityTraversalAfter" format="integer" />
<!-- Name of the method in this View's context to invoke when the view is
@@ -2890,15 +2893,13 @@
<attr name="forceHasOverlappingRendering" format="boolean" />
<!-- Defines text displayed in a small popup window on hover or long press. -->
- <attr name="tooltip" format="string" localization="suggested" />
+ <attr name="tooltipText" format="string" localization="suggested" />
<!-- Whether this view is a root of a keyboard navigation cluster.
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.
@@ -3270,8 +3266,8 @@
<!-- The unique id for the subtype. The text service (spell checker) framework keeps track
of enabled subtypes by ID. When the spell checker package gets upgraded, enabled IDs
will stay enabled even if other attributes are different. If the ID is unspecified or
- or explicitly specified to 0 in XML resources,
- {@code Arrays.hashCode(new Object[] {subtypeLocale, extraValue}) will be used instead.
+ explicitly specified to 0 in XML resources,
+ {@code Arrays.hashCode(new Object[] {subtypeLocale, extraValue})} will be used instead.
-->
<attr name="subtypeId" />
<!-- The BCP-47 Language Tag of the subtype. This replaces
@@ -4707,7 +4703,8 @@
screens with limited space for text. -->
<enum name="full" value="2" />
</attr>
- <!-- Specify the type of auto-size. -->
+ <!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
+ works only for TextView -->
<attr name="autoSizeText" format="enum">
<!-- No auto-sizing (default). -->
<enum name="none" value="0" />
@@ -5842,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. -->
@@ -7029,7 +7034,7 @@
<attr name="contentDescription" format="string" />
<!-- The tooltip text associated with the item. -->
- <attr name="tooltip" format="string" />
+ <attr name="tooltipText" format="string" />
</declare-styleable>
@@ -7918,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">
@@ -8476,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 b961394..82e33f8 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -776,6 +776,21 @@
<enum name="locked" value="14" />
</attr>
+ <!-- Specify the configuration changes that trigger the system to restart the
+ current activity if any of these configuration changes happen in the system.
+ The valid configuration changes include mcc and mnc which are the same with
+ those in configChanges. By default from Android O, we don't restart the activity
+ even the app doesn't specify mcc or mnc in configChanges. If the app wants to
+ restart, specify them in restartOnConfigChanges. -->
+ <attr name="restartOnConfigChanges">
+ <!-- The IMSI MCC has changed, that is a SIM has been detected and
+ updated the Mobile Country Code. -->
+ <flag name="mcc" value="0x0001" />
+ <!-- The IMSI MNC has changed, that is a SIM has been detected and
+ updated the Mobile Network Code. -->
+ <flag name="mnc" value="0x0002" />
+ </attr>
+
<!-- Specify one or more configuration changes that the activity will
handle itself. If not specified, the activity will be restarted
if any of these configuration changes happen in the system. Otherwise,
@@ -793,10 +808,16 @@
include/utils/ResourceTypes.h. -->
<attr name="configChanges">
<!-- The IMSI MCC has changed, that is a SIM has been detected and
- updated the Mobile Country Code. -->
+ updated the Mobile Country Code. By default from Android O, we
+ don't restart the activity even the app doesn't specify mcc in
+ configChanges. If the app wants to restart, specify mcc in
+ restartOnConfigChanges. -->
<flag name="mcc" value="0x0001" />
<!-- The IMSI MNC has changed, that is a SIM has been detected and
- updated the Mobile Network Code. -->
+ updated the Mobile Network Code. By default from Android O, we
+ don't restart the activity even the app doesn't specify mnc in
+ configChanges. If the app wants to restart, specify mnc in
+ restartOnConfigChanges. -->
<flag name="mnc" value="0x0002" />
<!-- The locale has changed, that is the user has selected a new
language that text should be displayed in. -->
@@ -843,6 +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 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" />
@@ -978,6 +1001,13 @@
<enum name="preferExternal" value="2" />
</attr>
+ <!-- If set to <code>true</code>, indicates to the platform that any split APKs
+ installed for this application should be loaded into their own Context
+ objects and not appear in the base application's Context.
+
+ <p>The default value of this attribute is <code>false</code>. -->
+ <attr name="isolatedSplits" format="boolean" />
+
<!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or
{@code <application>} tag. If specified on the {@code <application>}
tag these will be considered defaults for all activities in the
@@ -1135,18 +1165,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.
@@ -1209,13 +1236,17 @@
-->
<attr name="autoVerify" format="boolean" />
- <!-- Specify whether a component should be visible to ephemeral apps.
+ <!-- Specify whether a component should be visible to instant apps.
-->
- <attr name="visibleToEphemeral" format="boolean" />
+ <attr name="visibleToInstantApps" format="boolean" />
<!-- 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
@@ -1242,6 +1273,7 @@
<attr name="sharedUserId" />
<attr name="sharedUserLabel" />
<attr name="installLocation" />
+ <attr name="isolatedSplits" />
</declare-styleable>
<!-- The <code>application</code> tag describes application-level components
@@ -1597,6 +1629,27 @@
<attr name="name" />
</declare-styleable>
+
+ <!-- The <code>static-library</code> tag declares that this apk is providing itself
+ as a static shared library for other applications to use. Any app can declare such
+ a library and there can be only one static shared library per package. These libraries
+ are updatable, multiple versions can be installed at the same time, and an app links
+ against a specific version simulating static linking while allowing code sharing.
+ Other apks can link to it with the {@link #AndroidManifestUsesLibrary uses-static-library}
+ tag.
+
+ <p>This appears as a child tag of the
+ {@link #AndroidManifestApplication application} tag. -->
+ <declare-styleable name="AndroidManifestStaticLibrary" parent="AndroidManifestApplication">
+ <!-- Required public name of the library, which other components and
+ packages will use when referring to this library. This is a string using
+ Java-style scoping to ensure it is unique. The name should typically
+ be the same as the apk's package name. -->
+ <attr name="name" />
+ <!-- Required specific library version. -->
+ <attr name="version" />
+ </declare-styleable>
+
<!-- The <code>uses-libraries</code> specifies a shared library that this
package requires to be linked against. Specifying this flag tells the
system to include this library's code in your class loader.
@@ -1616,6 +1669,24 @@
<attr name="required" />
</declare-styleable>
+ <!-- The <code>uses-static-library</code> specifies a shared <strong>static</strong>
+ library that this package requires to be statically linked against. Specifying
+ this tag tells the system to include this library's code in your class loader.
+ Depending on a static shared library is equivalent to statically linking with
+ the library at build time while it offers apps to share code defined in such
+ libraries. Hence, static libraries are strictly required.
+
+ <p>This appears as a child tag of the
+ {@link #AndroidManifestApplication application} tag. -->
+ <declare-styleable name="AndroidManifestUsesStaticLibrary" parent="AndroidManifestApplication">
+ <!-- Required name of the library you use. -->
+ <attr name="name" />
+ <!-- Specify which version of the shared library should be statically linked. -->
+ <attr name="version" />
+ <!-- The SHA-256 digest of the library signing certificate. -->
+ <attr name="certDigest" format="string" />
+ </declare-styleable>
+
<!-- The <code>supports-screens</code> specifies the screen dimensions an
application supports. By default a modern application supports all
screen sizes and must explicitly disable certain screen sizes here;
@@ -1759,7 +1830,9 @@
<attr name="exported" />
<attr name="singleUser" />
<attr name="directBootAware" />
- <attr name="visibleToEphemeral" />
+ <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
@@ -1849,7 +1922,9 @@
client to bind to the service as if it were running it its own package. The service
must also be {@link android.R.attr#exported} if this flag is set. -->
<attr name="externalService" format="boolean" />
- <attr name="visibleToEphemeral" />
+ <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
@@ -1917,6 +1992,7 @@
<attr name="launchMode" />
<attr name="screenOrientation" />
<attr name="configChanges" />
+ <attr name="restartOnConfigChanges" />
<attr name="permission" />
<attr name="multiprocess" />
<attr name="process" />
@@ -1971,7 +2047,19 @@
This attribute is ignored if the activity isn't a launcher. -->
<attr name="onTopLauncher" format="boolean" />
<attr name="rotationAnimation" />
- <attr name="visibleToEphemeral" />
+ <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
@@ -2285,7 +2373,7 @@
</declare-styleable>
<!-- Declaration of an {@link android.content.Intent} object in XML. May
- also include zero or more {@link #IntentCategory <category> and
+ also include zero or more {@link #IntentCategory <category>} and
{@link #Extra <extra>} tags. -->
<declare-styleable name="Intent">
<!-- The action name to assign to the Intent, as per
@@ -2382,4 +2470,8 @@
<attr name="hash" format="string" />
</declare-styleable>
+ <declare-styleable name="AndroidManifestUsesSplit" parent="AndroidManifest">
+ <attr name="name" format="string" />
+ </declare-styleable>
+
</resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 4164e5d..b28c6f2 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -89,7 +89,7 @@
<color name="perms_dangerous_perm_color">#33b5e5</color>
<color name="shadow">#cc222222</color>
<color name="perms_costs_money">#fff4511e</color>
-
+
<!-- For search-related UIs -->
<color name="search_url_text_normal">#7fa87f</color>
<color name="search_url_text_selected">@android:color/black</color>
@@ -132,6 +132,10 @@
<drawable name="notification_template_icon_low_bg">#0cffffff</drawable>
<drawable name="notification_template_divider">#29000000</drawable>
<drawable name="notification_template_divider_media">#29ffffff</drawable>
+ <color name="notification_primary_text_color_light">@color/primary_text_default_material_light</color>
+ <color name="notification_primary_text_color_dark">@color/primary_text_default_material_dark</color>
+ <color name="notification_secondary_text_color_light">@color/secondary_text_material_light</color>
+ <color name="notification_secondary_text_color_dark">@color/secondary_text_material_dark</color>
<color name="notification_material_background_color">#ffffffff</color>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 8f0350a..e2498df7 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -20,11 +20,14 @@
<color name="primary_device_default_dark">@color/primary_material_dark</color>
<color name="primary_device_default_light">@color/primary_material_light</color>
<color name="primary_device_default_settings">@color/primary_material_settings</color>
+ <color name="primary_device_default_settings_light">@color/primary_material_settings_light</color>
<color name="primary_dark_device_default_dark">@color/primary_dark_material_dark</color>
<color name="primary_dark_device_default_light">@color/primary_dark_material_light</color>
<color name="primary_dark_device_default_settings">@color/primary_dark_material_settings</color>
+ <color name="primary_dark_device_default_settings_light">@color/primary_dark_material_settings_light</color>
<color name="secondary_device_default_settings">@color/secondary_material_settings</color>
+ <color name="secondary_device_default_settings_light">@color/secondary_material_settings_light</color>
<color name="tertiary_device_default_settings">@color/tertiary_material_settings</color>
<color name="quaternary_device_default_settings">@color/quaternary_material_settings</color>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 835b8b60..db89c22 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -27,12 +27,15 @@
<color name="primary_material_dark">@color/material_grey_900</color>
<color name="primary_material_light">@color/material_grey_100</color>
<color name="primary_material_settings">@color/material_blue_grey_900</color>
+ <color name="primary_material_settings_light">@color/material_grey_100</color>
<color name="primary_dark_material_dark">@color/black</color>
<color name="primary_dark_material_light">@color/material_grey_600</color>
<color name="primary_dark_material_light_light_status_bar">@color/material_grey_300</color>
<color name="primary_dark_material_settings">@color/material_blue_grey_950</color>
+ <color name="primary_dark_material_settings_light">@color/material_grey_300</color>
<color name="secondary_material_settings">@color/material_blue_grey_800</color>
+ <color name="secondary_material_settings_light">@color/material_grey_200</color>
<color name="tertiary_material_settings">@color/material_blue_grey_700</color>
<color name="quaternary_material_settings">@color/material_blue_grey_200</color>
@@ -88,6 +91,7 @@
<color name="material_grey_800">#ff424242</color>
<color name="material_grey_600">#ff757575</color>
<color name="material_grey_300">#ffe0e0e0</color>
+ <color name="material_grey_200">#ffeeeeee</color>
<color name="material_grey_100">#fff5f5f5</color>
<color name="material_grey_50">#fffafafa</color>
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..4266f88 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -488,6 +488,9 @@
<!-- The default minimal size of a resizable task, in both dimensions. -->
<dimen name="default_minimal_size_resizable_task">220dp</dimen>
+ <!-- Height of a task when in minimized mode from the top when launcher is resizable. -->
+ <dimen name="task_height_of_minimized_mode">80dp</dimen>
+
<!-- Minimum "smallest width" of the display for cascading menus to be enabled. -->
<dimen name="cascading_menus_min_smallest_width">720dp</dimen>
@@ -509,4 +512,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 099fe08..1146871 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2764,7 +2764,7 @@
<public name="fontStyle" />
<public name="font" />
<public name="fontWeight" />
- <public name="tooltip" />
+ <public name="tooltipText" />
<public name="autoSizeText" />
<public name="autoSizeStepGranularity" />
<public name="autoSizeStepSizeSet" />
@@ -2775,22 +2775,28 @@
<public name="layout_marginVertical" />
<public name="paddingHorizontal" />
<public name="paddingVertical" />
- <public name="visibleToEphemeral" />
+ <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" />
<public name="autoSizeMaxTextSize" />
<public name="supportsDismissingWindow" />
+ <public name="restartOnConfigChanges" />
+ <public name="certDigest" />
+ <public name="splitName" />
+ <public name="colorMode" />
+ <public name="isolatedSplits" />
</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 0204e93..ac8c896 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -397,7 +397,7 @@
<string name="network_logging_notification_title">Network traffic is being monitored</string>
<!-- Content text for a notification. Tapping opens a dialog with more information on network
logging. [CHAR LIMIT=NONE]-->
- <string name="network_logging_notification_text">Tap for more details</string>
+ <string name="network_logging_notification_text">Tap to learn more</string>
<!-- Factory reset warning dialog strings--> <skip />
<!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] -->
@@ -578,6 +578,9 @@
<!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] -->
<string name="notification_header_divider_symbol" translatable="false">•</string>
+ <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] -->
+ <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string>
+
<!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
<string name="notification_hidden_text">Contents hidden</string>
@@ -1019,6 +1022,14 @@
phone number and device IDs, whether a call is active, and the remote number
connected by a call.</string>
+ <!-- Title of an application permission. When granted the user is giving access to a third
+ party app to route its calls through the system. -->
+ <string name="permlab_manageOwnCalls">route calls through the system</string>
+ <!-- Description of an application permission. When granted the user is giving access to a
+ third party app to route its calls through the system. -->
+ <string name="permdesc_manageOwnCalls">Allows the app to route its calls through the system in
+ order to improve the calling experience.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readPhoneNumber">read phone number</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -2581,6 +2592,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. -->
@@ -3182,6 +3202,11 @@
<!-- Description of an application permission that lets it read install sessions. -->
<string name="permdesc_requestInstallPackages">Allows an application to request installation of packages.</string>
+ <!-- Title of an application permission that lets it read install sessions. -->
+ <string name="permlab_requestDeletePackages">request delete packages</string>
+ <!-- Description of an application permission that lets it read install sessions. -->
+ <string name="permdesc_requestDeletePackages">Allows an application to request deletion of packages.</string>
+
<!-- Title of an application permission that lets it ask user to ignore battery optimizations for that app. -->
<string name="permlab_requestIgnoreBatteryOptimizations">ask to ignore battery optimizations</string>
<!-- Description of an application permission that lets it ask user to ignore battery optimizations for that app-->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 842b575..faf451b 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1290,7 +1290,7 @@
<style name="Widget.ActionButton.Overflow">
<item name="src">@drawable/ic_menu_more</item>
<item name="contentDescription">@string/action_menu_overflow_description</item>
- <item name="tooltip">@string/action_menu_overflow_description</item>
+ <item name="tooltipText">@string/action_menu_overflow_description</item>
</style>
<style name="Widget.ActionButton.CloseMode">
diff --git a/core/res/res/values/styles_holo.xml b/core/res/res/values/styles_holo.xml
index 12b8164..dc32b26 100644
--- a/core/res/res/values/styles_holo.xml
+++ b/core/res/res/values/styles_holo.xml
@@ -660,7 +660,7 @@
<item name="src">@drawable/ic_menu_moreoverflow_holo_dark</item>
<item name="background">?attr/actionBarItemBackground</item>
<item name="contentDescription">@string/action_menu_overflow_description</item>
- <item name="tooltip">@string/action_menu_overflow_description</item>
+ <item name="tooltipText">@string/action_menu_overflow_description</item>
</style>
<style name="Widget.Holo.ActionButton.TextButton" parent="Widget.Holo.ButtonBar.Button" />
@@ -995,7 +995,7 @@
<style name="Widget.Holo.Light.ActionButton.Overflow">
<item name="src">@drawable/ic_menu_moreoverflow_holo_light</item>
<item name="contentDescription">@string/action_menu_overflow_description</item>
- <item name="tooltip">@string/action_menu_overflow_description</item>
+ <item name="tooltipText">@string/action_menu_overflow_description</item>
</style>
<style name="Widget.Holo.Light.ActionBar.TabView" parent="Widget.Holo.ActionBar.TabView" />
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 709200e..1e15348 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -451,14 +451,14 @@
</style>
<style name="TextAppearance.Material.Notification">
- <item name="textColor">@color/secondary_text_material_light</item>
+ <item name="textColor">@color/notification_secondary_text_color_light</item>
<item name="textSize">@dimen/notification_text_size</item>
</style>
<style name="TextAppearance.Material.Notification.Reply" />
<style name="TextAppearance.Material.Notification.Title">
- <item name="textColor">@color/primary_text_default_material_light</item>
+ <item name="textColor">@color/notification_primary_text_color_light</item>
<item name="textSize">@dimen/notification_title_text_size</item>
</style>
@@ -467,7 +467,7 @@
</style>
<style name="TextAppearance.Material.Notification.Info">
- <item name="textColor">@color/secondary_text_default_material_light</item>
+ <item name="textColor">@color/notification_secondary_text_color_light</item>
<item name="textSize">@dimen/notification_subtext_size</item>
</style>
@@ -944,7 +944,7 @@
<item name="src">@drawable/ic_menu_moreoverflow_material</item>
<item name="background">?attr/actionBarItemBackground</item>
<item name="contentDescription">@string/action_menu_overflow_description</item>
- <item name="tooltip">@string/action_menu_overflow_description</item>
+ <item name="tooltipText">@string/action_menu_overflow_description</item>
<item name="minWidth">@dimen/action_button_min_width_overflow_material</item>
<item name="minHeight">@dimen/action_button_min_height_material</item>
<item name="paddingStart">@dimen/action_bar_overflow_padding_start_material</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c370ef7..0626df3 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" />
@@ -501,7 +504,6 @@
<java-symbol type="string" name="NetworkPreferenceSwitchTitle" />
<java-symbol type="string" name="SetupCallDefault" />
<java-symbol type="string" name="accept" />
- <java-symbol type="string" name="accessibility_enabled" />
<java-symbol type="string" name="activity_chooser_view_see_all" />
<java-symbol type="string" name="activitychooserview_choose_application" />
<java-symbol type="string" name="activitychooserview_choose_application_error" />
@@ -598,7 +600,6 @@
<java-symbol type="string" name="contentServiceSync" />
<java-symbol type="string" name="contentServiceSyncNotificationTitle" />
<java-symbol type="string" name="contentServiceTooManyDeletesNotificationDesc" />
- <java-symbol type="string" name="continue_to_enable_accessibility" />
<java-symbol type="string" name="date_and_time" />
<java-symbol type="string" name="date_picker_decrement_day_button" />
<java-symbol type="string" name="date_picker_decrement_month_button" />
@@ -645,7 +646,6 @@
<java-symbol type="string" name="widget_default_class_name" />
<java-symbol type="string" name="emergency_calls_only" />
<java-symbol type="array" name="config_ephemeralResolverPackage" />
- <java-symbol type="string" name="enable_accessibility_canceled" />
<java-symbol type="string" name="eventTypeAnniversary" />
<java-symbol type="string" name="eventTypeBirthday" />
<java-symbol type="string" name="eventTypeCustom" />
@@ -1133,7 +1133,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" />
@@ -1251,6 +1253,11 @@
<java-symbol type="drawable" name="platlogo" />
<java-symbol type="drawable" name="stat_notify_sync_error" />
<java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+ <java-symbol type="drawable" name="ic_wifi_signal_0" />
+ <java-symbol type="drawable" name="ic_wifi_signal_1" />
+ <java-symbol type="drawable" name="ic_wifi_signal_2" />
+ <java-symbol type="drawable" name="ic_wifi_signal_3" />
+ <java-symbol type="drawable" name="ic_wifi_signal_4" />
<java-symbol type="drawable" name="ic_signal_wifi_badged_0_bars" />
<java-symbol type="drawable" name="ic_signal_wifi_badged_1_bar" />
<java-symbol type="drawable" name="ic_signal_wifi_badged_2_bars" />
@@ -1767,6 +1774,7 @@
<java-symbol type="id" name="replace_message" />
<java-symbol type="fraction" name="config_dimBehindFadeDuration" />
<java-symbol type="dimen" name="default_minimal_size_resizable_task" />
+ <java-symbol type="dimen" name="task_height_of_minimized_mode" />
<java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" />
<java-symbol type="fraction" name="config_autoBrightnessAdjustmentMaxGamma" />
<java-symbol type="integer" name="config_autoBrightnessAmbientLightHorizon"/>
@@ -2783,8 +2791,14 @@
<java-symbol type="drawable" name="lockscreen_notselected" />
<java-symbol type="drawable" name="lockscreen_selected" />
+ <java-symbol type="string" name="notification_header_divider_symbol_with_spaces" />
<java-symbol type="string" name="config_defaultCellBroadcastReceiverComponent" />
+ <java-symbol type="color" name="notification_primary_text_color_light" />
+ <java-symbol type="color" name="notification_primary_text_color_dark" />
+ <java-symbol type="color" name="notification_secondary_text_color_light" />
+ <java-symbol type="color" name="notification_secondary_text_color_dark" />
+
<java-symbol type="string" name="app_category_game" />
<java-symbol type="string" name="app_category_audio" />
<java-symbol type="string" name="app_category_video" />
@@ -2796,6 +2810,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 +2822,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/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 6bd6e1c..5961cb0 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -736,20 +736,12 @@
<!-- DeviceDefault theme for a window that should look like the Settings app. -->
<style name="Theme.DeviceDefault.Settings" parent="Theme.Material.Settings">
<!-- Color palette -->
- <item name="colorPrimary">@color/primary_device_default_settings</item>
- <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
- <item name="colorSecondary">@color/secondary_device_default_settings</item>
+ <item name="colorPrimary">@color/primary_device_default_settings_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
+ <item name="colorSecondary">@color/secondary_device_default_settings_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
</style>
- <!-- @hide DeviceDefault theme that should look like the Settings app. The action bar will use a
- light background and accent color for text and icon. -->
- <style name="Theme.DeviceDefault.Settings.LightActionBar">
- <!-- ActionBar -->
- <item name="actionBarTheme">@style/ThemeOverlay.DeviceDefault.ActionBar.Accent</item>
- <item name="actionBarStyle">@style/Widget.DeviceDefault.Light.ActionBar</item>
- </style>
-
<!-- @hide DeviceDefault theme for a window that should use Settings theme colors
but has a full dark palette. ONLY USED FOR QUICK SETTINGS THEME -->
<style name="Theme.DeviceDefault.QuickSettings" parent="Theme.Material">
@@ -817,12 +809,6 @@
<style name="ThemeOverlay.DeviceDefault" />
- <!-- @hide Theme overlay that inherits from material actionbar, and use accent color for
- primary text -->
- <style name="ThemeOverlay.DeviceDefault.ActionBar.Accent" parent="ThemeOverlay.Material.ActionBar">
- <item name="textColorPrimary">@color/btn_colored_borderless_text_material</item>
- </style>
-
<style name="ThemeOverlay.DeviceDefault.Accent">
<item name="colorAccent">@color/accent_device_default_dark</item>
</style>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index d0f202b..3587fec 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -1323,10 +1323,10 @@
</style>
<!-- Default theme for Settings and activities launched from Settings. -->
- <style name="Theme.Material.Settings" parent="Theme.Material.Light.DarkActionBar">
- <item name="colorPrimary">@color/primary_material_settings</item>
- <item name="colorPrimaryDark">@color/primary_dark_material_settings</item>
- <item name="colorSecondary">@color/secondary_material_settings</item>
+ <style name="Theme.Material.Settings" parent="Theme.Material.Light.LightStatusBar">
+ <item name="colorPrimary">@color/primary_material_settings_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_material_settings_light</item>
+ <item name="colorSecondary">@color/secondary_material_settings_light</item>
<item name="presentationTheme">@style/Theme.Material.Settings.Dialog.Presentation</item>
<item name="searchDialogTheme">@style/Theme.Material.Settings.SearchBar</item>
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/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
new file mode 100644
index 0000000..f60bf94
--- /dev/null
+++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static android.os.storage.VolumeInfo.STATE_MOUNTED;
+import static android.os.storage.VolumeInfo.STATE_UNMOUNTED;
+
+public class ApplicationPackageManagerTest extends TestCase {
+ private static final String sInternalVolPath = "/data";
+ private static final String sAdoptedVolPath = "/mnt/expand/123";
+ private static final String sPublicVolPath = "/emulated";
+ private static final String sPrivateUnmountedVolPath = "/private";
+
+ private static final String sInternalVolUuid = null; //StorageManager.UUID_PRIVATE_INTERNAL
+ private static final String sAdoptedVolUuid = "adopted";
+ private static final String sPublicVolUuid = "emulated";
+ private static final String sPrivateUnmountedVolUuid = "private";
+
+ private static final VolumeInfo sInternalVol = new VolumeInfo("private",
+ VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
+
+ private static final VolumeInfo sAdoptedVol = new VolumeInfo("adopted",
+ VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
+
+ private static final VolumeInfo sPublicVol = new VolumeInfo("public",
+ VolumeInfo.TYPE_PUBLIC, null /*DiskInfo*/, null /*partGuid*/);
+
+ private static final VolumeInfo sPrivateUnmountedVol = new VolumeInfo("private2",
+ VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
+
+ private static final List<VolumeInfo> sVolumes = new ArrayList<>();
+
+ static {
+ sInternalVol.path = sInternalVolPath;
+ sInternalVol.state = STATE_MOUNTED;
+ sInternalVol.fsUuid = sInternalVolUuid;
+
+ sAdoptedVol.path = sAdoptedVolPath;
+ sAdoptedVol.state = STATE_MOUNTED;
+ sAdoptedVol.fsUuid = sAdoptedVolUuid;
+
+ sPublicVol.state = STATE_MOUNTED;
+ sPublicVol.path = sPublicVolPath;
+ sPublicVol.fsUuid = sPublicVolUuid;
+
+ sPrivateUnmountedVol.state = STATE_UNMOUNTED;
+ sPrivateUnmountedVol.path = sPrivateUnmountedVolPath;
+ sPrivateUnmountedVol.fsUuid = sPrivateUnmountedVolUuid;
+
+ sVolumes.add(sInternalVol);
+ sVolumes.add(sAdoptedVol);
+ sVolumes.add(sPublicVol);
+ sVolumes.add(sPrivateUnmountedVol);
+ }
+
+ private static final class MockedApplicationPackageManager extends ApplicationPackageManager {
+ private boolean mForceAllowOnExternal = false;
+ private boolean mAllow3rdPartyOnInternal = true;
+
+ public MockedApplicationPackageManager() {
+ super(null, null);
+ }
+
+ public void setForceAllowOnExternal(boolean forceAllowOnExternal) {
+ mForceAllowOnExternal = forceAllowOnExternal;
+ }
+
+ public void setAllow3rdPartyOnInternal(boolean allow3rdPartyOnInternal) {
+ mAllow3rdPartyOnInternal = allow3rdPartyOnInternal;
+ }
+
+ @Override
+ public boolean isForceAllowOnExternal(Context context) {
+ return mForceAllowOnExternal;
+ }
+
+ @Override
+ public boolean isAllow3rdPartyOnInternal(Context context) {
+ return mAllow3rdPartyOnInternal;
+ }
+ }
+
+ private StorageManager getMockedStorageManager() {
+ StorageManager storageManager = Mockito.mock(StorageManager.class);
+ Mockito.when(storageManager.getVolumes()).thenReturn(sVolumes);
+ Mockito.when(storageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL))
+ .thenReturn(sInternalVol);
+ Mockito.when(storageManager.findVolumeByUuid(sAdoptedVolUuid))
+ .thenReturn(sAdoptedVol);
+ Mockito.when(storageManager.findVolumeByUuid(sPublicVolUuid))
+ .thenReturn(sPublicVol);
+ Mockito.when(storageManager.findVolumeByUuid(sPrivateUnmountedVolUuid))
+ .thenReturn(sPrivateUnmountedVol);
+ return storageManager;
+ }
+
+ private void verifyReturnedVolumes(List<VolumeInfo> actualVols, VolumeInfo... exptectedVols) {
+ boolean failed = false;
+ if (actualVols.size() != exptectedVols.length) {
+ failed = true;
+ } else {
+ for (VolumeInfo vol : exptectedVols) {
+ if (!actualVols.contains(vol)) {
+ failed = true;
+ break;
+ }
+ }
+ }
+
+ if (failed) {
+ fail("Wrong volumes returned.\n Expected: " + Arrays.toString(exptectedVols)
+ + "\n Actual: " + Arrays.toString(actualVols.toArray()));
+ }
+ }
+
+ public void testGetCandidateVolumes_systemApp() throws Exception {
+ ApplicationInfo sysAppInfo = new ApplicationInfo();
+ sysAppInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+ StorageManager storageManager = getMockedStorageManager();
+ IPackageManager pm = Mockito.mock(IPackageManager.class);
+
+ MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
+
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+ appPkgMgr.setForceAllowOnExternal(true);
+ List<VolumeInfo> candidates =
+ appPkgMgr.getPackageCandidateVolumes(sysAppInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+ appPkgMgr.setForceAllowOnExternal(false);
+ candidates = appPkgMgr.getPackageCandidateVolumes(sysAppInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+
+ appPkgMgr.setAllow3rdPartyOnInternal(false);
+ appPkgMgr.setForceAllowOnExternal(false);
+ candidates = appPkgMgr.getPackageCandidateVolumes(sysAppInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+
+ appPkgMgr.setAllow3rdPartyOnInternal(false);
+ appPkgMgr.setForceAllowOnExternal(true);
+ candidates = appPkgMgr.getPackageCandidateVolumes(sysAppInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+ }
+
+ public void testGetCandidateVolumes_3rdParty_internalOnly() throws Exception {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ StorageManager storageManager = getMockedStorageManager();
+
+ IPackageManager pm = Mockito.mock(IPackageManager.class);
+ Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(false);
+
+ MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
+
+ // must allow 3rd party on internal, otherwise the app wouldn't have been installed before.
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+
+ // INSTALL_LOCATION_INTERNAL_ONLY AND INSTALL_LOCATION_UNSPECIFIED are treated the same.
+ int[] locations = {PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY,
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED};
+
+ for (int location : locations) {
+ appInfo.installLocation = location;
+ appPkgMgr.setForceAllowOnExternal(true);
+ List<VolumeInfo> candidates = appPkgMgr.getPackageCandidateVolumes(
+ appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol, sAdoptedVol);
+
+ appPkgMgr.setForceAllowOnExternal(false);
+ candidates = appPkgMgr.getPackageCandidateVolumes(appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+ }
+ }
+
+ public void testGetCandidateVolumes_3rdParty_auto() throws Exception {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ StorageManager storageManager = getMockedStorageManager();
+
+ IPackageManager pm = Mockito.mock(IPackageManager.class);
+
+ MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
+ appPkgMgr.setForceAllowOnExternal(true);
+
+ // INSTALL_LOCATION_AUTO AND INSTALL_LOCATION_PREFER_EXTERNAL are treated the same.
+ int[] locations = {PackageInfo.INSTALL_LOCATION_AUTO,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL};
+
+ for (int location : locations) {
+ appInfo.installLocation = location;
+ appInfo.flags = 0;
+
+ appInfo.volumeUuid = sInternalVolUuid;
+ Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(false);
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+ List<VolumeInfo> candidates = appPkgMgr.getPackageCandidateVolumes(
+ appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol, sAdoptedVol);
+
+ appInfo.volumeUuid = sInternalVolUuid;
+ appPkgMgr.setAllow3rdPartyOnInternal(true);
+ Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(true);
+ candidates = appPkgMgr.getPackageCandidateVolumes(appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sInternalVol);
+
+ appInfo.flags = ApplicationInfo.FLAG_EXTERNAL_STORAGE;
+ appInfo.volumeUuid = sAdoptedVolUuid;
+ appPkgMgr.setAllow3rdPartyOnInternal(false);
+ candidates = appPkgMgr.getPackageCandidateVolumes(appInfo, storageManager, pm);
+ verifyReturnedVolumes(candidates, sAdoptedVol);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 2a3c22c..ca4141a 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -215,4 +215,46 @@
// DEV: Released API 20
verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
}
+
+ /**
+ * Unit test for PackageParser.getActivityConfigChanges().
+ * If the bit is 1 in the original configChanges, it is still 1 in the final configChanges.
+ * If the bit is 0 in the original configChanges and the bit is not set to 1 in
+ * restartOnConfigChanges, the bit is changed to 1 in the final configChanges by default.
+ */
+ @Test
+ public void testGetActivityConfigChanges() {
+ // Not set in either configChanges or restartOnConfigChanges.
+ int configChanges = 0x0000; // 00000000.
+ int restartOnConfigChanges = 0x0000; // 00000000.
+ int finalConfigChanges =
+ PackageParser.getActivityConfigChanges(configChanges, restartOnConfigChanges);
+ assertEquals(0x0003, finalConfigChanges); // Should be 00000011.
+
+ // Not set in configChanges, but set in restartOnConfigChanges.
+ configChanges = 0x0000; // 00000000.
+ restartOnConfigChanges = 0x0003; // 00000011.
+ finalConfigChanges =
+ PackageParser.getActivityConfigChanges(configChanges, restartOnConfigChanges);
+ assertEquals(0x0000, finalConfigChanges); // Should be 00000000.
+
+ // Set in configChanges.
+ configChanges = 0x0003; // 00000011.
+ restartOnConfigChanges = 0X0000; // 00000000.
+ finalConfigChanges =
+ PackageParser.getActivityConfigChanges(configChanges, restartOnConfigChanges);
+ assertEquals(0x0003, finalConfigChanges); // Should be 00000011.
+
+ restartOnConfigChanges = 0x0003; // 00000011.
+ finalConfigChanges =
+ PackageParser.getActivityConfigChanges(configChanges, restartOnConfigChanges);
+ assertEquals(0x0003, finalConfigChanges); // Should still be 00000011.
+
+ // Other bit set in configChanges.
+ configChanges = 0x0080; // 10000000, orientation.
+ restartOnConfigChanges = 0x0000; // 00000000.
+ finalConfigChanges =
+ PackageParser.getActivityConfigChanges(configChanges, restartOnConfigChanges);
+ assertEquals(0x0083, finalConfigChanges); // Should be 10000011.
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
similarity index 76%
rename from core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
rename to core/tests/coretests/src/android/metrics/LogMakerTest.java
index a340559..b0c394e 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,11 +95,12 @@
}
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);
builder.addTaggedData(4, 123.0F);
+ builder.addTaggedData(5, null);
Object[] out = builder.serialize();
assertEquals("onetwothree", out[1]);
assertEquals(123, out[3]);
@@ -107,11 +108,28 @@
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 testClearData() {
+ LogMaker builder = new LogMaker(0);
+ builder.addTaggedData(1, "onetwothree");
+ builder.clearTaggedData(1);
+ assertEquals(null, builder.getTaggedData(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/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
index 29020ba..5bfff26 100644
--- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
+++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
@@ -169,25 +169,6 @@
assertNull(activeScorer);
}
- public void testIsCallerActiveScorer_providerNotAvailable() throws Exception {
- ContentResolver cr = mTargetContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1);
-
- assertFalse(mNetworkScorerAppManager.isCallerActiveScorer(924));
- }
-
- public void testIsCallerActiveScorer_providerAvailable() throws Exception {
- setNetworkRecommendationPackageNames("package1");
- mockScoreNetworksGranted("package1");
- mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
-
- ContentResolver cr = mTargetContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1);
-
- assertTrue(mNetworkScorerAppManager.isCallerActiveScorer(924));
- assertFalse(mNetworkScorerAppManager.isCallerActiveScorer(925));
- }
-
private void setNetworkRecommendationPackageNames(String... names) {
if (names == null) {
names = new String[0];
diff --git a/core/tests/coretests/src/android/net/RecommendationRequestTest.java b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
index 39c1691..bd25500 100644
--- a/core/tests/coretests/src/android/net/RecommendationRequestTest.java
+++ b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
@@ -3,6 +3,7 @@
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.os.Parcel;
+import android.os.SystemClock;
import android.test.AndroidTestCase;
public class RecommendationRequestTest extends AndroidTestCase {
@@ -10,6 +11,8 @@
private WifiConfiguration mDefaultConfig;
private WifiConfiguration mConnectedConfig;
private WifiConfiguration[] mConnectableConfigs;
+ private int mLastSelectedNetworkId;
+ private long mLastSelectedNetworkTimestamp;
@Override
public void setUp() throws Exception {
@@ -35,6 +38,8 @@
mConnectedConfig = new WifiConfiguration();
mConnectedConfig.SSID = "connected_config";
mConnectableConfigs = new WifiConfiguration[] {mDefaultConfig, mConnectedConfig};
+ mLastSelectedNetworkId = 5;
+ mLastSelectedNetworkTimestamp = SystemClock.elapsedRealtime();
}
public void testParceling() throws Exception {
@@ -43,6 +48,7 @@
.setScanResults(mScanResults)
.setConnectedWifiConfig(mConnectedConfig)
.setConnectableConfigs(mConnectableConfigs)
+ .setLastSelectedNetwork(mLastSelectedNetworkId, mLastSelectedNetworkTimestamp)
.build();
RecommendationRequest parceled = passThroughParcel(request);
@@ -60,6 +66,8 @@
for (int i = 0; i < parceledConfigs.length; i++) {
assertEquals(mConnectableConfigs[i].SSID, parceledConfigs[i].SSID);
}
+ assertEquals(mLastSelectedNetworkId, parceled.getLastSelectedNetworkId());
+ assertEquals(mLastSelectedNetworkTimestamp, parceled.getLastSelectedNetworkTimestamp());
}
public void testParceling_nullScanResults() throws Exception {
@@ -82,6 +90,16 @@
assertNull(parceledConfigs);
}
+ public void testParceling_unsetLastSelectedNetwork() throws Exception {
+ RecommendationRequest request = new RecommendationRequest.Builder()
+ .build();
+
+ RecommendationRequest parceled = passThroughParcel(request);
+
+ assertEquals(0, parceled.getLastSelectedNetworkId());
+ assertEquals(0, parceled.getLastSelectedNetworkTimestamp());
+ }
+
private RecommendationRequest passThroughParcel(RecommendationRequest request) {
Parcel p = Parcel.obtain();
RecommendationRequest output = null;
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/Android.mk b/data/etc/Android.mk
index 6718259..b2c6840 100644
--- a/data/etc/Android.mk
+++ b/data/etc/Android.mk
@@ -18,6 +18,14 @@
########################
include $(CLEAR_VARS)
+LOCAL_MODULE := framework-sysconfig.xml
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+########################
+include $(CLEAR_VARS)
LOCAL_MODULE := platform.xml
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions
@@ -31,4 +39,3 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
-
diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml
new file mode 100644
index 0000000..62ef25b
--- /dev/null
+++ b/data/etc/framework-sysconfig.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<!-- These are configurations that must exist on all Android devices. -->
+<config>
+
+ <!-- Broadcast actions that are currently exempted from O+ background
+ 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/docs/html/about/versions/android-4.0.jd b/docs/html/about/versions/android-4.0.jd
index 48afcd4..421019c 100644
--- a/docs/html/about/versions/android-4.0.jd
+++ b/docs/html/about/versions/android-4.0.jd
@@ -309,7 +309,7 @@
android.media.effect.EffectContext#getFactory EffectContext.getFactory()}, which returns an instance
of {@link android.media.effect.EffectFactory}.</li>
<li>Call {@link android.media.effect.EffectFactory#createEffect createEffect()}, passing it an
-effect name from @link android.media.effect.EffectFactory}, such as {@link
+effect name from {@link android.media.effect.EffectFactory}, such as {@link
android.media.effect.EffectFactory#EFFECT_FISHEYE} or {@link
android.media.effect.EffectFactory#EFFECT_VIGNETTE}.</li>
</ol>
diff --git a/docs/html/guide/topics/resources/providing-resources.jd b/docs/html/guide/topics/resources/providing-resources.jd
index 77e8d77..021a07e 100644
--- a/docs/html/guide/topics/resources/providing-resources.jd
+++ b/docs/html/guide/topics/resources/providing-resources.jd
@@ -565,6 +565,38 @@
method, which indicates whether the screen is round.</p>
</td>
</tr>
+ <tr id="WideColorGamutQualifier">
+ <td>Wide Color Gamut</td>
+ <td>
+ <code>widecg</code><br/>
+ <code>nowidecg</code>
+ </td>
+ <td>
+ <ul class="nolist">
+ <li>{@code widecg}: Displays with a wide color gamut such as Display P3 or AdobeRGB</li>
+ <li>{@code nowidecg}: Displays with a narrow color gamut such as sRGB</li>
+ </ul>
+ <p><em>Added in API level 26.</em></p>
+ <p>Also see the {@link android.content.res.Configuration#isScreenWideColorGamut()} configuration
+ method, which indicates whether the screen has a wide color gamut.</p>
+ </td>
+ </tr>
+ <tr id="HDRQualifier">
+ <td>High Dynamic Range (HDR)</td>
+ <td>
+ <code>highdr</code><br/>
+ <code>lowdr</code>
+ </td>
+ <td>
+ <ul class="nolist">
+ <li>{@code highdr}: Displays with a high-dynamic range</li>
+ <li>{@code lowdr}: Displays with a low/standard dynamic range</li>
+ </ul>
+ <p><em>Added in API level 26.</em></p>
+ <p>Also see the {@link android.content.res.Configuration#isScreenHdr()} configuration
+ method, which indicates whether the screen has a HDR capabilities.</p>
+ </td>
+ </tr>
<tr id="OrientationQualifier">
<td>Screen orientation</td>
<td>
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 2733c43f..2c93c32 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.content.res.AssetManager;
+import android.text.FontConfig;
import android.util.Log;
import dalvik.annotation.optimization.CriticalNative;
@@ -65,10 +66,23 @@
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();
}
@@ -89,7 +103,7 @@
}
}
- 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) {
if (mBuilderPtr == 0) {
throw new IllegalStateException("Unable to call addFontWeightStyle after freezing.");
@@ -97,11 +111,12 @@
return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, axes, weight, style);
}
- public boolean addFontFromAsset(AssetManager mgr, String path) {
+ public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
+ boolean isAsset) {
if (mBuilderPtr == 0) {
throw new IllegalStateException("Unable to call addFontFromAsset after freezing.");
}
- return nAddFontFromAsset(mBuilderPtr, mgr, path);
+ return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset);
}
private static native long nInitBuilder(String lang, int variant);
@@ -110,10 +125,14 @@
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 builderPtr, ByteBuffer font, int ttcIndex);
private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
- int ttcIndex, List<FontListParser.Axis> listOfAxis,
+ int ttcIndex, List<FontConfig.Axis> listOfAxis,
int weight, boolean isItalic);
- private static native boolean nAddFontFromAsset(long builderPtr, AssetManager mgr, String path);
+ 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 5b53296..4ec564a 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,12 +16,12 @@
package android.graphics;
+import android.text.FontConfig;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
@@ -37,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);
@@ -105,12 +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 ArrayList<Axis> parseFontVariationSettings(@Nullable String settings) {
- ArrayList<Axis> axisList = new ArrayList<>();
- if (settings == null) {
- return axisList;
- }
+ public static FontConfig.Axis[] parseFontVariationSettings(String settings) {
String[] settingList = settings.split(",");
+ ArrayList<FontConfig.Axis> axisList = new ArrayList<>();
settingLoop:
for (String setting : settingList) {
int pos = 0;
@@ -152,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;
+ return axisList.toArray(new FontConfig.Axis[axisList.size()]);
}
@VisibleForTesting
@@ -166,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);
}
@@ -184,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();
@@ -199,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"));
@@ -229,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
@@ -243,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");
@@ -262,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/Outline.java b/graphics/java/android/graphics/Outline.java
index 3e59f34..1c85df0 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -119,7 +119,7 @@
* Currently, only Outlines that can be represented as a rectangle, circle,
* or round rect support clipping.
*
- * @see {@link android.view.View#setClipToOutline(boolean)}
+ * @see android.view.View#setClipToOutline(boolean)
*/
public boolean canClip() {
return mMode != MODE_CONVEX_PATH;
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 4ee0c34..7815ae1 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -71,7 +71,6 @@
private LocaleList mLocales;
private String mFontFeatureSettings;
- private String mFontVariationSettings;
private static final Object sCacheLock = new Object();
@@ -1495,37 +1494,6 @@
}
/**
- * Returns the font variation settings.
- *
- * @return the paint's currently set font variation settings. Default is null.
- *
- * @see #setFontVariationSettings(String)
- */
- public String getFontVariationSettings() {
- return mFontVariationSettings;
- }
-
- /**
- * Set font variation settings.
- *
- * @param settings font variation settings, e.g. "'wdth' 300, 'wght' 1.8"
- *
- * @see #getFontVariationSettings()
- *
- * @param settings the font variation settings. You can pass null or empty string as no
- * variation settings.
- */
- public void setFontVariationSettings(String settings) {
- settings = TextUtils.nullIfEmpty(settings);
- if (settings == mFontVariationSettings
- || (settings != null && settings.equals(mFontVariationSettings))) {
- return;
- }
- mFontVariationSettings = settings;
- setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface, settings));
- }
-
- /**
* Get the current value of hyphen edit.
*
* @return the current hyphen edit value
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 166ef1b..38e8061 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -16,12 +16,25 @@
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 android.graphics.FontListParser;
+
+import com.android.internal.annotations.GuardedBy;
+
+import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -29,6 +42,8 @@
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";
@@ -110,6 +130,163 @@
}
/**
+ * @hide
+ * Used by Resources.
+ */
+ @NonNull
+ public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
+ if (sFallbackFonts != null) {
+ synchronized (sDynamicTypefaceCache) {
+ final String key = createAssetUid(mgr, path);
+ Typeface typeface = sDynamicTypefaceCache.get(key);
+ if (typeface != null) return typeface;
+
+ FontFamily fontFamily = new FontFamily();
+ if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */)) {
+ FontFamily[] families = {fontFamily};
+ typeface = createFromFamiliesWithDefault(families);
+ sDynamicTypefaceCache.put(key, typeface);
+ return typeface;
+ }
+ }
+ }
+ throw new RuntimeException("Font resource not found " + path);
+ }
+
+ /**
+ * 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
@@ -172,15 +349,6 @@
return typeface;
}
- /** @hide */
- public static Typeface createFromTypefaceWithVariation(Typeface family,
- String fontVariationSettings) {
- final long ni = family == null ? 0 : family.native_instance;
- ArrayList<FontListParser.Axis> axes =
- FontListParser.parseFontVariationSettings(fontVariationSettings);
- return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
- }
-
/**
* Returns one of the default typeface objects, based on the specified style
*
@@ -205,12 +373,14 @@
if (typeface != null) return typeface;
FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFontFromAsset(mgr, path)) {
+ 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();
}
}
}
@@ -259,6 +429,8 @@
fontFamily.freeze();
FontFamily[] families = { fontFamily };
return createFromFamiliesWithDefault(families);
+ } else {
+ fontFamily.abortCreation();
}
}
throw new RuntimeException("Font not found " + path);
@@ -306,25 +478,25 @@
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();
@@ -342,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));
}
}
@@ -359,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.
@@ -372,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;
@@ -453,8 +625,6 @@
}
private static native long nativeCreateFromTypeface(long native_instance, int style);
- private static native long nativeCreateFromTypefaceWithVariation(
- long native_instance, List<FontListParser.Axis> axes);
private static native long nativeCreateWeightAlias(long native_instance, int weight);
private static native void nativeUnref(long native_instance);
private static native int nativeGetStyle(long native_instance);
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 6ddc2d7..850f40e 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1320,7 +1320,7 @@
* provide an appropriate Resources object.
*
* @return a new drawable object based on this constant state
- * @see {@link #newDrawable(Resources)}
+ * @see #newDrawable(Resources)
*/
public abstract @NonNull Drawable newDrawable();
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 23de416..2b4e6c2 100644
--- a/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
+++ b/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
@@ -17,101 +17,103 @@
package android.graphics;
import android.test.suitebuilder.annotation.SmallTest;
-import java.util.List;
+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');
- List<FontListParser.Axis> axes = FontListParser.parseFontVariationSettings("'wdth' 1");
- assertEquals(tag, axes.get(0).tag);
- assertEquals(1.0f, axes.get(0).styleValue);
+ FontConfig.Axis[] axis = FontListParser.parseFontVariationSettings("'wdth' 1");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
- axes = FontListParser.parseFontVariationSettings("\"wdth\" 100");
- assertEquals(tag, axes.get(0).tag);
- assertEquals(100.0f, axes.get(0).styleValue);
+ axis = FontListParser.parseFontVariationSettings("\"wdth\" 100");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(100.0f, axis[0].getStyleValue());
- axes = FontListParser.parseFontVariationSettings(" 'wdth' 100");
- assertEquals(tag, axes.get(0).tag);
- assertEquals(100.0f, axes.get(0).styleValue);
+ axis = FontListParser.parseFontVariationSettings(" 'wdth' 100");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(100.0f, axis[0].getStyleValue());
- axes = FontListParser.parseFontVariationSettings("\t'wdth' 0.5");
- assertEquals(tag, axes.get(0).tag);
- assertEquals(0.5f, axes.get(0).styleValue);
+ axis = FontListParser.parseFontVariationSettings("\t'wdth' 0.5");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(0.5f, axis[0].getStyleValue());
tag = FontListParser.makeTag('A', 'X', ' ', ' ');
- axes = FontListParser.parseFontVariationSettings("'AX ' 1");
- assertEquals(tag, axes.get(0).tag);
- assertEquals(1.0f, axes.get(0).styleValue);
+ axis = FontListParser.parseFontVariationSettings("'AX ' 1");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
- axes = FontListParser.parseFontVariationSettings("'AX '\t1");
- assertEquals(tag, axes.get(0).tag);
- assertEquals(1.0f, axes.get(0).styleValue);
+ axis = FontListParser.parseFontVariationSettings("'AX '\t1");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
- axes = FontListParser.parseFontVariationSettings("'AX '\n1");
- assertEquals(tag, axes.get(0).tag);
- assertEquals(1.0f, axes.get(0).styleValue);
+ axis = FontListParser.parseFontVariationSettings("'AX '\n1");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
- axes = FontListParser.parseFontVariationSettings("'AX '\r1");
- assertEquals(tag, axes.get(0).tag);
- assertEquals(1.0f, axes.get(0).styleValue);
+ axis = FontListParser.parseFontVariationSettings("'AX '\r1");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
- axes = FontListParser.parseFontVariationSettings("'AX '\r\t\n 1");
- assertEquals(tag, axes.get(0).tag);
- assertEquals(1.0f, axes.get(0).styleValue);
+ axis = FontListParser.parseFontVariationSettings("'AX '\r\t\n 1");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
// Test for invalid input
- axes = FontListParser.parseFontVariationSettings("");
- assertEquals(0, axes.size());
- axes = FontListParser.parseFontVariationSettings("invalid_form");
- assertEquals(0, axes.size());
+ axis = FontListParser.parseFontVariationSettings("");
+ assertEquals(0, axis.length);
+ axis = FontListParser.parseFontVariationSettings("invalid_form");
+ assertEquals(0, axis.length);
// Test with invalid tag
- axes = FontListParser.parseFontVariationSettings("'' 1");
- assertEquals(0, axes.size());
- axes = FontListParser.parseFontVariationSettings("'invalid' 1");
- assertEquals(0, axes.size());
+ axis = FontListParser.parseFontVariationSettings("'' 1");
+ assertEquals(0, axis.length);
+ axis = FontListParser.parseFontVariationSettings("'invalid' 1");
+ assertEquals(0, axis.length);
// Test with invalid styleValue
- axes = FontListParser.parseFontVariationSettings("'wdth' ");
- assertEquals(0, axes.size());
- axes = FontListParser.parseFontVariationSettings("'wdth' x");
- assertEquals(0, axes.size());
- axes = FontListParser.parseFontVariationSettings("'wdth' \t");
- assertEquals(0, axes.size());
- axes = FontListParser.parseFontVariationSettings("'wdth' \n\r");
- assertEquals(0, axes.size());
+ axis = FontListParser.parseFontVariationSettings("'wdth' ");
+ assertEquals(0, axis.length);
+ axis = FontListParser.parseFontVariationSettings("'wdth' x");
+ assertEquals(0, axis.length);
+ axis = FontListParser.parseFontVariationSettings("'wdth' \t");
+ assertEquals(0, axis.length);
+ axis = FontListParser.parseFontVariationSettings("'wdth' \n\r");
+ assertEquals(0, axis.length);
}
@SmallTest
public void testParseFontVariationStyleSettings() {
- List<FontListParser.Axis> axes =
+ 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, axes.get(0).tag);
- assertEquals(10.0f, axes.get(0).styleValue);
- assertEquals(tag2, axes.get(1).tag);
- assertEquals(1.0f, axes.get(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
- axes = FontListParser.parseFontVariationSettings(" 'wdth' 10,ab'wdth' 1");
+ axis = FontListParser.parseFontVariationSettings(" 'wdth' 10,ab'wdth' 1");
tag1 = FontListParser.makeTag('w', 'd', 't', 'h');
- assertEquals(tag1, axes.get(0).tag);
- assertEquals(10.0f, axes.get(0).styleValue);
- assertEquals(1, axes.size());
+ assertEquals(tag1, axis[0].getTag());
+ assertEquals(10.0f, axis[0].getStyleValue());
+ assertEquals(1, axis.length);
}
@SmallTest
public void testInvalidTagCharacters() {
- List<FontListParser.Axis> axes =
+ FontConfig.Axis[] axis =
FontListParser.parseFontVariationSettings("'\u0000\u0000\u0000\u0000' 10");
- assertEquals(0, axes.size());
- axes = FontListParser.parseFontVariationSettings("'\u3042\u3044\u3046\u3048' 10");
- assertEquals(0, axes.size());
+ assertEquals(0, axis.length);
+ axis = FontListParser.parseFontVariationSettings("'\u3042\u3044\u3046\u3048' 10");
+ assertEquals(0, axis.length);
}
@SmallTest
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/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index d6b1cf1..bf7a779a 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -45,6 +45,7 @@
PURPOSE_DECRYPT,
PURPOSE_SIGN,
PURPOSE_VERIFY,
+ PURPOSE_WRAP_KEY,
})
public @interface PurposeEnum {}
@@ -69,6 +70,12 @@
public static final int PURPOSE_VERIFY = 1 << 3;
/**
+ * Purpose of key: wrapping key for secure key import.
+ */
+ public static final int PURPOSE_WRAP_KEY = 1 << 4;
+
+
+ /**
* @hide
*/
public static abstract class Purpose {
@@ -84,6 +91,8 @@
return KeymasterDefs.KM_PURPOSE_SIGN;
case PURPOSE_VERIFY:
return KeymasterDefs.KM_PURPOSE_VERIFY;
+ case PURPOSE_WRAP_KEY:
+ return KeymasterDefs.KM_PURPOSE_WRAP_KEY;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
@@ -99,6 +108,8 @@
return PURPOSE_SIGN;
case KeymasterDefs.KM_PURPOSE_VERIFY:
return PURPOSE_VERIFY;
+ case KeymasterDefs.KM_PURPOSE_WRAP_KEY:
+ return PURPOSE_WRAP_KEY;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
diff --git a/libs/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 a30c849..763a178 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1907,6 +1907,8 @@
if (diff != 0) return diff;
diff = (int32_t)(screenLayout2 - o.screenLayout2);
if (diff != 0) return diff;
+ diff = (int32_t)(colorMode - o.colorMode);
+ if (diff != 0) return diff;
diff = (int32_t)(uiMode - o.uiMode);
if (diff != 0) return diff;
diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp);
@@ -1967,6 +1969,9 @@
if (screenLayout2 != o.screenLayout2) {
return screenLayout2 < o.screenLayout2 ? -1 : 1;
}
+ if (colorMode != o.colorMode) {
+ return colorMode < o.colorMode ? -1 : 1;
+ }
if (uiMode != o.uiMode) {
return uiMode < o.uiMode ? -1 : 1;
}
@@ -1992,6 +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 ((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;
@@ -2103,6 +2110,17 @@
}
}
+ 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 (((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;
+ }
+ }
+
if (orientation != o.orientation) {
if (!orientation) return false;
if (!o.orientation) return true;
@@ -2390,6 +2408,17 @@
}
}
+ 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 (((colorMode^o.colorMode) & MASK_HDR) != 0 &&
+ (requested->colorMode & MASK_HDR)) {
+ return colorMode & MASK_HDR;
+ }
+ }
+
if ((orientation != o.orientation) && requested->orientation) {
return (orientation);
}
@@ -2639,6 +2668,18 @@
if (screenRound != 0 && screenRound != setScreenRound) {
return false;
}
+
+ const int hdr = colorMode & MASK_HDR;
+ const int setHdr = settings.colorMode & MASK_HDR;
+ if (hdr != 0 && hdr != setHdr) {
+ return false;
+ }
+
+ const int wideColorGamut = colorMode & MASK_WIDE_COLOR_GAMUT;
+ const int setWideColorGamut = settings.colorMode & MASK_WIDE_COLOR_GAMUT;
+ if (wideColorGamut != 0 && wideColorGamut != setWideColorGamut) {
+ return false;
+ }
}
if (screenSizeDp != 0) {
@@ -2959,6 +3000,34 @@
break;
}
}
+ if ((colorMode&MASK_HDR) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (colorMode&MASK_HDR) {
+ case ResTable_config::HDR_NO:
+ res.append("lowdr");
+ break;
+ case ResTable_config::HDR_YES:
+ res.append("highdr");
+ break;
+ default:
+ res.appendFormat("hdr=%d", dtohs(colorMode&MASK_HDR));
+ break;
+ }
+ }
+ if ((colorMode&MASK_WIDE_COLOR_GAMUT) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (colorMode&MASK_WIDE_COLOR_GAMUT) {
+ case ResTable_config::WIDE_COLOR_GAMUT_NO:
+ res.append("nowidecg");
+ break;
+ case ResTable_config::WIDE_COLOR_GAMUT_YES:
+ res.append("widecg");
+ break;
+ default:
+ res.appendFormat("wideColorGamut=%d", dtohs(colorMode&MASK_WIDE_COLOR_GAMUT));
+ break;
+ }
+ }
if (orientation != ORIENTATION_ANY) {
if (res.size() > 0) res.append("-");
switch (orientation) {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index c118b57..86ab123 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1146,11 +1146,26 @@
SCREENROUND_YES = ACONFIGURATION_SCREENROUND_YES,
};
+ enum {
+ // 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,
+
+ // colorMode bits for HDR/LDR.
+ MASK_HDR = 0x0c,
+ 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 screenConfigPad1; // Reserved padding.
+ uint8_t colorMode; // Wide-gamut, HDR, etc.
uint16_t screenConfigPad2; // Reserved padding.
};
uint32_t screenConfig2;
@@ -1193,6 +1208,7 @@
CONFIG_UI_MODE = ACONFIGURATION_UI_MODE,
CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR,
CONFIG_SCREEN_ROUND = ACONFIGURATION_SCREEN_ROUND,
+ CONFIG_COLOR_MODE = ACONFIGURATION_COLOR_MODE,
};
// Compare two configuration, returning CONFIG_* flags set for each value
diff --git a/tools/aapt2/util/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h
similarity index 66%
rename from tools/aapt2/util/StringPiece.h
rename to libs/androidfw/include/androidfw/StringPiece.h
index 5144b1f..c9effd1 100644
--- a/tools/aapt2/util/StringPiece.h
+++ b/libs/androidfw/include/androidfw/StringPiece.h
@@ -14,26 +14,24 @@
* limitations under the License.
*/
-#ifndef AAPT_STRING_PIECE_H
-#define AAPT_STRING_PIECE_H
+#ifndef ANDROIDFW_STRING_PIECE_H
+#define ANDROIDFW_STRING_PIECE_H
#include <ostream>
#include <string>
#include "utils/JenkinsHash.h"
-#include "utils/String8.h"
#include "utils/Unicode.h"
-namespace aapt {
+namespace android {
-/**
- * Read only wrapper around basic C strings.
- * Prevents excessive copying.
- *
- * WARNING: When creating from std::basic_string<>, moving the original
- * std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
- * BasicStringPiece<> should only be used transitively.
- */
+// Read only wrapper around basic C strings. Prevents excessive copying.
+// StringPiece does not own the data it is wrapping. The lifetime of the underlying
+// data must outlive this StringPiece.
+//
+// WARNING: When creating from std::basic_string<>, moving the original
+// std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
+// BasicStringPiece<> should only be used transitively.
template <typename TChar>
class BasicStringPiece {
public:
@@ -53,15 +51,14 @@
BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const;
- BasicStringPiece<TChar> substr(
- BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const;
+ BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
+ BasicStringPiece<TChar>::const_iterator end) const;
const TChar* data() const;
size_t length() const;
size_t size() const;
bool empty() const;
- std::basic_string<TChar> ToString() const;
+ std::basic_string<TChar> to_string() const;
bool contains(const BasicStringPiece<TChar>& rhs) const;
int compare(const BasicStringPiece<TChar>& rhs) const;
@@ -89,17 +86,14 @@
constexpr const size_t BasicStringPiece<TChar>::npos;
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece()
- : data_(nullptr), length_(0) {}
+inline BasicStringPiece<TChar>::BasicStringPiece() : data_(nullptr), length_(0) {}
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(
- const BasicStringPiece<TChar>& str)
+inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str)
: data_(str.data_), length_(str.length_) {}
template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(
- const std::basic_string<TChar>& str)
+inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str)
: data_(str.data()), length_(str.length()) {}
template <>
@@ -123,16 +117,14 @@
}
template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(
- const TChar* str, size_t len) {
+inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
data_ = str;
length_ = len;
return *this;
}
template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
- size_t start, size_t len) const {
+inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
if (len == npos) {
len = length_ - start;
}
@@ -171,13 +163,12 @@
}
template <typename TChar>
-inline std::basic_string<TChar> BasicStringPiece<TChar>::ToString() const {
+inline std::basic_string<TChar> BasicStringPiece<TChar>::to_string() const {
return std::basic_string<TChar>(data_, length_);
}
template <>
-inline bool BasicStringPiece<char>::contains(
- const BasicStringPiece<char>& rhs) const {
+inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const {
if (!data_ || !rhs.data_) {
return false;
}
@@ -188,8 +179,7 @@
}
template <>
-inline int BasicStringPiece<char>::compare(
- const BasicStringPiece<char>& rhs) const {
+inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
const char nullStr = '\0';
const char* b1 = data_ != nullptr ? data_ : &nullStr;
const char* e1 = b1 + length_;
@@ -205,15 +195,21 @@
return static_cast<int>(length_ - rhs.length_);
}
-inline ::std::ostream& operator<<(::std::ostream& out,
- const BasicStringPiece<char16_t>& str) {
- android::String8 utf8(str.data(), str.size());
- return out.write(utf8.string(), utf8.size());
+inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
+ const ssize_t result_len = utf16_to_utf8_length(str.data(), str.size());
+ if (result_len < 0) {
+ // Empty string.
+ return out;
+ }
+
+ std::string result;
+ result.resize(static_cast<size_t>(result_len));
+ utf16_to_utf8(str.data(), str.length(), &*result.begin(), static_cast<size_t>(result_len) + 1);
+ return out << result;
}
template <>
-inline bool BasicStringPiece<char16_t>::contains(
- const BasicStringPiece<char16_t>& rhs) const {
+inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const {
if (!data_ || !rhs.data_) {
return false;
}
@@ -224,8 +220,7 @@
}
template <>
-inline int BasicStringPiece<char16_t>::compare(
- const BasicStringPiece<char16_t>& rhs) const {
+inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
const char16_t nullStr = u'\0';
const char16_t* b1 = data_ != nullptr ? data_ : &nullStr;
const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
@@ -233,66 +228,52 @@
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator<(
- const BasicStringPiece<TChar>& rhs) const {
+inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
return compare(rhs) < 0;
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator>(
- const BasicStringPiece<TChar>& rhs) const {
+inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
return compare(rhs) > 0;
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator==(
- const BasicStringPiece<TChar>& rhs) const {
+inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
return compare(rhs) == 0;
}
template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator!=(
- const BasicStringPiece<TChar>& rhs) const {
+inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
return compare(rhs) != 0;
}
template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator
-BasicStringPiece<TChar>::begin() const {
+inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
return data_;
}
template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator
-BasicStringPiece<TChar>::end() const {
+inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
return data_ + length_;
}
-inline ::std::ostream& operator<<(::std::ostream& out,
- const BasicStringPiece<char>& str) {
+inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
return out.write(str.data(), str.size());
}
-} // namespace aapt
-
-inline ::std::ostream& operator<<(::std::ostream& out,
- const std::u16string& str) {
- android::String8 utf8(str.data(), str.size());
- return out.write(utf8.string(), utf8.size());
-}
+} // namespace android
namespace std {
template <typename TChar>
-struct hash<aapt::BasicStringPiece<TChar>> {
- size_t operator()(const aapt::BasicStringPiece<TChar>& str) const {
+struct hash<android::BasicStringPiece<TChar>> {
+ size_t operator()(const android::BasicStringPiece<TChar>& str) const {
uint32_t hashCode = android::JenkinsHashMixBytes(
- 0, reinterpret_cast<const uint8_t*>(str.data()),
- sizeof(TChar) * str.size());
+ 0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size());
return static_cast<size_t>(hashCode);
}
};
} // namespace std
-#endif // AAPT_STRING_PIECE_H
+#endif // ANDROIDFW_STRING_PIECE_H
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index 6754cd8..650f813 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -34,6 +34,7 @@
LoadedArsc_test.cpp \
ResTable_test.cpp \
Split_test.cpp \
+ StringPiece_test.cpp \
TestHelpers.cpp \
TestMain.cpp \
Theme_test.cpp \
diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp
index 778c7bf..b54915f 100644
--- a/libs/androidfw/tests/Config_test.cpp
+++ b/libs/androidfw/tests/Config_test.cpp
@@ -182,4 +182,24 @@
EXPECT_TRUE(targetConfigC.isBetterThan(targetConfigB, &deviceConfig));
}
+TEST(ConfigTest, ScreenIsWideGamut) {
+ ResTable_config defaultConfig;
+ memset(&defaultConfig, 0, sizeof(defaultConfig));
+
+ ResTable_config wideGamutConfig = defaultConfig;
+ wideGamutConfig.colorMode = ResTable_config::WIDE_COLOR_GAMUT_YES;
+
+ EXPECT_EQ(defaultConfig.diff(wideGamutConfig), ResTable_config::CONFIG_COLOR_MODE);
+}
+
+TEST(ConfigTest, ScreenIsHdr) {
+ ResTable_config defaultConfig;
+ memset(&defaultConfig, 0, sizeof(defaultConfig));
+
+ ResTable_config hdrConfig = defaultConfig;
+ hdrConfig.colorMode = ResTable_config::HDR_YES;
+
+ EXPECT_EQ(defaultConfig.diff(hdrConfig), ResTable_config::CONFIG_COLOR_MODE);
+}
+
} // namespace android.
diff --git a/tools/aapt2/util/StringPiece_test.cpp b/libs/androidfw/tests/StringPiece_test.cpp
similarity index 96%
rename from tools/aapt2/util/StringPiece_test.cpp
rename to libs/androidfw/tests/StringPiece_test.cpp
index 048961d..316a5c1 100644
--- a/tools/aapt2/util/StringPiece_test.cpp
+++ b/libs/androidfw/tests/StringPiece_test.cpp
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-#include "util/StringPiece.h"
+#include "androidfw/StringPiece.h"
#include <algorithm>
#include <string>
#include <vector>
-#include "test/Test.h"
+#include "TestHelpers.h"
-namespace aapt {
+namespace android {
TEST(StringPieceTest, CompareNonNullTerminatedPiece) {
StringPiece a("hello world", 5);
@@ -92,4 +92,4 @@
EXPECT_FALSE(text16.contains(long_needle16));
}
-} // namespace aapt
+} // namespace android
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8f7787b..138a5ef 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -7,7 +7,7 @@
# Enables fine-grained GLES error checking
# If set to true, every GLES call is wrapped & error checked
# Has moderate overhead
-HWUI_ENABLE_OPENGL_VALIDATION := false
+HWUI_ENABLE_OPENGL_VALIDATION := true
hwui_src_files := \
hwui/Bitmap.cpp \
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 5e4a7f7..3853356 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -104,15 +104,15 @@
}
}
-bool DisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
- std::function<void(RenderNode*, TreeInfo&, bool)> childFn) {
+bool DisplayList::prepareListAndChildren(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
info.prepareTextures = info.canvasContext.pinImages(bitmapResources);
for (auto&& op : children) {
RenderNode* childNode = op->renderNode;
info.damageAccumulator->pushTransform(&op->localMatrix);
bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
- childFn(childNode, info, childFunctorsNeedLayer);
+ childFn(childNode, observer, info, childFunctorsNeedLayer);
info.damageAccumulator->popTransform();
}
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index cab092f..ef0fd31 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -125,8 +125,8 @@
virtual void syncContents();
virtual void updateChildren(std::function<void(RenderNode*)> updateFn);
- virtual bool prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
- std::function<void(RenderNode*, TreeInfo&, bool)> childFn);
+ virtual bool prepareListAndChildren(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn);
protected:
// allocator into which all ops and LsaVector arrays allocated
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/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index a5443d9..7d8f046 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -22,6 +22,7 @@
#include "OpDumper.h"
#include "RecordedOp.h"
#include "TreeInfo.h"
+#include "utils/FatVector.h"
#include "utils/MathUtils.h"
#include "utils/StringUtils.h"
#include "utils/TraceUtils.h"
@@ -39,6 +40,20 @@
namespace android {
namespace uirenderer {
+// Used for tree mutations that are purely destructive.
+// Generic tree mutations should use MarkAndSweepObserver instead
+class ImmediateRemoved : public TreeObserver {
+public:
+ explicit ImmediateRemoved(TreeInfo* info) : mTreeInfo(info) {}
+
+ void onMaybeRemovedFromTree(RenderNode* node) override {
+ node->onRemovedFromTree(mTreeInfo);
+ }
+
+private:
+ TreeInfo* mTreeInfo;
+};
+
RenderNode::RenderNode()
: mDirtyPropertyFields(0)
, mNeedsDisplayListSync(false)
@@ -49,20 +64,17 @@
}
RenderNode::~RenderNode() {
- deleteDisplayList(nullptr);
+ ImmediateRemoved observer(nullptr);
+ deleteDisplayList(observer);
delete mStagingDisplayList;
LOG_ALWAYS_FATAL_IF(hasLayer(), "layer missed detachment!");
}
-void RenderNode::setStagingDisplayList(DisplayList* displayList, TreeObserver* observer) {
+void RenderNode::setStagingDisplayList(DisplayList* displayList) {
+ mValid = (displayList != nullptr);
mNeedsDisplayListSync = true;
delete mStagingDisplayList;
mStagingDisplayList = displayList;
- // If mParentCount == 0 we are the sole reference to this RenderNode,
- // so immediately free the old display list
- if (!mParentCount && !mStagingDisplayList) {
- deleteDisplayList(observer);
- }
}
/**
@@ -187,12 +199,13 @@
void RenderNode::prepareTree(TreeInfo& info) {
ATRACE_CALL();
LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing");
+ MarkAndSweepRemoved observer(&info);
// The OpenGL renderer reserves the stencil buffer for overdraw debugging. Functors
// will need to be drawn in a layer.
bool functorsNeedLayer = Properties::debugOverdraw && !Properties::isSkiaEnabled();
- prepareTreeImpl(info, functorsNeedLayer);
+ prepareTreeImpl(observer, info, functorsNeedLayer);
}
void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
@@ -283,7 +296,7 @@
* While traversing down the tree, functorsNeedLayer flag is set to true if anything that uses the
* stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer.
*/
-void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
+void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
info.damageAccumulator->pushTransform(this);
if (info.mode == TreeInfo::MODE_FULL) {
@@ -309,14 +322,14 @@
prepareLayer(info, animatorDirtyMask);
if (info.mode == TreeInfo::MODE_FULL) {
- pushStagingDisplayListChanges(info);
+ pushStagingDisplayListChanges(observer, info);
}
if (mDisplayList) {
info.out.hasFunctors |= mDisplayList->hasFunctor();
- bool isDirty = mDisplayList->prepareListAndChildren(info, childFunctorsNeedLayer,
- [](RenderNode* child, TreeInfo& info, bool functorsNeedLayer) {
- child->prepareTreeImpl(info, functorsNeedLayer);
+ bool isDirty = mDisplayList->prepareListAndChildren(observer, info, childFunctorsNeedLayer,
+ [](RenderNode* child, TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
+ child->prepareTreeImpl(observer, info, functorsNeedLayer);
});
if (isDirty) {
damageSelf(info);
@@ -353,7 +366,7 @@
}
}
-void RenderNode::syncDisplayList(TreeInfo* info) {
+void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
// Make sure we inc first so that we don't fluctuate between 0 and 1,
// which would thrash the layer cache
if (mStagingDisplayList) {
@@ -361,7 +374,7 @@
child->incParentRefCount();
});
}
- deleteDisplayList(info ? info->observer : nullptr, info);
+ deleteDisplayList(observer, info);
mDisplayList = mStagingDisplayList;
mStagingDisplayList = nullptr;
if (mDisplayList) {
@@ -369,20 +382,20 @@
}
}
-void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
+void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
if (mNeedsDisplayListSync) {
mNeedsDisplayListSync = false;
// Damage with the old display list first then the new one to catch any
// changes in isRenderable or, in the future, bounds
damageSelf(info);
- syncDisplayList(&info);
+ syncDisplayList(observer, &info);
damageSelf(info);
}
}
-void RenderNode::deleteDisplayList(TreeObserver* observer, TreeInfo* info) {
+void RenderNode::deleteDisplayList(TreeObserver& observer, TreeInfo* info) {
if (mDisplayList) {
- mDisplayList->updateChildren([observer, info](RenderNode* child) {
+ mDisplayList->updateChildren([&observer, info](RenderNode* child) {
child->decParentRefCount(observer, info);
});
if (!mDisplayList->reuseDisplayList(this, info ? &info->canvasContext : nullptr)) {
@@ -392,38 +405,53 @@
mDisplayList = nullptr;
}
-void RenderNode::destroyHardwareResources(TreeObserver* observer, TreeInfo* info) {
+void RenderNode::destroyHardwareResources(TreeInfo* info) {
+ ImmediateRemoved observer(info);
+ destroyHardwareResourcesImpl(observer, info);
+}
+
+void RenderNode::destroyHardwareResourcesImpl(TreeObserver& observer, TreeInfo* info) {
if (hasLayer()) {
renderthread::CanvasContext::destroyLayer(this);
}
if (mDisplayList) {
- mDisplayList->updateChildren([observer, info](RenderNode* child) {
- child->destroyHardwareResources(observer, info);
+ mDisplayList->updateChildren([&observer, info](RenderNode* child) {
+ child->destroyHardwareResourcesImpl(observer, info);
});
- if (mNeedsDisplayListSync) {
- // Next prepare tree we are going to push a new display list, so we can
- // drop our current one now
- deleteDisplayList(observer, info);
+ setStagingDisplayList(nullptr);
+ deleteDisplayList(observer, info);
+ }
+}
+
+void RenderNode::destroyLayers() {
+ if (hasLayer()) {
+ renderthread::CanvasContext::destroyLayer(this);
+ }
+ if (mDisplayList) {
+ mDisplayList->updateChildren([](RenderNode* child) {
+ child->destroyLayers();
+ });
+ }
+}
+
+void RenderNode::decParentRefCount(TreeObserver& observer, TreeInfo* info) {
+ LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!");
+ mParentCount--;
+ if (!mParentCount) {
+ observer.onMaybeRemovedFromTree(this);
+ if (CC_UNLIKELY(mPositionListener.get())) {
+ mPositionListener->onPositionLost(*this, info);
}
}
}
-void RenderNode::decParentRefCount(TreeObserver* observer, TreeInfo* info) {
- LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!");
- mParentCount--;
- if (!mParentCount) {
- if (observer) {
- observer->onMaybeRemovedFromTree(this);
- }
- if (CC_UNLIKELY(mPositionListener.get())) {
- mPositionListener->onPositionLost(*this, info);
- }
- // If a child of ours is being attached to our parent then this will incorrectly
- // destroy its hardware resources. However, this situation is highly unlikely
- // and the failure is "just" that the layer is re-created, so this should
- // be safe enough
- destroyHardwareResources(observer, info);
- }
+void RenderNode::onRemovedFromTree(TreeInfo* info) {
+ destroyHardwareResources(info);
+}
+
+void RenderNode::clearRoot() {
+ ImmediateRemoved observer(nullptr);
+ decParentRefCount(observer);
}
/**
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index b8964f0..14a664c 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -34,6 +34,7 @@
#include "RenderProperties.h"
#include "pipeline/skia/SkiaDisplayList.h"
#include "pipeline/skia/SkiaLayer.h"
+#include "utils/FatVector.h"
#include <vector>
@@ -101,7 +102,7 @@
kReplayFlag_ClipChildren = 0x1
};
- ANDROID_API void setStagingDisplayList(DisplayList* newData, TreeObserver* observer);
+ ANDROID_API void setStagingDisplayList(DisplayList* newData);
void computeOrdering();
@@ -164,6 +165,10 @@
return mStagingProperties;
}
+ bool isValid() {
+ return mValid;
+ }
+
int getWidth() const {
return properties().getWidth();
}
@@ -173,7 +178,8 @@
}
ANDROID_API virtual void prepareTree(TreeInfo& info);
- void destroyHardwareResources(TreeObserver* observer, TreeInfo* info = nullptr);
+ void destroyHardwareResources(TreeInfo* info = nullptr);
+ void destroyLayers();
// UI thread only!
ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
@@ -232,24 +238,35 @@
return mParentCount;
}
+ void onRemovedFromTree(TreeInfo* info);
+
+ // Called by CanvasContext to promote a RenderNode to be a root node
+ void makeRoot() {
+ incParentRefCount();
+ }
+
+ // Called by CanvasContext when it drops a RenderNode from being a root node
+ void clearRoot();
+
private:
void computeOrderingImpl(RenderNodeOp* opState,
std::vector<RenderNodeOp*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface);
void syncProperties();
- void syncDisplayList(TreeInfo* info);
+ void syncDisplayList(TreeObserver& observer, TreeInfo* info);
- void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
+ void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer);
void pushStagingPropertiesChanges(TreeInfo& info);
- void pushStagingDisplayListChanges(TreeInfo& info);
+ void pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info);
void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
void pushLayerUpdate(TreeInfo& info);
- void deleteDisplayList(TreeObserver* observer, TreeInfo* info = nullptr);
+ void deleteDisplayList(TreeObserver& observer, TreeInfo* info = nullptr);
+ void destroyHardwareResourcesImpl(TreeObserver& observer, TreeInfo* info = nullptr);
void damageSelf(TreeInfo& info);
void incParentRefCount() { mParentCount++; }
- void decParentRefCount(TreeObserver* observer, TreeInfo* info = nullptr);
+ void decParentRefCount(TreeObserver& observer, TreeInfo* info = nullptr);
void output(std::ostream& output, uint32_t level);
String8 mName;
@@ -259,6 +276,10 @@
RenderProperties mProperties;
RenderProperties mStagingProperties;
+ // Owned by UI. Set when DL is set, cleared when DL cleared or when node detached
+ // (likely by parent re-record/removal)
+ bool mValid = false;
+
bool mNeedsDisplayListSync;
// WARNING: Do not delete this directly, you must go through deleteDisplayList()!
DisplayList* mDisplayList;
@@ -361,5 +382,28 @@
std::unique_ptr<skiapipeline::SkiaLayer> mSkiaLayer;
}; // class RenderNode
+class MarkAndSweepRemoved : public TreeObserver {
+PREVENT_COPY_AND_ASSIGN(MarkAndSweepRemoved);
+
+public:
+ explicit MarkAndSweepRemoved(TreeInfo* info) : mTreeInfo(info) {}
+
+ void onMaybeRemovedFromTree(RenderNode* node) override {
+ mMarked.emplace_back(node);
+ }
+
+ ~MarkAndSweepRemoved() {
+ for (auto& node : mMarked) {
+ if (!node->hasParents()) {
+ node->onRemovedFromTree(mTreeInfo);
+ }
+ }
+ }
+
+private:
+ FatVector<sp<RenderNode>, 10> mMarked;
+ TreeInfo* mTreeInfo;
+};
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 89e2a01..e54bc36 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -367,7 +367,7 @@
// (see https://code.google.com/p/skia/issues/detail?id=1303)
bool SkiaCanvas::getClipBounds(SkRect* outRect) const {
SkIRect ibounds;
- if (!mCanvas->getClipDeviceBounds(&ibounds)) {
+ if (!mCanvas->getDeviceClipBounds(&ibounds)) {
return false;
}
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 749efdd..c6fbe2b 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -47,9 +47,9 @@
// Due to the unordered nature of tree pushes, once prepareTree
// is finished it is possible that the node was "resurrected" and has
// a non-zero parent count.
- virtual void onMaybeRemovedFromTree(RenderNode* node) {}
+ virtual void onMaybeRemovedFromTree(RenderNode* node) = 0;
protected:
- ~TreeObserver() {}
+ virtual ~TreeObserver() {}
};
// This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN
@@ -91,10 +91,6 @@
LayerUpdateQueue* layerUpdateQueue = nullptr;
ErrorHandler* errorHandler = nullptr;
- // Optional, may be nullptr. Used to allow things to observe interesting
- // tree state changes
- TreeObserver* observer = nullptr;
-
int32_t windowInsetLeft = 0;
int32_t windowInsetTop = 0;
bool updateWindowPositions = false;
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 6a00379..956f66e 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -17,8 +17,7 @@
#include "MinikinSkia.h"
#include <log/log.h>
-#include <SkFontDescriptor.h>
-#include <SkFontMgr.h>
+
#include <SkPaint.h>
#include <SkTypeface.h>
@@ -87,28 +86,6 @@
return mTtcIndex;
}
-minikin::MinikinFont* MinikinFontSkia::createFontWithVariation(
- const std::vector<minikin::FontVariation>& variations) const {
- SkFontMgr::FontParameters params;
-
- int ttcIndex;
- SkStreamAsset* stream = mTypeface->openStream(&ttcIndex);
- LOG_ALWAYS_FATAL_IF(stream == nullptr, "openStream failed");
-
- params.setCollectionIndex(ttcIndex);
- std::vector<SkFontMgr::FontParameters::Axis> skAxes;
- skAxes.resize(variations.size());
- for (size_t i = 0; i < variations.size(); i++) {
- skAxes[i].fTag = variations[i].axisTag;
- skAxes[i].fStyleValue = SkFloatToScalar(variations[i].value);
- }
- params.setAxes(skAxes.data(), skAxes.size());
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
- sk_sp<SkTypeface> face(fm->createFromStream(stream, params));
-
- return new MinikinFontSkia(std::move(face), mFontData, mFontSize, ttcIndex);
-}
-
uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) {
uint32_t flags = paint->getFlags();
SkPaint::Hinting hinting = paint->getHinting();
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index 249b0cb..3ee916c 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -44,8 +44,6 @@
const void* GetFontData() const;
size_t GetFontSize() const;
int GetFontIndex() const;
- minikin::MinikinFont* createFontWithVariation(
- const std::vector<minikin::FontVariation>&) const;
static uint32_t packPaintFlags(const SkPaint* paint);
static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags);
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index b69b0cb..9041b44 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -62,7 +62,7 @@
Typeface* Typeface::createFromTypeface(Typeface* src, SkTypeface::Style style) {
Typeface* resolvedFace = Typeface::resolveDefault(src);
Typeface* result = new Typeface;
- if (result != nullptr) {
+ if (result != 0) {
result->fFontCollection = resolvedFace->fFontCollection;
result->fFontCollection->Ref();
result->fSkiaStyle = style;
@@ -72,30 +72,10 @@
return result;
}
-Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src,
- const std::vector<minikin::FontVariation>& variations) {
- Typeface* resolvedFace = Typeface::resolveDefault(src);
- Typeface* result = new Typeface();
- if (result != nullptr) {
- result->fFontCollection =
- resolvedFace->fFontCollection->createCollectionWithVariation(variations);
- if (result->fFontCollection == nullptr) {
- // None of passed axes are supported by this collection.
- // So we will reuse the same collection with incrementing reference count.
- result->fFontCollection = resolvedFace->fFontCollection;
- result->fFontCollection->Ref();
- }
- result->fSkiaStyle = resolvedFace->fSkiaStyle;
- result->fBaseWeight = resolvedFace->fBaseWeight;
- resolveStyle(result);
- }
- return result;
-}
-
Typeface* Typeface::createWeightAlias(Typeface* src, int weight) {
Typeface* resolvedFace = Typeface::resolveDefault(src);
Typeface* result = new Typeface;
- if (result != nullptr) {
+ if (result != 0) {
result->fFontCollection = resolvedFace->fFontCollection;
result->fFontCollection->Ref();
result->fSkiaStyle = resolvedFace->fSkiaStyle;
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 4392ebc..1be630c 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -43,9 +43,6 @@
static Typeface* createFromTypeface(Typeface* src, SkTypeface::Style style);
- static Typeface* createFromTypefaceWithVariation(Typeface* src,
- const std::vector<minikin::FontVariation>& variations);
-
static Typeface* createWeightAlias(Typeface* src, int baseweight);
static Typeface* createFromFamilies(const std::vector<minikin::FontFamily*>& families);
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 419c8a9..ea302a1 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -19,7 +19,6 @@
#include "RenderNode.h"
#include "SkClipStack.h"
#include <private/hwui/DrawGlInfo.h>
-#include <SkPath.h>
#include <GrContext.h>
namespace android {
@@ -60,8 +59,7 @@
SkImageInfo canvasInfo = canvas->imageInfo();
SkMatrix44 mat4(canvas->getTotalMatrix());
- SkIRect ibounds;
- canvas->getClipDeviceBounds(&ibounds);
+ SkIRect ibounds = canvas->getDeviceClipBounds();
DrawGlInfo info;
info.clipLeft = ibounds.fLeft;
@@ -76,9 +74,7 @@
//apply a simple clip with a scissor or a complex clip with a stencil
SkRegion clipRegion;
- SkPath path;
- canvas->getClipStack()->asPath(&path);
- clipRegion.setPath(path, SkRegion(ibounds));
+ canvas->temporary_internal_getRgnClip(&clipRegion);
if (CC_UNLIKELY(clipRegion.isComplex())) {
//It is only a temporary solution to use a scissor to draw the stencil.
//There is a bug 31489986 to implement efficiently non-rectangular clips.
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.h b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
index bf39dad..012c948 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.h
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
@@ -37,9 +37,9 @@
public:
GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas)
: mFunctor(functor)
- , mListener(listener) {
- canvas->getClipBounds(&mBounds);
- }
+ , mListener(listener)
+ , mBounds(canvas->getLocalClipBounds())
+ {}
virtual ~GLFunctorDrawable();
void syncFunctor() const;
@@ -51,7 +51,7 @@
private:
Functor* mFunctor;
sp<GlFunctorLifecycleListener> mListener;
- SkRect mBounds;
+ const SkRect mBounds;
};
}; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 9db8cd3..36d02ecb 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -51,8 +51,9 @@
}
}
-bool SkiaDisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
- std::function<void(RenderNode*, TreeInfo&, bool)> childFn) {
+bool SkiaDisplayList::prepareListAndChildren(TreeObserver& observer, TreeInfo& info,
+ bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
// If the prepare tree is triggered by the UI thread and no previous call to
// pinImages has failed then we must pin all mutable images in the GPU cache
// until the next UI thread draw.
@@ -74,7 +75,7 @@
info.damageAccumulator->pushTransform(&mat4);
// TODO: a layer is needed if the canvas is rotated or has a non-rect clip
info.hasBackwardProjectedNodes = false;
- childFn(childNode, info, functorsNeedLayer);
+ childFn(childNode, observer, info, functorsNeedLayer);
hasBackwardProjectedNodesSubtree |= info.hasBackwardProjectedNodes;
info.damageAccumulator->popTransform();
}
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index ff86fd1..2a01330 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -113,8 +113,8 @@
* to subclass from DisplayList
*/
- bool prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
- std::function<void(RenderNode*, TreeInfo&, bool)> childFn) override;
+ bool prepareListAndChildren(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) override;
/**
* Calls the provided function once for each child of this DisplayList
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1b3bf96..a53e5e0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -146,21 +146,38 @@
, mProfiler(mFrames)
, mContentDrawBounds(0, 0, 0, 0)
, mRenderPipeline(std::move(renderPipeline)) {
+ rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
mRenderThread.renderState().registerCanvasContext(this);
mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
}
CanvasContext::~CanvasContext() {
- destroy(nullptr);
+ destroy();
mRenderThread.renderState().unregisterCanvasContext(this);
+ for (auto& node : mRenderNodes) {
+ node->clearRoot();
+ }
+ mRenderNodes.clear();
}
-void CanvasContext::destroy(TreeObserver* observer) {
+void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
+ int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
+ node->makeRoot();
+ mRenderNodes.emplace(mRenderNodes.begin() + pos, node);
+}
+
+void CanvasContext::removeRenderNode(RenderNode* node) {
+ node->clearRoot();
+ mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node),
+ mRenderNodes.end());
+}
+
+void CanvasContext::destroy() {
stopDrawing();
setSurface(nullptr);
- freePrefetchedLayers(observer);
- destroyHardwareResources(observer);
+ freePrefetchedLayers();
+ destroyHardwareResources();
mAnimationContext->destroy();
}
@@ -320,7 +337,7 @@
mAnimationContext->runRemainingAnimations(info);
GL_CHECKPOINT(MODERATE);
- freePrefetchedLayers(info.observer);
+ freePrefetchedLayers();
GL_CHECKPOINT(MODERATE);
mIsDirty = true;
@@ -504,19 +521,19 @@
}
}
-void CanvasContext::freePrefetchedLayers(TreeObserver* observer) {
+void CanvasContext::freePrefetchedLayers() {
if (mPrefetchedLayers.size()) {
for (auto& node : mPrefetchedLayers) {
ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...",
node->getName());
- node->destroyHardwareResources(observer);
- node->decStrong(observer);
+ node->destroyLayers();
+ node->decStrong(nullptr);
}
mPrefetchedLayers.clear();
}
}
-void CanvasContext::buildLayer(RenderNode* node, TreeObserver* observer) {
+void CanvasContext::buildLayer(RenderNode* node) {
ATRACE_CALL();
if (!mRenderPipeline->isContextReady()) return;
@@ -525,7 +542,6 @@
TreeInfo info(TreeInfo::MODE_FULL, *this);
info.damageAccumulator = &mDamageAccumulator;
- info.observer = observer;
info.layerUpdateQueue = &mLayerUpdateQueue;
info.runAnimations = false;
node->prepareTree(info);
@@ -545,12 +561,12 @@
return mRenderPipeline->copyLayerInto(layer, bitmap);
}
-void CanvasContext::destroyHardwareResources(TreeObserver* observer) {
+void CanvasContext::destroyHardwareResources() {
stopDrawing();
if (mRenderPipeline->isContextReady()) {
- freePrefetchedLayers(observer);
+ freePrefetchedLayers();
for (const sp<RenderNode>& node : mRenderNodes) {
- node->destroyHardwareResources(observer);
+ node->destroyHardwareResources();
}
mRenderPipeline->onDestroyHardwareResources();
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 0174b86..aa01caa 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -132,17 +132,17 @@
void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
int64_t syncQueued, RenderNode* target);
void draw();
- void destroy(TreeObserver* observer);
+ void destroy();
// IFrameCallback, Choreographer-driven frame callback entry point
virtual void doFrame() override;
void prepareAndDraw(RenderNode* node);
- void buildLayer(RenderNode* node, TreeObserver* observer);
+ void buildLayer(RenderNode* node);
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
void markLayerInUse(RenderNode* node);
- void destroyHardwareResources(TreeObserver* observer);
+ void destroyHardwareResources();
static void trimMemory(RenderThread& thread, int level);
DeferredLayerUpdater* createTextureLayer();
@@ -160,15 +160,8 @@
void serializeDisplayListTree();
- void addRenderNode(RenderNode* node, bool placeFront) {
- int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
- mRenderNodes.emplace(mRenderNodes.begin() + pos, node);
- }
-
- void removeRenderNode(RenderNode* node) {
- mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node),
- mRenderNodes.end());
- }
+ void addRenderNode(RenderNode* node, bool placeFront);
+ void removeRenderNode(RenderNode* node);
void setContentDrawBounds(int left, int top, int right, int bottom) {
mContentDrawBounds.set(left, top, right, bottom);
@@ -213,7 +206,7 @@
void setSurface(Surface* window);
- void freePrefetchedLayers(TreeObserver* observer);
+ void freePrefetchedLayers();
bool isSwapChainStuffed();
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 4ff54a5..7d641d3 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -65,12 +65,11 @@
}
}
-int DrawFrameTask::drawFrame(TreeObserver* observer) {
+int DrawFrameTask::drawFrame() {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
mSyncResult = SyncResult::OK;
mSyncQueued = systemTime(CLOCK_MONOTONIC);
- mObserver = observer;
postAndWait();
return mSyncResult;
@@ -89,7 +88,6 @@
bool canDrawThisFrame;
{
TreeInfo info(TreeInfo::MODE_FULL, *mContext);
- info.observer = mObserver;
canUnblockUiThread = syncFrameState(info);
canDrawThisFrame = info.out.canDrawThisFrame;
}
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index c02d376..fb48062 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -65,7 +65,7 @@
void pushLayerUpdate(DeferredLayerUpdater* layer);
void removeLayerUpdate(DeferredLayerUpdater* layer);
- int drawFrame(TreeObserver* observer);
+ int drawFrame();
int64_t* frameInfo() { return mFrameInfo; }
@@ -90,7 +90,6 @@
int mSyncResult;
int64_t mSyncQueued;
- TreeObserver* mObserver;
int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
};
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 022e871..fb79272 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -230,19 +230,18 @@
return mDrawFrameTask.frameInfo();
}
-int RenderProxy::syncAndDrawFrame(TreeObserver* observer) {
- return mDrawFrameTask.drawFrame(observer);
+int RenderProxy::syncAndDrawFrame() {
+ return mDrawFrameTask.drawFrame();
}
-CREATE_BRIDGE2(destroy, CanvasContext* context, TreeObserver* observer) {
- args->context->destroy(args->observer);
+CREATE_BRIDGE1(destroy, CanvasContext* context) {
+ args->context->destroy();
return nullptr;
}
-void RenderProxy::destroy(TreeObserver* observer) {
+void RenderProxy::destroy() {
SETUP_TASK(destroy);
args->context = mContext;
- args->observer = observer;
// destroyCanvasAndSurface() needs a fence as when it returns the
// underlying BufferQueue is going to be released from under
// the render thread.
@@ -282,16 +281,15 @@
return layer;
}
-CREATE_BRIDGE3(buildLayer, CanvasContext* context, RenderNode* node, TreeObserver* observer) {
- args->context->buildLayer(args->node, args->observer);
+CREATE_BRIDGE2(buildLayer, CanvasContext* context, RenderNode* node) {
+ args->context->buildLayer(args->node);
return nullptr;
}
-void RenderProxy::buildLayer(RenderNode* node, TreeObserver* observer) {
+void RenderProxy::buildLayer(RenderNode* node) {
SETUP_TASK(buildLayer);
args->context = mContext;
args->node = node;
- args->observer = observer;
postAndWait(task);
}
@@ -328,15 +326,14 @@
postAndWait(task);
}
-CREATE_BRIDGE2(destroyHardwareResources, CanvasContext* context, TreeObserver* observer) {
- args->context->destroyHardwareResources(args->observer);
+CREATE_BRIDGE1(destroyHardwareResources, CanvasContext* context) {
+ args->context->destroyHardwareResources();
return nullptr;
}
-void RenderProxy::destroyHardwareResources(TreeObserver* observer) {
+void RenderProxy::destroyHardwareResources() {
SETUP_TASK(destroyHardwareResources);
args->context = mContext;
- args->observer = observer;
postAndWait(task);
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 44a5a14..1629090 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -44,7 +44,6 @@
class DisplayList;
class Layer;
class Rect;
-class TreeObserver;
namespace renderthread {
@@ -87,19 +86,19 @@
ANDROID_API void setLightCenter(const Vector3& lightCenter);
ANDROID_API void setOpaque(bool opaque);
ANDROID_API int64_t* frameInfo();
- ANDROID_API int syncAndDrawFrame(TreeObserver* observer);
- ANDROID_API void destroy(TreeObserver* observer);
+ ANDROID_API int syncAndDrawFrame();
+ ANDROID_API void destroy();
ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion);
ANDROID_API DeferredLayerUpdater* createTextureLayer();
- ANDROID_API void buildLayer(RenderNode* node, TreeObserver* observer);
+ ANDROID_API void buildLayer(RenderNode* node);
ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap);
ANDROID_API void pushLayerUpdate(DeferredLayerUpdater* layer);
ANDROID_API void cancelLayerUpdate(DeferredLayerUpdater* layer);
ANDROID_API void detachSurfaceTexture(DeferredLayerUpdater* layer);
- ANDROID_API void destroyHardwareResources(TreeObserver* observer);
+ ANDROID_API void destroyHardwareResources();
ANDROID_API static void trimMemory(int level);
ANDROID_API static void overrideProperty(const char* name, const char* value);
diff --git a/libs/hwui/tests/common/TestListViewSceneBase.cpp b/libs/hwui/tests/common/TestListViewSceneBase.cpp
index 6d2e8599..38c4848 100644
--- a/libs/hwui/tests/common/TestListViewSceneBase.cpp
+++ b/libs/hwui/tests/common/TestListViewSceneBase.cpp
@@ -69,7 +69,7 @@
// draw it to parent DisplayList
canvas->drawRenderNode(mListItems[ci].get());
}
- mListView->setStagingDisplayList(canvas->finishRecording(), nullptr);
+ mListView->setStagingDisplayList(canvas->finishRecording());
}
} // namespace test
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 275ce16..0916d72 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -193,10 +193,7 @@
}
SkRect TestUtils::getClipBounds(const SkCanvas* canvas) {
- SkClipStack::BoundsType boundType;
- SkRect clipBounds;
- canvas->getClipStack()->getBounds(&clipBounds, &boundType);
- return clipBounds;
+ return SkRect::Make(canvas->getDeviceClipBounds());
}
SkRect TestUtils::getLocalClipBounds(const SkCanvas* canvas) {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 8b287de..16bc6f7 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -156,6 +156,11 @@
int* mSignal;
};
+ class MockTreeObserver : public TreeObserver {
+ public:
+ virtual void onMaybeRemovedFromTree(RenderNode* node) {}
+ };
+
static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
for (int i = 0; i < 16; i++) {
if (!MathUtils::areEqual(a[i], b[i])) {
@@ -215,7 +220,7 @@
std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(props.getWidth(),
props.getHeight()));
setup(props, *canvas.get());
- node->setStagingDisplayList(canvas->finishRecording(), nullptr);
+ node->setStagingDisplayList(canvas->finishRecording());
}
node->setPropertyFieldsDirty(0xFFFFFFFF);
return node;
@@ -236,7 +241,7 @@
if (setup) {
RecordingCanvasType canvas(props.getWidth(), props.getHeight());
setup(props, canvas);
- node->setStagingDisplayList(canvas.finishRecording(), nullptr);
+ node->setStagingDisplayList(canvas.finishRecording());
}
node->setPropertyFieldsDirty(0xFFFFFFFF);
return node;
@@ -247,7 +252,7 @@
std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(
node.stagingProperties().getWidth(), node.stagingProperties().getHeight()));
contentCallback(*canvas.get());
- node.setStagingDisplayList(canvas->finishRecording(), nullptr);
+ node.setStagingDisplayList(canvas->finishRecording());
}
static sp<RenderNode> createSkiaNode(int left, int top, int right, int bottom,
@@ -265,14 +270,14 @@
RenderProperties& props = node->mutateStagingProperties();
props.setLeftTopRightBottom(left, top, right, bottom);
if (displayList) {
- node->setStagingDisplayList(displayList, nullptr);
+ node->setStagingDisplayList(displayList);
}
if (setup) {
std::unique_ptr<skiapipeline::SkiaRecordingCanvas> canvas(
new skiapipeline::SkiaRecordingCanvas(nullptr,
props.getWidth(), props.getHeight()));
setup(props, *canvas.get());
- node->setStagingDisplayList(canvas->finishRecording(), nullptr);
+ node->setStagingDisplayList(canvas->finishRecording());
}
node->setPropertyFieldsDirty(0xFFFFFFFF);
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
@@ -350,8 +355,9 @@
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
+ MarkAndSweepRemoved observer(nullptr);
node->syncProperties();
- node->syncDisplayList(nullptr);
+ node->syncDisplayList(observer, nullptr);
auto displayList = node->getDisplayList();
if (displayList) {
for (auto&& childOp : displayList->getChildren()) {
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index c0d9450..5b685bb 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -60,6 +60,6 @@
0, 100 * (i + 2), minikin::kBidi_Force_LTR, paint, nullptr);
}
- container->setStagingDisplayList(canvas->finishRecording(), nullptr);
+ container->setStagingDisplayList(canvas->finishRecording());
}
};
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 396e896..f8d6397 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -151,7 +151,7 @@
testContext.waitForVsync();
nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
- proxy->syncAndDrawFrame(nullptr);
+ proxy->syncAndDrawFrame();
}
proxy->resetProfileInfo();
@@ -167,7 +167,7 @@
ATRACE_NAME("UI-Draw Frame");
UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
scene->doFrame(i);
- proxy->syncAndDrawFrame(nullptr);
+ proxy->syncAndDrawFrame();
}
if (opts.reportFrametimeWeight) {
proxy->fence();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 42ba3db..ef5ce0d 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -40,7 +40,7 @@
ASSERT_FALSE(canvasContext->hasSurface());
- canvasContext->destroy(nullptr);
+ canvasContext->destroy();
}
RENDERTHREAD_TEST(CanvasContext, invokeFunctor) {
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 6f3ed9c..a391d1e 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -311,13 +311,36 @@
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_TRUE(node->isValid());
+ EXPECT_FALSE(node->nothingToDraw());
+ node->setStagingDisplayList(nullptr);
+ EXPECT_FALSE(node->isValid());
+ EXPECT_FALSE(node->nothingToDraw());
+ node->destroyHardwareResources();
+ EXPECT_TRUE(node->nothingToDraw());
+ EXPECT_FALSE(node->isValid());
+ }
+
+ {
+ // 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..f21b3f7 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -321,7 +321,6 @@
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
- info.observer = nullptr;
parent->prepareTree(info);
//parent(A) -> (receiverBackground, child)
@@ -437,7 +436,6 @@
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
- info.observer = nullptr;
parent->prepareTree(info);
int drawCounter = 0;
@@ -527,7 +525,6 @@
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
- info.observer = nullptr;
parent->prepareTree(info);
std::unique_ptr<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas());
@@ -545,7 +542,6 @@
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
- info.observer = nullptr;
renderNode->prepareTree(info);
//create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
@@ -865,10 +861,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/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index ab8e4e1..8af4bbe 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -66,6 +66,70 @@
EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents";
}
+TEST(RenderNode, validity) {
+ auto child = TestUtils::createNode(0, 0, 200, 400,
+ [](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver);
+ });
+ auto parent = TestUtils::createNode(0, 0, 200, 400,
+ [&child](RenderProperties& props, Canvas& canvas) {
+ canvas.drawRenderNode(child.get());
+ });
+
+ EXPECT_TRUE(child->isValid());
+ EXPECT_TRUE(parent->isValid());
+ EXPECT_TRUE(child->nothingToDraw());
+ EXPECT_TRUE(parent->nothingToDraw());
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+ EXPECT_TRUE(child->isValid());
+ EXPECT_TRUE(parent->isValid());
+ EXPECT_FALSE(child->nothingToDraw());
+ EXPECT_FALSE(parent->nothingToDraw());
+
+ TestUtils::recordNode(*parent, [](Canvas& canvas) {
+ canvas.drawColor(Color::Amber_500, SkBlendMode::kSrcOver);
+ });
+
+ EXPECT_TRUE(child->isValid());
+ EXPECT_TRUE(parent->isValid());
+ EXPECT_FALSE(child->nothingToDraw());
+ EXPECT_FALSE(parent->nothingToDraw());
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+ EXPECT_FALSE(child->isValid());
+ EXPECT_TRUE(parent->isValid());
+ EXPECT_TRUE(child->nothingToDraw());
+ EXPECT_FALSE(parent->nothingToDraw());
+
+ TestUtils::recordNode(*child, [](Canvas& canvas) {
+ canvas.drawColor(Color::Amber_500, SkBlendMode::kSrcOver);
+ });
+
+ EXPECT_TRUE(child->isValid());
+ EXPECT_TRUE(child->nothingToDraw());
+
+ TestUtils::recordNode(*parent, [&child](Canvas& canvas) {
+ canvas.drawRenderNode(child.get());
+ });
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+ EXPECT_TRUE(child->isValid());
+ EXPECT_TRUE(parent->isValid());
+ EXPECT_FALSE(child->nothingToDraw());
+ EXPECT_FALSE(parent->nothingToDraw());
+
+ parent->destroyHardwareResources();
+
+ EXPECT_FALSE(child->isValid());
+ EXPECT_FALSE(parent->isValid());
+ EXPECT_TRUE(child->nothingToDraw());
+ EXPECT_TRUE(parent->nothingToDraw());
+}
+
TEST(RenderNode, releasedCallback) {
class DecRefOnReleased : public GlFunctorLifecycleListener {
public:
@@ -112,7 +176,6 @@
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
- info.observer = nullptr;
{
auto nonNullDLNode = TestUtils::createNode(0, 0, 200, 400,
@@ -131,7 +194,7 @@
nullDLNode->prepareTree(info);
}
- canvasContext->destroy(nullptr);
+ canvasContext->destroy();
}
RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) {
@@ -151,7 +214,6 @@
LayerUpdateQueue layerUpdateQueue;
info.damageAccumulator = &damageAccumulator;
info.layerUpdateQueue = &layerUpdateQueue;
- info.observer = nullptr;
// Put node on HW layer
rootNode->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
@@ -165,5 +227,5 @@
EXPECT_FALSE(info.layerUpdateQueue->entries().empty());
EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode);
EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
- canvasContext->destroy(nullptr);
+ canvasContext->destroy();
}
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 8f6fc8b..be460bf 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -126,7 +126,6 @@
TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
- info.observer = nullptr;
SkiaDisplayList skiaDL(SkRect::MakeWH(200, 200));
@@ -137,7 +136,9 @@
ASSERT_FALSE(cleanVD.isDirty());
ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed());
- ASSERT_FALSE(skiaDL.prepareListAndChildren(info, false, [](RenderNode*, TreeInfo&, bool) {}));
+ TestUtils::MockTreeObserver observer;
+ ASSERT_FALSE(skiaDL.prepareListAndChildren(observer, info, false,
+ [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
ASSERT_TRUE(cleanVD.getPropertyChangeWillBeConsumed());
// prepare again this time adding a dirty VD
@@ -146,7 +147,8 @@
ASSERT_TRUE(dirtyVD.isDirty());
ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
- ASSERT_TRUE(skiaDL.prepareListAndChildren(info, false, [](RenderNode*, TreeInfo&, bool) {}));
+ ASSERT_TRUE(skiaDL.prepareListAndChildren(observer, info, false,
+ [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
// prepare again this time adding a RenderNode and a callback
@@ -155,8 +157,8 @@
SkCanvas dummyCanvas;
skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas);
bool hasRun = false;
- ASSERT_TRUE(skiaDL.prepareListAndChildren(info, false,
- [&hasRun, renderNode, infoPtr](RenderNode* n, TreeInfo& i, bool r) {
+ ASSERT_TRUE(skiaDL.prepareListAndChildren(observer, info, false,
+ [&hasRun, renderNode, infoPtr](RenderNode* n, TreeObserver& observer, TreeInfo& i, bool r) {
hasRun = true;
ASSERT_EQ(renderNode.get(), n);
ASSERT_EQ(infoPtr, &i);
@@ -164,7 +166,7 @@
}));
ASSERT_TRUE(hasRun);
- canvasContext->destroy(nullptr);
+ canvasContext->destroy();
}
TEST(SkiaDisplayList, updateChildren) {
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/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
index 8b80d69..79fc864 100644
--- a/libs/hwui/utils/TestWindowContext.cpp
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -98,8 +98,8 @@
}
void finishDrawing() {
- mRootNode->setStagingDisplayList(mCanvas->finishRecording(), nullptr);
- mProxy->syncAndDrawFrame(nullptr);
+ mRootNode->setStagingDisplayList(mCanvas->finishRecording());
+ mProxy->syncAndDrawFrame();
// Surprisingly, calling mProxy->fence() here appears to make no difference to
// the timings we record.
}
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/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index dc8264a..391a905 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -306,12 +306,26 @@
* until there are no glitches.
* This tuning step should be done while playing silence.
* This technique provides a compromise between latency and glitch rate.
+ *
+ * @deprecated Use {@link AudioTrack.Builder#setPerformanceMode(int)} with
+ * {@link AudioTrack#PERFORMANCE_MODE_LOW_LATENCY} to control performance.
*/
public final static int FLAG_LOW_LATENCY = 0x1 << 8;
+ /**
+ * @hide
+ * Flag requesting a deep buffer path when creating an {@code AudioTrack}.
+ *
+ * A deep buffer path, if available, may consume less power and is
+ * suitable for media playback where latency is not a concern.
+ * Use {@link AudioTrack.Builder#setPerformanceMode(int)} with
+ * {@link AudioTrack#PERFORMANCE_MODE_POWER_SAVING} to enable.
+ */
+ public final static int FLAG_DEEP_BUFFER = 0x1 << 9;
+
private final static int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO |
FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY |
- FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY;
+ FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER;
private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED |
FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY;
@@ -541,6 +555,8 @@
/**
* Sets the combination of flags.
+ *
+ * This is a bitwise OR with the existing flags.
* @param flags a combination of {@link AudioAttributes#FLAG_AUDIBILITY_ENFORCED},
* {@link AudioAttributes#FLAG_HW_AV_SYNC}.
* @return the same Builder instance.
@@ -553,6 +569,17 @@
/**
* @hide
+ * Replaces flags.
+ * @param flags any combination of {@link AudioAttributes#FLAG_ALL}.
+ * @return the same Builder instance.
+ */
+ public Builder replaceFlags(int flags) {
+ mFlags = flags & AudioAttributes.FLAG_ALL;
+ return this;
+ }
+
+ /**
+ * @hide
* Adds a Bundle of data
* @param bundle a non-null Bundle
* @return the same builder instance
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7c60385..fa904cd 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.
@@ -4129,7 +4074,9 @@
* currently connected to the system and meeting the criteria specified in the
* <code>flags</code> parameter.
* @param flags A set of bitflags specifying the criteria to test.
- * @see {@link GET_DEVICES_OUTPUTS}, {@link GET_DEVICES_INPUTS} and {@link GET_DEVICES_ALL}.
+ * @see #GET_DEVICES_OUTPUTS
+ * @see #GET_DEVICES_INPUTS
+ * @see #GET_DEVICES_ALL
* @return A (possibly zero-length) array of AudioDeviceInfo objects.
*/
public AudioDeviceInfo[] getDevices(int flags) {
@@ -4200,7 +4147,9 @@
* parameter.
* This is an internal function. The public API front is getDevices(int).
* @param flags A set of bitflags specifying the criteria to test.
- * @see {@link GET_DEVICES_OUTPUTS}, {@link GET_DEVICES_INPUTS} and {@link GET_DEVICES_ALL}.
+ * @see #GET_DEVICES_OUTPUTS
+ * @see #GET_DEVICES_INPUTS
+ * @see #GET_DEVICES_ALL
* @return A (possibly zero-length) array of AudioDeviceInfo objects.
* @hide
*/
@@ -4246,7 +4195,7 @@
* Unregisters an {@link AudioDeviceCallback} object which has been previously registered
* to receive notifications of changes to the set of connected audio devices.
* @param callback The {@link AudioDeviceCallback} object that was previously registered
- * with {@link AudioManager#registerAudioDeviceCallback) to be unregistered.
+ * with {@link AudioManager#registerAudioDeviceCallback} to be unregistered.
*/
public void unregisterAudioDeviceCallback(AudioDeviceCallback callback) {
synchronized (mDeviceCallbacks) {
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 031ac06..b23f5fd 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -214,6 +214,66 @@
*/
public final static int WRITE_NON_BLOCKING = 1;
+ /** @hide */
+ @IntDef({
+ PERFORMANCE_MODE_NONE,
+ PERFORMANCE_MODE_LOW_LATENCY,
+ PERFORMANCE_MODE_POWER_SAVING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PerformanceMode {}
+
+ /**
+ * Default performance mode for an {@link AudioTrack}.
+ */
+ public static final int PERFORMANCE_MODE_NONE = 0;
+
+ /**
+ * Low latency performance mode for an {@link AudioTrack}.
+ * If the device supports it, this mode
+ * enables a lower latency path through to the audio output sink.
+ * Effects may no longer work with such an {@code AudioTrack} and
+ * the sample rate must match that of the output sink.
+ * <p>
+ * Applications should be aware that low latency requires careful
+ * buffer management, with smaller chunks of audio data written by each
+ * {@code write()} call.
+ * <p>
+ * If this flag is used without specifying a {@code bufferSizeInBytes} then the
+ * {@code AudioTrack}'s actual buffer size may be too small.
+ * It is recommended that a fairly
+ * large buffer should be specified when the {@code AudioTrack} is created.
+ * Then the actual size can be reduced by calling
+ * {@link #setBufferSizeInFrames(int)}. The buffer size can be optimized
+ * by lowering it after each {@code write()} call until the audio glitches,
+ * which is detected by calling
+ * {@link #getUnderrunCount()}. Then the buffer size can be increased
+ * until there are no glitches.
+ * This tuning step should be done while playing silence.
+ * This technique provides a compromise between latency and glitch rate.
+ */
+ public static final int PERFORMANCE_MODE_LOW_LATENCY = 1;
+
+ /**
+ * Power saving performance mode for an {@link AudioTrack}.
+ * If the device supports it, this
+ * mode will enable a lower power path to the audio output sink.
+ * In addition, this lower power path typically will have
+ * deeper internal buffers and better underrun resistance,
+ * with a tradeoff of higher latency.
+ * <p>
+ * In this mode, applications should attempt to use a larger buffer size
+ * and deliver larger chunks of audio data per {@code write()} call.
+ * Use {@link #getBufferSizeInFrames()} to determine
+ * the actual buffer size of the {@code AudioTrack} as it may have increased
+ * to accommodate a deeper buffer.
+ */
+ public static final int PERFORMANCE_MODE_POWER_SAVING = 2;
+
+ // keep in sync with system/media/audio/include/system/audio-base.h
+ private static final int AUDIO_OUTPUT_FLAG_FAST = 0x4;
+ private static final int AUDIO_OUTPUT_FLAG_DEEP_BUFFER = 0x8;
+
//--------------------------------------------------------------------------
// Member variables
//--------------------
@@ -648,6 +708,7 @@
private int mBufferSizeInBytes;
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
private int mMode = MODE_STREAM;
+ private int mPerformanceMode = PERFORMANCE_MODE_NONE;
/**
* Constructs a new Builder with the default values as described above.
@@ -752,6 +813,32 @@
}
/**
+ * Sets the {@link AudioTrack} performance mode. This is an advisory request which
+ * may not be supported by the particular device, and the framework is free
+ * to ignore such request if it is incompatible with other requests or hardware.
+ *
+ * @param performanceMode one of
+ * {@link AudioTrack#PERFORMANCE_MODE_NONE},
+ * {@link AudioTrack#PERFORMANCE_MODE_LOW_LATENCY},
+ * or {@link AudioTrack#PERFORMANCE_MODE_POWER_SAVING}.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException if {@code performanceMode} is not valid.
+ */
+ public @NonNull Builder setPerformanceMode(@PerformanceMode int performanceMode) {
+ switch (performanceMode) {
+ case PERFORMANCE_MODE_NONE:
+ case PERFORMANCE_MODE_LOW_LATENCY:
+ case PERFORMANCE_MODE_POWER_SAVING:
+ mPerformanceMode = performanceMode;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid performance mode " + performanceMode);
+ }
+ return this;
+ }
+
+ /**
* Builds an {@link AudioTrack} instance initialized with all the parameters set
* on this <code>Builder</code>.
* @return a new successfully initialized {@link AudioTrack} instance.
@@ -765,6 +852,25 @@
.setUsage(AudioAttributes.USAGE_MEDIA)
.build();
}
+ switch (mPerformanceMode) {
+ case PERFORMANCE_MODE_LOW_LATENCY:
+ mAttributes = new AudioAttributes.Builder(mAttributes)
+ .replaceFlags((mAttributes.getAllFlags()
+ | AudioAttributes.FLAG_LOW_LATENCY)
+ & ~AudioAttributes.FLAG_DEEP_BUFFER)
+ .build();
+ break;
+ case PERFORMANCE_MODE_NONE:
+ break;
+ case PERFORMANCE_MODE_POWER_SAVING:
+ mAttributes = new AudioAttributes.Builder(mAttributes)
+ .replaceFlags((mAttributes.getAllFlags()
+ | AudioAttributes.FLAG_DEEP_BUFFER)
+ & ~AudioAttributes.FLAG_LOW_LATENCY)
+ .build();
+ break;
+ }
+
if (mFormat == null) {
mFormat = new AudioFormat.Builder()
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
@@ -1278,6 +1384,27 @@
}
/**
+ * Returns the current performance mode of the {@link AudioTrack}.
+ *
+ * @return one of {@link AudioTrack#PERFORMANCE_MODE_NONE},
+ * {@link AudioTrack#PERFORMANCE_MODE_LOW_LATENCY},
+ * or {@link AudioTrack#PERFORMANCE_MODE_POWER_SAVING}.
+ * Use {@link AudioTrack.Builder#setPerformanceMode}
+ * in the {@link AudioTrack.Builder} to enable a performance mode.
+ * @throws IllegalStateException if track is not initialized.
+ */
+ public @PerformanceMode int getPerformanceMode() {
+ final int flags = native_get_flags();
+ if ((flags & AUDIO_OUTPUT_FLAG_FAST) != 0) {
+ return PERFORMANCE_MODE_LOW_LATENCY;
+ } else if ((flags & AUDIO_OUTPUT_FLAG_DEEP_BUFFER) != 0) {
+ return PERFORMANCE_MODE_POWER_SAVING;
+ } else {
+ return PERFORMANCE_MODE_NONE;
+ }
+ }
+
+ /**
* Returns the output sample rate in Hz for the specified stream type.
*/
static public int getNativeOutputSampleRate(int streamType) {
@@ -2855,6 +2982,8 @@
private native final int native_get_underrun_count();
+ private native final int native_get_flags();
+
// longArray must be a non-null array of length >= 2
// [0] is assigned the frame position
// [1] is assigned the time in CLOCK_MONOTONIC nanoseconds
diff --git a/media/java/android/media/BufferingParams.aidl b/media/java/android/media/BufferingParams.aidl
new file mode 100644
index 0000000..d156d44
--- /dev/null
+++ b/media/java/android/media/BufferingParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+parcelable BufferingParams;
diff --git a/media/java/android/media/BufferingParams.java b/media/java/android/media/BufferingParams.java
new file mode 100644
index 0000000..fdcd6ba
--- /dev/null
+++ b/media/java/android/media/BufferingParams.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Structure for source buffering management params.
+ *
+ * Used by {@link MediaPlayer#getDefaultBufferingParams()},
+ * {@link MediaPlayer#getBufferingParams()} and
+ * {@link MediaPlayer#setBufferingParams(BufferingParams)}
+ * to control source buffering behavior.
+ *
+ * <p>There are two stages of source buffering in {@link MediaPlayer}: initial buffering
+ * (when {@link MediaPlayer} is being prepared) and rebuffering (when {@link MediaPlayer}
+ * is playing back source). {@link BufferingParams} includes mode and corresponding
+ * watermarks for each stage of source buffering. The watermarks could be either size
+ * based (in milliseconds), or time based (in kilobytes) or both, depending on the mode.
+ *
+ * <p>There are 4 buffering modes: {@link #BUFFERING_MODE_NONE},
+ * {@link #BUFFERING_MODE_TIME_ONLY}, {@link #BUFFERING_MODE_SIZE_ONLY} and
+ * {@link #BUFFERING_MODE_TIME_THEN_SIZE}.
+ * {@link MediaPlayer} source component has default buffering modes which can be queried
+ * by calling {@link MediaPlayer#getDefaultBufferingParams()}.
+ * Users should always use those default modes or their downsized version when trying to
+ * change buffering params. For example, {@link #BUFFERING_MODE_TIME_THEN_SIZE} can be
+ * downsized to {@link #BUFFERING_MODE_NONE}, {@link #BUFFERING_MODE_TIME_ONLY} or
+ * {@link #BUFFERING_MODE_SIZE_ONLY}. But {@link #BUFFERING_MODE_TIME_ONLY} can not be
+ * downsized to {@link #BUFFERING_MODE_SIZE_ONLY}.
+ * <ul>
+ * <li><strong>initial buffering stage:</strong> has one watermark which is used when
+ * {@link MediaPlayer} is being prepared. When cached data amount exceeds this watermark,
+ * {@link MediaPlayer} is prepared.</li>
+ * <li><strong>rebuffering stage:</strong> has two watermarks, low and high, which are
+ * used when {@link MediaPlayer} is playing back content.
+ * <ul>
+ * <li> When cached data amount exceeds high watermark, {@link MediaPlayer} will pause
+ * buffering. Buffering will resume when cache runs below some limit which could be low
+ * watermark or some intermediate value decided by the source component.</li>
+ * <li> When cached data amount runs below low watermark, {@link MediaPlayer} will paused
+ * playback. Playback will resume when cached data amount exceeds high watermark
+ * or reaches end of stream.</li>
+ * </ul>
+ * </ul>
+ * <p>Users should use {@link Builder} to change {@link BufferingParams}.
+ */
+public final class BufferingParams implements Parcelable {
+ /**
+ * This mode indicates that source buffering is not supported.
+ */
+ public static final int BUFFERING_MODE_NONE = 0;
+ /**
+ * This mode indicates that only time based source buffering is supported. This means
+ * the watermark(s) are time based.
+ */
+ public static final int BUFFERING_MODE_TIME_ONLY = 1;
+ /**
+ * This mode indicates that only size based source buffering is supported. This means
+ * the watermark(s) are size based.
+ */
+ public static final int BUFFERING_MODE_SIZE_ONLY = 2;
+ /**
+ * This mode indicates that both time and size based source buffering are supported,
+ * and time based calculation precedes size based. Size based calculation will be used
+ * only when time information is not available from the source.
+ */
+ public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ BUFFERING_MODE_NONE,
+ BUFFERING_MODE_TIME_ONLY,
+ BUFFERING_MODE_SIZE_ONLY,
+ BUFFERING_MODE_TIME_THEN_SIZE,
+ }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BufferingMode {}
+
+ private static final int BUFFERING_NO_WATERMARK = -1;
+
+ // params
+ private int mInitialBufferingMode = BUFFERING_MODE_NONE;
+ private int mRebufferingMode = BUFFERING_MODE_NONE;
+
+ private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
+ private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
+
+ private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+
+ private BufferingParams() {
+ }
+
+ /**
+ * Return the initial buffering mode used when {@link MediaPlayer} is being prepared.
+ * @return one of the values that can be set in {@link Builder#setInitialBufferingMode(int)}
+ */
+ public int getInitialBufferingMode() {
+ return mInitialBufferingMode;
+ }
+
+ /**
+ * Return the rebuffering mode used when {@link MediaPlayer} is playing back source.
+ * @return one of the values that can be set in {@link Builder#setRebufferingMode(int)}
+ */
+ public int getRebufferingMode() {
+ return mRebufferingMode;
+ }
+
+ /**
+ * Return the time based initial buffering watermark in milliseconds.
+ * It is meaningful only when initial buffering mode obatined from
+ * {@link #getInitialBufferingMode()} is time based.
+ * @return time based initial buffering watermark in milliseconds
+ */
+ public int getInitialBufferingWatermarkMs() {
+ return mInitialWatermarkMs;
+ }
+
+ /**
+ * Return the size based initial buffering watermark in kilobytes.
+ * It is meaningful only when initial buffering mode obatined from
+ * {@link #getInitialBufferingMode()} is size based.
+ * @return size based initial buffering watermark in kilobytes
+ */
+ public int getInitialBufferingWatermarkKB() {
+ return mInitialWatermarkKB;
+ }
+
+ /**
+ * Return the time based low watermark in milliseconds for rebuffering.
+ * It is meaningful only when rebuffering mode obatined from
+ * {@link #getRebufferingMode()} is time based.
+ * @return time based low watermark for rebuffering in milliseconds
+ */
+ public int getRebufferingWatermarkLowMs() {
+ return mRebufferingWatermarkLowMs;
+ }
+
+ /**
+ * Return the time based high watermark in milliseconds for rebuffering.
+ * It is meaningful only when rebuffering mode obatined from
+ * {@link #getRebufferingMode()} is time based.
+ * @return time based high watermark for rebuffering in milliseconds
+ */
+ public int getRebufferingWatermarkHighMs() {
+ return mRebufferingWatermarkHighMs;
+ }
+
+ /**
+ * Return the size based low watermark in kilobytes for rebuffering.
+ * It is meaningful only when rebuffering mode obatined from
+ * {@link #getRebufferingMode()} is size based.
+ * @return size based low watermark for rebuffering in kilobytes
+ */
+ public int getRebufferingWatermarkLowKB() {
+ return mRebufferingWatermarkLowKB;
+ }
+
+ /**
+ * Return the size based high watermark in kilobytes for rebuffering.
+ * It is meaningful only when rebuffering mode obatined from
+ * {@link #getRebufferingMode()} is size based.
+ * @return size based high watermark for rebuffering in kilobytes
+ */
+ public int getRebufferingWatermarkHighKB() {
+ return mRebufferingWatermarkHighKB;
+ }
+
+ /**
+ * Builder class for {@link BufferingParams} objects.
+ * <p> Here is an example where <code>Builder</code> is used to define the
+ * {@link BufferingParams} to be used by a {@link MediaPlayer} instance:
+ *
+ * <pre class="prettyprint">
+ * BufferingParams myParams = mediaplayer.getDefaultBufferingParams();
+ * myParams = new BufferingParams.Builder(myParams)
+ * .setInitialBufferingWatermarkMs(10000)
+ * .build();
+ * mediaplayer.setBufferingParams(myParams);
+ * </pre>
+ */
+ public static class Builder {
+ private int mInitialBufferingMode = BUFFERING_MODE_NONE;
+ private int mRebufferingMode = BUFFERING_MODE_NONE;
+
+ private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
+ private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
+
+ private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+
+ /**
+ * Constructs a new Builder with the defaults.
+ * By default, both initial buffering mode and rebuffering mode are
+ * {@link BufferingParams#BUFFERING_MODE_NONE}, and all watermarks are -1.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Constructs a new Builder from a given {@link BufferingParams} instance
+ * @param bp the {@link BufferingParams} object whose data will be reused
+ * in the new Builder.
+ */
+ public Builder(BufferingParams bp) {
+ mInitialBufferingMode = bp.mInitialBufferingMode;
+ mRebufferingMode = bp.mRebufferingMode;
+
+ mInitialWatermarkMs = bp.mInitialWatermarkMs;
+ mInitialWatermarkKB = bp.mInitialWatermarkKB;
+
+ mRebufferingWatermarkLowMs = bp.mRebufferingWatermarkLowMs;
+ mRebufferingWatermarkHighMs = bp.mRebufferingWatermarkHighMs;
+ mRebufferingWatermarkLowKB = bp.mRebufferingWatermarkLowKB;
+ mRebufferingWatermarkHighKB = bp.mRebufferingWatermarkHighKB;
+ }
+
+ /**
+ * Combines all of the fields that have been set and return a new
+ * {@link BufferingParams} object. <code>IllegalStateException</code> will be
+ * thrown if there is conflict between fields.
+ * @return a new {@link BufferingParams} object
+ */
+ public BufferingParams build() {
+ if (isTimeBasedMode(mRebufferingMode)
+ && mRebufferingWatermarkLowMs > mRebufferingWatermarkHighMs) {
+ throw new IllegalStateException("Illegal watermark:"
+ + mRebufferingWatermarkLowMs + " : " + mRebufferingWatermarkHighMs);
+ }
+ if (isSizeBasedMode(mRebufferingMode)
+ && mRebufferingWatermarkLowKB > mRebufferingWatermarkHighKB) {
+ throw new IllegalStateException("Illegal watermark:"
+ + mRebufferingWatermarkLowKB + " : " + mRebufferingWatermarkHighKB);
+ }
+
+ BufferingParams bp = new BufferingParams();
+ bp.mInitialBufferingMode = mInitialBufferingMode;
+ bp.mRebufferingMode = mRebufferingMode;
+
+ bp.mInitialWatermarkMs = mInitialWatermarkMs;
+ bp.mInitialWatermarkKB = mInitialWatermarkKB;
+
+ bp.mRebufferingWatermarkLowMs = mRebufferingWatermarkLowMs;
+ bp.mRebufferingWatermarkHighMs = mRebufferingWatermarkHighMs;
+ bp.mRebufferingWatermarkLowKB = mRebufferingWatermarkLowKB;
+ bp.mRebufferingWatermarkHighKB = mRebufferingWatermarkHighKB;
+ return bp;
+ }
+
+ private boolean isTimeBasedMode(int mode) {
+ return (mode == BUFFERING_MODE_TIME_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
+ }
+
+ private boolean isSizeBasedMode(int mode) {
+ return (mode == BUFFERING_MODE_SIZE_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
+ }
+
+ /**
+ * Sets the initial buffering mode.
+ * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
+ * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
+ * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
+ * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * @return the same Builder instance.
+ */
+ public Builder setInitialBufferingMode(@BufferingMode int mode) {
+ switch (mode) {
+ case BUFFERING_MODE_NONE:
+ case BUFFERING_MODE_TIME_ONLY:
+ case BUFFERING_MODE_SIZE_ONLY:
+ case BUFFERING_MODE_TIME_THEN_SIZE:
+ mInitialBufferingMode = mode;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal buffering mode " + mode);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the rebuffering mode.
+ * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
+ * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
+ * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
+ * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingMode(@BufferingMode int mode) {
+ switch (mode) {
+ case BUFFERING_MODE_NONE:
+ case BUFFERING_MODE_TIME_ONLY:
+ case BUFFERING_MODE_SIZE_ONLY:
+ case BUFFERING_MODE_TIME_THEN_SIZE:
+ mRebufferingMode = mode;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal buffering mode " + mode);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the time based watermark in milliseconds for initial buffering.
+ * @param watermarkMs time based watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setInitialBufferingWatermarkMs(int watermarkMs) {
+ mInitialWatermarkMs = watermarkMs;
+ return this;
+ }
+
+ /**
+ * Sets the size based watermark in kilobytes for initial buffering.
+ * @param watermarkKB size based watermark in kilobytes
+ * @return the same Builder instance.
+ */
+ public Builder setInitialBufferingWatermarkKB(int watermarkKB) {
+ mInitialWatermarkKB = watermarkKB;
+ return this;
+ }
+
+ /**
+ * Sets the time based low watermark in milliseconds for rebuffering.
+ * @param watermarkMs time based low watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarkLowMs(int watermarkMs) {
+ mRebufferingWatermarkLowMs = watermarkMs;
+ return this;
+ }
+
+ /**
+ * Sets the time based high watermark in milliseconds for rebuffering.
+ * @param watermarkMs time based high watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarkHighMs(int watermarkMs) {
+ mRebufferingWatermarkHighMs = watermarkMs;
+ return this;
+ }
+
+ /**
+ * Sets the size based low watermark in milliseconds for rebuffering.
+ * @param watermarkKB size based low watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarkLowKB(int watermarkKB) {
+ mRebufferingWatermarkLowKB = watermarkKB;
+ return this;
+ }
+
+ /**
+ * Sets the size based high watermark in milliseconds for rebuffering.
+ * @param watermarkKB size based high watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarkHighKB(int watermarkKB) {
+ mRebufferingWatermarkHighKB = watermarkKB;
+ return this;
+ }
+
+ /**
+ * Sets the time based low and high watermarks in milliseconds for rebuffering.
+ * @param lowWatermarkMs time based low watermark in milliseconds
+ * @param highWatermarkMs time based high watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarksMs(int lowWatermarkMs, int highWatermarkMs) {
+ mRebufferingWatermarkLowMs = lowWatermarkMs;
+ mRebufferingWatermarkHighMs = highWatermarkMs;
+ return this;
+ }
+
+ /**
+ * Sets the size based low and high watermarks in kilobytes for rebuffering.
+ * @param lowWatermarkKB size based low watermark in kilobytes
+ * @param highWatermarkKB size based high watermark in kilobytes
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarksKB(int lowWatermarkKB, int highWatermarkKB) {
+ mRebufferingWatermarkLowKB = lowWatermarkKB;
+ mRebufferingWatermarkHighKB = highWatermarkKB;
+ return this;
+ }
+ }
+
+ private BufferingParams(Parcel in) {
+ mInitialBufferingMode = in.readInt();
+ mRebufferingMode = in.readInt();
+
+ mInitialWatermarkMs = in.readInt();
+ mInitialWatermarkKB = in.readInt();
+
+ mRebufferingWatermarkLowMs = in.readInt();
+ mRebufferingWatermarkHighMs = in.readInt();
+ mRebufferingWatermarkLowKB = in.readInt();
+ mRebufferingWatermarkHighKB = in.readInt();
+ }
+
+ public static final Parcelable.Creator<BufferingParams> CREATOR =
+ new Parcelable.Creator<BufferingParams>() {
+ @Override
+ public BufferingParams createFromParcel(Parcel in) {
+ return new BufferingParams(in);
+ }
+
+ @Override
+ public BufferingParams[] newArray(int size) {
+ return new BufferingParams[size];
+ }
+ };
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mInitialBufferingMode);
+ dest.writeInt(mRebufferingMode);
+
+ dest.writeInt(mInitialWatermarkMs);
+ dest.writeInt(mInitialWatermarkKB);
+
+ dest.writeInt(mRebufferingWatermarkLowMs);
+ dest.writeInt(mRebufferingWatermarkHighMs);
+ dest.writeInt(mRebufferingWatermarkLowKB);
+ dest.writeInt(mRebufferingWatermarkHighKB);
+ }
+}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 52d5b7ca..71f7790 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1597,8 +1597,8 @@
final Rational[] rationalArray = new Rational[values.length];
for (int j = 0; j < values.length; ++j) {
final String[] numbers = values[j].split("/");
- rationalArray[j] = new Rational(Long.parseLong(numbers[0]),
- Long.parseLong(numbers[1]));
+ rationalArray[j] = new Rational((long) Double.parseDouble(numbers[0]),
+ (long) Double.parseDouble(numbers[1]));
}
mAttributes[i].put(tag,
ExifAttribute.createURational(rationalArray, mExifByteOrder));
@@ -1609,8 +1609,8 @@
final Rational[] rationalArray = new Rational[values.length];
for (int j = 0; j < values.length; ++j) {
final String[] numbers = values[j].split("/");
- rationalArray[j] = new Rational(Long.parseLong(numbers[0]),
- Long.parseLong(numbers[1]));
+ rationalArray[j] = new Rational((long) Double.parseDouble(numbers[0]),
+ (long) Double.parseDouble(numbers[1]));
}
mAttributes[i].put(tag,
ExifAttribute.createSRational(rationalArray, mExifByteOrder));
@@ -3442,8 +3442,8 @@
String[] rationalNumber = entryValue.split("/");
if (rationalNumber.length == 2) {
try {
- long numerator = Long.parseLong(rationalNumber[0]);
- long denominator = Long.parseLong(rationalNumber[1]);
+ long numerator = (long) Double.parseDouble(rationalNumber[0]);
+ long denominator = (long) Double.parseDouble(rationalNumber[1]);
if (numerator < 0L || denominator < 0L) {
return new Pair<>(IFD_FORMAT_SRATIONAL, -1);
}
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/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index e481aa1..a1f816b 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -95,7 +95,8 @@
public @interface Format {}
// All the native functions are listed here.
- private static native long nativeSetup(@NonNull FileDescriptor fd, int format);
+ private static native long nativeSetup(@NonNull FileDescriptor fd, int format)
+ throws IllegalArgumentException, IOException;
private static native void nativeRelease(long nativeObject);
private static native void nativeStart(long nativeObject);
private static native void nativeStop(long nativeObject);
@@ -134,19 +135,13 @@
if (path == null) {
throw new IllegalArgumentException("path must not be null");
}
- if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 &&
- format != OutputFormat.MUXER_OUTPUT_WEBM) {
- throw new IllegalArgumentException("format is invalid");
- }
// Use RandomAccessFile so we can open the file with RW access;
// RW access allows the native writer to memory map the output file.
RandomAccessFile file = null;
try {
file = new RandomAccessFile(path, "rws");
FileDescriptor fd = file.getFD();
- mNativeObject = nativeSetup(fd, format);
- mState = MUXER_STATE_INITIALIZED;
- mCloseGuard.open("release");
+ setUpMediaMuxer(fd, format);
} finally {
if (file != null) {
file.close();
@@ -155,6 +150,32 @@
}
/**
+ * Constructor.
+ * Creates a media muxer that writes to the specified FileDescriptor. File descriptor
+ * must be seekable and writable. Application should not use the file referenced
+ * by this file descriptor until {@link #stop}. It is the application's responsibility
+ * to close the file descriptor. It is safe to do so as soon as this call returns.
+ * @param fd The FileDescriptor of the output media file.
+ * @param format The format of the output media file.
+ * @see android.media.MediaMuxer.OutputFormat
+ * @throws IllegalArgumentException if fd is invalid or format is not supported.
+ * @throws IOException if failed to open the file for write.
+ */
+ public MediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException {
+ setUpMediaMuxer(fd, format);
+ }
+
+ private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException {
+ if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 &&
+ format != OutputFormat.MUXER_OUTPUT_WEBM) {
+ throw new IllegalArgumentException("format: " + format + " is invalid");
+ }
+ mNativeObject = nativeSetup(fd, format);
+ mState = MUXER_STATE_INITIALIZED;
+ mCloseGuard.open("release");
+ }
+
+ /**
* Sets the orientation hint for output video playback.
* <p>This method should be called before {@link #start}. Calling this
* method will not rotate the video frame when muxer is generating the file,
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index e3a0f25..4023400 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -45,6 +45,7 @@
import android.widget.VideoView;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
+import android.media.BufferingParams;
import android.media.MediaFormat;
import android.media.MediaTimeProvider;
import android.media.PlaybackParams;
@@ -479,6 +480,11 @@
* <td>{} </p></td>
* <td>This method can be called in any state and calling it does not change
* the object state. </p></td></tr>
+ * <tr><td>setBufferingParams</p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error}</p></td>
+ * <td>{Idle} </p></td>
+ * <td>This method does not change the object state.
+ * </p></td></tr>
* <tr><td>setPlaybackParams</p></td>
* <td>{Initialized, Prepared, Started, Paused, PlaybackCompleted, Error}</p></td>
* <td>{Idle, Stopped} </p></td>
@@ -1390,6 +1396,45 @@
public native boolean isPlaying();
/**
+ * Gets the default buffering management params.
+ * Calling it only after {@code setDataSource} has been called.
+ * Each type of data source might have different set of default params.
+ *
+ * @return the default buffering management params supported by the source component.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized, or {@code setDataSource} has not been called.
+ */
+ @NonNull
+ public native BufferingParams getDefaultBufferingParams();
+
+ /**
+ * Gets the current buffering management params used by the source component.
+ * Calling it only after {@code setDataSource} has been called.
+ *
+ * @return the current buffering management params used by the source component.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized, or {@code setDataSource} has not been called.
+ */
+ @NonNull
+ public native BufferingParams getBufferingParams();
+
+ /**
+ * Sets buffering management params.
+ * The object sets its internal BufferingParams to the input, except that the input is
+ * invalid or not supported.
+ * Call it only after {@code setDataSource} has been called.
+ * Users should only use supported mode returned by {@link #getDefaultBufferingParams()}
+ * or its downsized version as described in {@link BufferingParams}.
+ *
+ * @param params the buffering management params.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released, or {@code setDataSource} has not been called.
+ * @throws IllegalArgumentException if params is invalid or not supported.
+ */
+ public native void setBufferingParams(@NonNull BufferingParams params);
+
+ /**
* Change playback speed of audio by resampling the audio.
* <p>
* Specifies resampling as audio mode for variable rate playback, i.e.,
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 64576ec..3e88450 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -801,6 +801,30 @@
}
/**
+ * Sets the next output file descriptor to be used when the maximum filesize is reached
+ * on the prior output {@link #setOutputFile} or {@link #setNextOutputFile}). File descriptor
+ * must be seekable and writable. After setting the next output file, application should not
+ * use the file referenced by this file descriptor until {@link #stop}. It is the application's
+ * responsibility to close the file descriptor. It is safe to do so as soon as this call returns.
+ * Application must call this after receiving on the
+ * {@link android.media.MediaRecorder.OnInfoListener} a "what" code of
+ * {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING} and before receiving a "what" code of
+ * {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED}. The file is not used until switching to
+ * that output. Application will receive{@link #MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED}
+ * when the next output file is used. Application will not be able to set a new output file if
+ * the previous one has not been used. Application is responsible for cleaning up unused files
+ * after {@link #stop} is called.
+ *
+ * @param fd an open file descriptor to be written into.
+ * @throws IllegalStateException if it is called before prepare().
+ * @throws IOException if setNextOutputFile fails otherwise.
+ */
+ public void setNextOutputFile(FileDescriptor fd) throws IllegalStateException, IOException
+ {
+ _setNextOutputFile(fd);
+ }
+
+ /**
* Sets the path of the output file to be produced. Call this after
* setOutputFormat() but before prepare().
*
@@ -814,9 +838,38 @@
mPath = path;
}
+ /**
+ * Sets the next output file path to be used when the maximum filesize is reached
+ * on the prior output {@link #setOutputFile} or {@link #setNextOutputFile}). File should
+ * be seekable. After setting the next output file, application should not use the file
+ * referenced by this file descriptor until {@link #stop}. Application must call this
+ * after receiving on the {@link android.media.MediaRecorder.OnInfoListener} a "what" code of
+ * {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING} and before receiving a "what" code of
+ * {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED}. The file is not used until switching to
+ * that output. Application will receive {@link #MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED}
+ * when the next output file is used. Application will not be able to set a new output file if
+ * the previous one has not been used. Application is responsible for cleaning up unused files
+ * after {@link #stop} is called.
+ *
+ * @param path The pathname to use.
+ * @throws IllegalStateException if it is called before prepare().
+ * @throws IOException if setNextOutputFile fails otherwise.
+ */
+ public void setNextOutputFile(String path) throws IllegalStateException, IOException
+ {
+ if (path != null) {
+ RandomAccessFile file = new RandomAccessFile(path, "rws");
+ try {
+ _setNextOutputFile(file.getFD());
+ } finally {
+ file.close();
+ }
+ }
+ }
+
// native implementation
- private native void _setOutputFile(FileDescriptor fd, long offset, long length)
- throws IllegalStateException, IOException;
+ private native void _setOutputFile(FileDescriptor fd) throws IllegalStateException, IOException;
+ private native void _setNextOutputFile(FileDescriptor fd) throws IllegalStateException, IOException;
private native void _prepare() throws IllegalStateException, IOException;
/**
@@ -833,12 +886,12 @@
if (mPath != null) {
RandomAccessFile file = new RandomAccessFile(mPath, "rws");
try {
- _setOutputFile(file.getFD(), 0, 0);
+ _setOutputFile(file.getFD());
} finally {
file.close();
}
} else if (mFd != null) {
- _setOutputFile(mFd, 0, 0);
+ _setOutputFile(mFd);
} else {
throw new IOException("No valid output file");
}
@@ -980,9 +1033,26 @@
*/
public static final int MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800;
/** A maximum filesize had been setup and has now been reached.
+ * Note: This event will not be sent if application already set
+ * next output file through {@link #setNextOutputFile}.
* @see android.media.MediaRecorder.OnInfoListener
*/
public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801;
+ /** A maximum filesize had been setup and current recorded file size
+ * has reached 90% of the limit. This is sent once per file upon
+ * reaching/passing the 90% limit. To continue the recording, applicaiton
+ * should use {@link #setNextOutputFile} to set the next output file.
+ * Otherwise, recording will stop when reaching maximum file size.
+ * @see android.media.MediaRecorder.OnInfoListener
+ */
+ public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING = 802;
+ /** A maximum filesize had been reached and MediaRecorder has switched
+ * output to a new file set by application {@link #setNextOutputFile}.
+ * For best practice, application should use this event to keep track
+ * of whether the file previously set has been used or not.
+ * @see android.media.MediaRecorder.OnInfoListener
+ */
+ public static final int MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED = 803;
/** informational events for individual tracks, for testing purpose.
* The track informational event usually contains two parts in the ext1
diff --git a/media/java/android/media/MediaSyncEvent.java b/media/java/android/media/MediaSyncEvent.java
index 31af6b5..04448f0 100644
--- a/media/java/android/media/MediaSyncEvent.java
+++ b/media/java/android/media/MediaSyncEvent.java
@@ -36,7 +36,7 @@
* The corresponding action is triggered only when the presentation is completed
* (meaning the media has been presented to the user) on the specified session.
* A synchronization of this type requires a source audio session ID to be set via
- * {@link #setAudioSessionId(int) method.
+ * {@link #setAudioSessionId(int)} method.
*/
public static final int SYNC_EVENT_PRESENTATION_COMPLETE =
AudioSystem.SYNC_EVENT_PRESENTATION_COMPLETE;
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 9819395..bd0a1b4 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -78,6 +78,7 @@
}
mAttributes = attr;
mImplType = implType;
+ mState = AudioPlaybackConfiguration.PLAYER_STATE_IDLE;
};
/**
@@ -138,7 +139,8 @@
void baseStart() {
if (DEBUG) { Log.v(TAG, "baseStart() piid=" + mPlayerIId); }
try {
- getService().playerEvent(mPlayerIId, AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
+ mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
+ getService().playerEvent(mPlayerIId, mState);
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
}
@@ -152,7 +154,8 @@
void basePause() {
if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); }
try {
- getService().playerEvent(mPlayerIId, AudioPlaybackConfiguration.PLAYER_STATE_PAUSED);
+ mState = AudioPlaybackConfiguration.PLAYER_STATE_PAUSED;
+ getService().playerEvent(mPlayerIId, mState);
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, PAUSED state will not be tracked", e);
}
@@ -161,7 +164,8 @@
void baseStop() {
if (DEBUG) { Log.v(TAG, "baseStop() piid=" + mPlayerIId); }
try {
- getService().playerEvent(mPlayerIId, AudioPlaybackConfiguration.PLAYER_STATE_STOPPED);
+ mState = AudioPlaybackConfiguration.PLAYER_STATE_STOPPED;
+ getService().playerEvent(mPlayerIId, mState);
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, STOPPED state will not be tracked", e);
}
@@ -193,7 +197,7 @@
* Releases AppOps related resources.
*/
void baseRelease() {
- if (DEBUG) { Log.v(TAG, "baseRelease() piid=" + mPlayerIId); }
+ if (DEBUG) { Log.v(TAG, "baseRelease() piid=" + mPlayerIId + " state=" + mState); }
try {
if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
getService().releasePlayer(mPlayerIId);
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/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index e197141..5f12742 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -224,7 +224,7 @@
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
* @return the same Builder instance.
* @throws IllegalArgumentException
- * @see {@link #excludeRule(AudioAttributes, int)}
+ * @see #excludeRule(AudioAttributes, int)
*/
@SystemApi
public Builder addRule(AudioAttributes attrToMatch, int rule)
@@ -253,7 +253,7 @@
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
* @return the same Builder instance.
* @throws IllegalArgumentException
- * @see {@link #addRule(AudioAttributes, int)}
+ * @see #addRule(AudioAttributes, int)
*/
@SystemApi
public Builder excludeRule(AudioAttributes attrToMatch, int rule)
@@ -275,7 +275,7 @@
* {@link AudioAttributes} or an {@link java.lang.Integer}).
* @return the same Builder instance.
* @throws IllegalArgumentException
- * @see {@link #excludeMixRule(int, Object)}
+ * @see #excludeMixRule(int, Object)
*/
@SystemApi
public Builder addMixRule(int rule, Object property) throws IllegalArgumentException {
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..9a08fbe 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";
@@ -643,7 +654,7 @@
*
* <p>This is used to indicate the broadcast standard (e.g. ATSC, DVB or ISDB) the current
* channel conforms to. Use {@link #TYPE_OTHER} for streaming-based channels, which is the
- * default channel type. The value should match to one of the followings:
+ * default channel type. The value should match one of the followings:
* {@link #TYPE_1SEG},
* {@link #TYPE_ATSC_C},
* {@link #TYPE_ATSC_M_H},
@@ -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() {}
/**
@@ -1063,6 +1088,238 @@
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
/**
+ * The program type for movie.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_MOVIE = "TYPE_MOVIE";
+
+ /**
+ * The program type for TV series.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_TV_SERIES = "TYPE_TV_SERIES";
+
+ /**
+ * The program type for TV season.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_TV_SEASON = "TYPE_TV_SEASON";
+
+ /**
+ * The program type for TV episode.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_TV_EPISODE = "TYPE_TV_EPISODE";
+
+ /**
+ * The program type for clip.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_CLIP = "TYPE_CLIP";
+
+ /**
+ * The program type for event.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_EVENT = "TYPE_EVENT";
+
+ /**
+ * The program type for channel.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_CHANNEL = "TYPE_CHANNEL";
+
+ /**
+ * The program type for track.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_TRACK = "TYPE_TRACK";
+
+ /**
+ * The program type for album.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_ALBUM = "TYPE_ALBUM";
+
+ /**
+ * The program type for artist.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_ARTIST = "TYPE_ARTIST";
+
+ /**
+ * The program type for playlist.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_PLAYLIST = "TYPE_PLAYLIST";
+
+ /**
+ * The program type for station.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_STATION = "TYPE_STATION";
+
+ /**
+ * The watch next type for CONTINUE.
+ *
+ * @see #COLUMN_WATCH_NEXT_TYPE
+ */
+ public static final String WATCH_NEXT_TYPE_CONTINUE = "WATCH_NEXT_TYPE_CONTINUE";
+
+ /**
+ * The watch next type for NEXT.
+ *
+ * @see #COLUMN_WATCH_NEXT_TYPE
+ */
+ public static final String WATCH_NEXT_TYPE_NEXT = "WATCH_NEXT_TYPE_NEXT";
+
+ /**
+ * The watch next type for NEW.
+ *
+ * @see #COLUMN_WATCH_NEXT_TYPE
+ */
+ public static final String WATCH_NEXT_TYPE_NEW = "WATCH_NEXT_TYPE_NEW";
+
+ /**
+ * The aspect ratio for 16:9.
+ *
+ * @see #COLUMN_POSTER_ART_ASPECT_RATIO
+ * @see #COLUMN_THUMBNAIL_ASPECT_RATIO
+ */
+ public static final String ASPECT_RATIO_16_9 = "ASPECT_RATIO_16_9";
+
+ /**
+ * The aspect ratio for 3:2.
+ *
+ * @see #COLUMN_POSTER_ART_ASPECT_RATIO
+ * @see #COLUMN_THUMBNAIL_ASPECT_RATIO
+ */
+ public static final String ASPECT_RATIO_3_2 = "ASPECT_RATIO_3_2";
+
+ /**
+ * The aspect ratio for 1:1.
+ *
+ * @see #COLUMN_POSTER_ART_ASPECT_RATIO
+ * @see #COLUMN_THUMBNAIL_ASPECT_RATIO
+ */
+ public static final String ASPECT_RATIO_1_1 = "ASPECT_RATIO_1_1";
+
+ /**
+ * The aspect ratio for 2:3.
+ *
+ * @see #COLUMN_POSTER_ART_ASPECT_RATIO
+ * @see #COLUMN_THUMBNAIL_ASPECT_RATIO
+ */
+ public static final String ASPECT_RATIO_2_3 = "ASPECT_RATIO_2_3";
+
+ /**
+ * The availability for "available to this user".
+ *
+ * @see #COLUMN_AVAILABILITY
+ */
+ public static final String AVAILABILITY_AVAILABLE = "AVAILABILITY_AVAILABLE";
+
+ /**
+ * The availability for "free with subscription".
+ *
+ * @see #COLUMN_AVAILABILITY
+ */
+ public static final String AVAILABILITY_FREE_WITH_SUBSCRIPTION =
+ "AVAILABILITY_FREE_WITH_SUBSCRIPTION";
+
+ /**
+ * The availability for "paid content, either to-own or rental
+ * (user has not purchased/rented).
+ *
+ * @see #COLUMN_AVAILABILITY
+ */
+ public static final String AVAILABILITY_PAID_CONTENT = "AVAILABILITY_PAID_CONTENT";
+
+ /**
+ * The interaction type for "listens".
+ *
+ * @see #COLUMN_INTERACTION_TYPE
+ */
+ public static final String INTERACTION_TYPE_LISTENS = "INTERACTION_TYPE_LISTENS";
+
+ /**
+ * The interaction type for "followers".
+ *
+ * @see #COLUMN_INTERACTION_TYPE
+ */
+ public static final String INTERACTION_TYPE_FOLLOWERS = "INTERACTION_TYPE_FOLLOWERS";
+
+ /**
+ * The interaction type for "fans".
+ *
+ * @see #COLUMN_INTERACTION_TYPE
+ */
+ public static final String INTERACTION_TYPE_FANS = "INTERACTION_TYPE_FANS";
+
+ /**
+ * The interaction type for "likes".
+ *
+ * @see #COLUMN_INTERACTION_TYPE
+ */
+ public static final String INTERACTION_TYPE_LIKES = "INTERACTION_TYPE_LIKES";
+
+ /**
+ * The interaction type for "thumbs".
+ *
+ * @see #COLUMN_INTERACTION_TYPE
+ */
+ public static final String INTERACTION_TYPE_THUMBS = "INTERACTION_TYPE_THUMBS";
+
+ /**
+ * The interaction type for "views".
+ *
+ * @see #COLUMN_INTERACTION_TYPE
+ */
+ public static final String INTERACTION_TYPE_VIEWS = "INTERACTION_TYPE_VIEWS";
+
+ /**
+ * The interaction type for "viewers".
+ *
+ * @see #COLUMN_INTERACTION_TYPE
+ */
+ public static final String INTERACTION_TYPE_VIEWERS = "INTERACTION_TYPE_VIEWERS";
+
+ /**
+ * The review rating style for five star rating.
+ *
+ * @see #COLUMN_REVIEW_RATING_STYLE
+ */
+ public static final String REVIEW_RATING_STYLE_STARS = "REVIEW_RATING_STYLE_STARS";
+
+ /**
+ * The review rating style for thumbs-up and thumbs-down rating.
+ *
+ * @see #COLUMN_REVIEW_RATING_STYLE
+ */
+ public static final String REVIEW_RATING_STYLE_THUMBS_UP_DOWN =
+ "REVIEW_RATING_STYLE_THUMBS_UP_DOWN";
+
+ /**
+ * The review rating style for 0 to 100 point system.
+ *
+ * @see #COLUMN_REVIEW_RATING_STYLE
+ */
+ public static final String REVIEW_RATING_STYLE_PERCENTAGE =
+ "REVIEW_RATING_STYLE_PERCENTAGE";
+
+ /**
* The ID of the TV channel that provides this TV program.
*
* <p>This is a part of the channel URI and matches to {@link BaseColumns#_ID}.
@@ -1074,6 +1331,44 @@
public static final String COLUMN_CHANNEL_ID = "channel_id";
/**
+ * The type of this program content.
+ *
+ * <p>The value should match one of the followings:
+ * {@link #TYPE_MOVIE},
+ * {@link #TYPE_TV_SERIES},
+ * {@link #TYPE_TV_SEASON},
+ * {@link #TYPE_TV_EPISODE},
+ * {@link #TYPE_CLIP},
+ * {@link #TYPE_EVENT},
+ * {@link #TYPE_CHANNEL},
+ * {@link #TYPE_TRACK},
+ * {@link #TYPE_ALBUM},
+ * {@link #TYPE_ARTIST},
+ * {@link #TYPE_PLAYLIST}, and
+ * {@link #TYPE_STATION}.
+ *
+ * <p>This is a required field if the program is from a {@link Channels#TYPE_PREVIEW}
+ * channel.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_TYPE = "type";
+
+ /**
+ * The "watch next" type of this program content.
+ *
+ * <p>The value should match one of the followings:
+ * {@link #WATCH_NEXT_TYPE_CONTINUE},
+ * {@link #WATCH_NEXT_TYPE_NEXT}, and
+ * {@link #WATCH_NEXT_TYPE_NEW}.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_WATCH_NEXT_TYPE = "watch_next_type";
+
+ /**
* The title of this TV program.
*
* <p>If this program is an episodic TV show, it is recommended that the title is the series
@@ -1165,6 +1460,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 +1473,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";
@@ -1300,6 +1599,19 @@
public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
/**
+ * The aspect ratio of the poster art for this TV program.
+ *
+ * <p>The value should match one of the followings:
+ * {@link #ASPECT_RATIO_16_9},
+ * {@link #ASPECT_RATIO_3_2},
+ * {@link #ASPECT_RATIO_1_1}, and
+ * {@link #ASPECT_RATIO_2_3}.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_POSTER_ART_ASPECT_RATIO = "poster_art_aspect_ratio";
+
+ /**
* The URI for the thumbnail of this TV program.
*
* <p>The system can generate a thumbnail from the poster art if this column is not
@@ -1322,6 +1634,104 @@
public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
/**
+ * The aspect ratio of the thumbnail for this TV program.
+ *
+ * <p>The value should match one of the followings:
+ * {@link #ASPECT_RATIO_16_9},
+ * {@link #ASPECT_RATIO_3_2},
+ * {@link #ASPECT_RATIO_1_1}, and
+ * {@link #ASPECT_RATIO_2_3}.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
+
+ /**
+ * The URI for the logo of this TV program.
+ *
+ * <p>This is a small badge shown on top of the poster art or thumbnail representing the
+ * source of the content.
+ *
+ * <p>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_LOGO = "logo";
+
+ /**
+ * The availability of this TV program.
+ *
+ * <p>The value should match one of the followings:
+ * {@link #AVAILABILITY_AVAILABLE},
+ * {@link #AVAILABILITY_FREE_WITH_SUBSCRIPTION}, and
+ * {@link #AVAILABILITY_PAID_CONTENT}.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_AVAILABILITY = "availability";
+
+ /**
+ * The starting price of this TV program.
+ *
+ * <p>This indicates the lowest regular acquisition cost of the content. It is only used
+ * if the availability of the program is {@link #AVAILABILITY_PAID_CONTENT}.
+ *
+ * <p>Type: TEXT
+ * @see #COLUMN_OFFER_PRICE
+ */
+ public static final String COLUMN_STARTING_PRICE = "starting_price";
+
+ /**
+ * The offer price of this TV program.
+ *
+ * <p>This is the promotional cost of the content. It is only used if the availability of
+ * the program is {@link #AVAILABILITY_PAID_CONTENT}.
+ *
+ * <p>Type: TEXT
+ * @see #COLUMN_STARTING_PRICE
+ */
+ public static final String COLUMN_OFFER_PRICE = "offer_price";
+
+ /**
+ * The release date of this TV program.
+ *
+ * <p>The value should be in the form of either "yyyy-MM-dd" or "yyyy".
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_RELEASE_DATE = "release_date";
+
+ /**
+ * The count of the items included in this TV program.
+ *
+ * <p>This is only relevant if the program represents a collection of items such as series,
+ * episodes, or music tracks.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String COLUMN_ITEM_COUNT = "item_count";
+
+ /**
+ * The flag indicating whether this TV program is live or not.
+ *
+ * <p>A value of 1 indicates that the content is airing and should be consumed now, a value
+ * of 0 indicates that the content is off the air and does not need to be consumed at the
+ * present time. If not specified, the value is set to 0 (not live) by default.
+ *
+ * <p>Type: INTEGER (boolean)
+ */
+ public static final String COLUMN_LIVE = "live";
+
+ /**
* The flag indicating whether this TV program is searchable or not.
*
* <p>The columns of searchable programs can be read by other applications that have proper
@@ -1410,6 +1820,163 @@
*/
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";
+
+ /**
+ * The type of interaction for this TV program.
+ *
+ * <p> The value should match one of the followings:
+ * {@link #INTERACTION_TYPE_LISTENS},
+ * {@link #INTERACTION_TYPE_FOLLOWERS},
+ * {@link #INTERACTION_TYPE_FANS},
+ * {@link #INTERACTION_TYPE_LIKES},
+ * {@link #INTERACTION_TYPE_THUMBS},
+ * {@link #INTERACTION_TYPE_VIEWS}, and
+ * {@link #INTERACTION_TYPE_VIEWERS}.
+ *
+ * <p>Type: TEXT
+ * @see #COLUMN_INTERACTION_COUNT
+ */
+ public static final String COLUMN_INTERACTION_TYPE = "interaction_type";
+
+ /**
+ * The interaction count for this program.
+ *
+ * <p>This indicates the number of times interaction has happened.
+ *
+ * <p>Type: INTEGER
+ * @see #COLUMN_INTERACTION_TYPE
+ */
+ public static final String COLUMN_INTERACTION_COUNT = "interaction_count";
+
+ /**
+ * The author or artist of this content.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_AUTHOR = "author";
+
+ /**
+ * The review rating score style used for {@link #COLUMN_REVIEW_RATING}.
+ *
+ * <p> The value should match one of the followings: {@link #REVIEW_RATING_STYLE_STARS},
+ * {@link #REVIEW_RATING_STYLE_THUMBS_UP_DOWN}, and {@link #REVIEW_RATING_STYLE_PERCENTAGE}.
+ *
+ * <p>Type: TEXT
+ * @see #COLUMN_REVIEW_RATING
+ */
+ public static final String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
+
+ /**
+ * The review rating score for this program.
+ *
+ * <p>The format of the value is dependent on {@link #COLUMN_REVIEW_RATING_STYLE}. If the
+ * style is {@link #REVIEW_RATING_STYLE_STARS}, the value should be a real number between
+ * 0.0 and 5.0. (e.g. "4.5") If the style is {@link #REVIEW_RATING_STYLE_THUMBS_UP_DOWN},
+ * the value should be two integers, one for thumbs-up count and the other for thumbs-down
+ * count, with a comma between them. (e.g. "200,40") If the style is
+ * {@link #REVIEW_RATING_STYLE_PERCENTAGE}, the value shoule be a real number between 0 and
+ * 100. (e.g. "99.9")
+ *
+ * <p>Type: TEXT
+ * @see #COLUMN_REVIEW_RATING_STYLE
+ */
+ public static final String COLUMN_REVIEW_RATING = "review_rating";
+
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..b630270 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -317,6 +317,31 @@
*/
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";
+
+ /**
+ * Action sent by an application telling the system to set the given channel as browsable.
+ *
+ * <p>The intent must contain the following bundle parameters:
+ * <ul>
+ * <li>{@link #EXTRA_CHANNEL_ID} then channel ID as an integer.
+ * <li>{@link #EXTRA_PACKAGE_NAME} the package name of the requesting application.
+ * </ul>
+ */
+ public static final String ACTION_MAKE_CHANNEL_BROWSABLE
+ = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
+
+ /** The key for a bundle parameter containing a channel ID as an integer */
+ public static final String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID";
+
+ /** The key for a bundle parameter containing a package name as a string. */
+ public static final String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME";
+
private final ITvInputManager mService;
private final Object mLock = new Object();
diff --git a/media/jni/android_media_BufferingParams.h b/media/jni/android_media_BufferingParams.h
new file mode 100644
index 0000000..24c51f5
--- /dev/null
+++ b/media/jni/android_media_BufferingParams.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_BUFFERING_PARAMS_H_
+#define _ANDROID_MEDIA_BUFFERING_PARAMS_H_
+
+#include <media/BufferingSettings.h>
+
+namespace android {
+
+// This entire class is inline
+struct BufferingParams {
+ BufferingSettings settings;
+
+ struct fields_t {
+ jclass clazz;
+ jmethodID constructID;
+
+ jfieldID initial_buffering_mode;
+ jfieldID rebuffering_mode;
+ jfieldID initial_watermark_ms;
+ jfieldID initial_watermark_kb;
+ jfieldID rebuffering_watermark_low_ms;
+ jfieldID rebuffering_watermark_high_ms;
+ jfieldID rebuffering_watermark_low_kb;
+ jfieldID rebuffering_watermark_high_kb;
+
+ void init(JNIEnv *env) {
+ jclass lclazz = env->FindClass("android/media/BufferingParams");
+ if (lclazz == NULL) {
+ return;
+ }
+
+ clazz = (jclass)env->NewGlobalRef(lclazz);
+ if (clazz == NULL) {
+ return;
+ }
+
+ constructID = env->GetMethodID(clazz, "<init>", "()V");
+
+ initial_buffering_mode = env->GetFieldID(clazz, "mInitialBufferingMode", "I");
+ rebuffering_mode = env->GetFieldID(clazz, "mRebufferingMode", "I");
+ initial_watermark_ms = env->GetFieldID(clazz, "mInitialWatermarkMs", "I");
+ initial_watermark_kb = env->GetFieldID(clazz, "mInitialWatermarkKB", "I");
+ rebuffering_watermark_low_ms = env->GetFieldID(clazz, "mRebufferingWatermarkLowMs", "I");
+ rebuffering_watermark_high_ms = env->GetFieldID(clazz, "mRebufferingWatermarkHighMs", "I");
+ rebuffering_watermark_low_kb = env->GetFieldID(clazz, "mRebufferingWatermarkLowKB", "I");
+ rebuffering_watermark_high_kb = env->GetFieldID(clazz, "mRebufferingWatermarkHighKB", "I");
+
+ env->DeleteLocalRef(lclazz);
+ }
+
+ void exit(JNIEnv *env) {
+ env->DeleteGlobalRef(clazz);
+ clazz = NULL;
+ }
+ };
+
+ void fillFromJobject(JNIEnv *env, const fields_t& fields, jobject params) {
+ settings.mInitialBufferingMode =
+ (BufferingMode)env->GetIntField(params, fields.initial_buffering_mode);
+ settings.mRebufferingMode =
+ (BufferingMode)env->GetIntField(params, fields.rebuffering_mode);
+ settings.mInitialWatermarkMs =
+ env->GetIntField(params, fields.initial_watermark_ms);
+ settings.mInitialWatermarkKB =
+ env->GetIntField(params, fields.initial_watermark_kb);
+ settings.mRebufferingWatermarkLowMs =
+ env->GetIntField(params, fields.rebuffering_watermark_low_ms);
+ settings.mRebufferingWatermarkHighMs =
+ env->GetIntField(params, fields.rebuffering_watermark_high_ms);
+ settings.mRebufferingWatermarkLowKB =
+ env->GetIntField(params, fields.rebuffering_watermark_low_kb);
+ settings.mRebufferingWatermarkHighKB =
+ env->GetIntField(params, fields.rebuffering_watermark_high_kb);
+ }
+
+ jobject asJobject(JNIEnv *env, const fields_t& fields) {
+ jobject params = env->NewObject(fields.clazz, fields.constructID);
+ if (params == NULL) {
+ return NULL;
+ }
+ env->SetIntField(params, fields.initial_buffering_mode, (jint)settings.mInitialBufferingMode);
+ env->SetIntField(params, fields.rebuffering_mode, (jint)settings.mRebufferingMode);
+ env->SetIntField(params, fields.initial_watermark_ms, (jint)settings.mInitialWatermarkMs);
+ env->SetIntField(params, fields.initial_watermark_kb, (jint)settings.mInitialWatermarkKB);
+ env->SetIntField(params, fields.rebuffering_watermark_low_ms, (jint)settings.mRebufferingWatermarkLowMs);
+ env->SetIntField(params, fields.rebuffering_watermark_high_ms, (jint)settings.mRebufferingWatermarkHighMs);
+ env->SetIntField(params, fields.rebuffering_watermark_low_kb, (jint)settings.mRebufferingWatermarkLowKB);
+ env->SetIntField(params, fields.rebuffering_watermark_high_kb, (jint)settings.mRebufferingWatermarkHighKB);
+
+ return params;
+ }
+};
+
+} // namespace android
+
+#endif // _ANDROID_MEDIA_BUFFERING_PARAMS_H_
diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp
index 216624e..c3461f0 100644
--- a/media/jni/android_media_MediaMuxer.cpp
+++ b/media/jni/android_media_MediaMuxer.cpp
@@ -23,6 +23,9 @@
#include "jni.h"
#include "JNIHelp.h"
+#include <unistd.h>
+#include <fcntl.h>
+
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
@@ -138,6 +141,28 @@
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
ALOGV("native_setup: fd %d", fd);
+ // It appears that if an invalid file descriptor is passed through
+ // binder calls, the server-side of the inter-process function call
+ // is skipped. As a result, the check at the server-side to catch
+ // the invalid file descritpor never gets invoked. This is to workaround
+ // this issue by checking the file descriptor first before passing
+ // it through binder call.
+ int flags = fcntl(fd, F_GETFL);
+ if (flags == -1) {
+ ALOGE("Fail to get File Status Flags err: %s", strerror(errno));
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Invalid file descriptor");
+ return 0;
+ }
+
+ // fd must be in read-write mode or write-only mode.
+ if ((flags & (O_RDWR | O_WRONLY)) == 0) {
+ ALOGE("File descriptor is not in read-write mode or write-only mode");
+ jniThrowException(env, "java/io/IOException",
+ "File descriptor is not in read-write mode or write-only mode");
+ return 0;
+ }
+
MediaMuxer::OutputFormat fileFormat =
static_cast<MediaMuxer::OutputFormat>(format);
sp<MediaMuxer> muxer = new MediaMuxer(fd, fileFormat);
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index c52ed94..8225052 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -37,6 +37,7 @@
#include "utils/Errors.h" // for status_t
#include "utils/KeyedVector.h"
#include "utils/String8.h"
+#include "android_media_BufferingParams.h"
#include "android_media_MediaDataSource.h"
#include "android_media_PlaybackParams.h"
#include "android_media_SyncParams.h"
@@ -69,6 +70,7 @@
};
static fields_t fields;
+static BufferingParams::fields_t gBufferingParamsFields;
static PlaybackParams::fields_t gPlaybackParamsFields;
static SyncParams::fields_t gSyncParamsFields;
@@ -343,6 +345,66 @@
setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
}
+static jobject
+android_media_MediaPlayer_getDefaultBufferingParams(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ BufferingParams bp;
+ BufferingSettings &settings = bp.settings;
+ process_media_player_call(
+ env, thiz, mp->getDefaultBufferingSettings(&settings),
+ "java/lang/IllegalStateException", "unexpected error");
+ ALOGV("getDefaultBufferingSettings:{%s}", settings.toString().string());
+
+ return bp.asJobject(env, gBufferingParamsFields);
+}
+
+static jobject
+android_media_MediaPlayer_getBufferingParams(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ BufferingParams bp;
+ BufferingSettings &settings = bp.settings;
+ process_media_player_call(
+ env, thiz, mp->getBufferingSettings(&settings),
+ "java/lang/IllegalStateException", "unexpected error");
+ ALOGV("getBufferingSettings:{%s}", settings.toString().string());
+
+ return bp.asJobject(env, gBufferingParamsFields);
+}
+
+static void
+android_media_MediaPlayer_setBufferingParams(JNIEnv *env, jobject thiz, jobject params)
+{
+ if (params == NULL) {
+ return;
+ }
+
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ BufferingParams bp;
+ bp.fillFromJobject(env, gBufferingParamsFields, params);
+ ALOGV("setBufferingParams:{%s}", bp.settings.toString().string());
+
+ process_media_player_call(
+ env, thiz, mp->setBufferingSettings(bp.settings),
+ "java/lang/IllegalStateException", "unexpected error");
+}
+
static void
android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)
{
@@ -860,6 +922,7 @@
env->DeleteLocalRef(clazz);
+ gBufferingParamsFields.init(env);
gPlaybackParamsFields.init(env);
gSyncParamsFields.init(env);
}
@@ -1046,6 +1109,9 @@
{"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD},
{"_setDataSource", "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback },
{"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface},
+ {"getDefaultBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getDefaultBufferingParams},
+ {"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getBufferingParams},
+ {"setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer_setBufferingParams},
{"_prepare", "()V", (void *)android_media_MediaPlayer_prepare},
{"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync},
{"_start", "()V", (void *)android_media_MediaPlayer_start},
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 6c79ab7..7c509d2 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -290,7 +290,7 @@
}
static void
-android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
+android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor)
{
ALOGV("setOutputFile");
if (fileDescriptor == NULL) {
@@ -303,7 +303,25 @@
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
- status_t opStatus = mr->setOutputFile(fd, offset, length);
+ status_t opStatus = mr->setOutputFile(fd);
+ process_media_recorder_call(env, opStatus, "java/io/IOException", "setOutputFile failed.");
+}
+
+static void
+android_media_MediaRecorder_setNextOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor)
+{
+ ALOGV("setNextOutputFile");
+ if (fileDescriptor == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+ int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+ sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+ if (mr == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ status_t opStatus = mr->setNextOutputFile(fd);
process_media_recorder_call(env, opStatus, "java/io/IOException", "setOutputFile failed.");
}
@@ -617,7 +635,8 @@
{"setVideoEncoder", "(I)V", (void *)android_media_MediaRecorder_setVideoEncoder},
{"setAudioEncoder", "(I)V", (void *)android_media_MediaRecorder_setAudioEncoder},
{"setParameter", "(Ljava/lang/String;)V", (void *)android_media_MediaRecorder_setParameter},
- {"_setOutputFile", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaRecorder_setOutputFileFD},
+ {"_setOutputFile", "(Ljava/io/FileDescriptor;)V", (void *)android_media_MediaRecorder_setOutputFileFD},
+ {"_setNextOutputFile", "(Ljava/io/FileDescriptor;)V", (void *)android_media_MediaRecorder_setNextOutputFileFD},
{"setVideoSize", "(II)V", (void *)android_media_MediaRecorder_setVideoSize},
{"setVideoFrameRate", "(I)V", (void *)android_media_MediaRecorder_setVideoFrameRate},
{"setMaxDuration", "(I)V", (void *)android_media_MediaRecorder_setMaxDuration},
diff --git a/media/mca/effect/java/android/media/effect/package.html b/media/mca/effect/java/android/media/effect/package.html
index 8a210fd..1d297ef 100644
--- a/media/mca/effect/java/android/media/effect/package.html
+++ b/media/mca/effect/java/android/media/effect/package.html
@@ -24,7 +24,7 @@
android.media.effect.EffectContext#getFactory EffectContext.getFactory()}, which returns an instance
of {@link android.media.effect.EffectFactory}.</li>
<li>Call {@link android.media.effect.EffectFactory#createEffect createEffect()}, passing it an
-effect name from @link android.media.effect.EffectFactory}, such as {@link
+effect name from {@link android.media.effect.EffectFactory}, such as {@link
android.media.effect.EffectFactory#EFFECT_FISHEYE} or {@link
android.media.effect.EffectFactory#EFFECT_VIGNETTE}.</li>
</ol>
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..2f75c10
--- /dev/null
+++ b/native/android/hardware_buffer.cpp
@@ -0,0 +1,308 @@
+/*
+ * 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;
+ }
+
+ if (desc->format == AHARDWAREBUFFER_FORMAT_BLOB && desc->height != 1) {
+ ALOGE("Height must be 1 when using the AHARDWAREBUFFER_FORMAT_BLOB "
+ "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/res/values-am/strings.xml b/packages/CarrierDefaultApp/res/values-am/strings.xml
new file mode 100644
index 0000000..fc62e8d
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-am/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"አገልግሎትዎን ያግብሩ"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"ምንም የውሂብ አገልግሎት የለም"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"አገልግሎትዎን ለማግበር መታ ያድርጉ"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"ምንም አገልግሎት የለም፣ እባክዎ የአገልግሎት አቅራቢዎን ያነጋግሩ"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"ከተያዥ መግቢያ ጋር በመገናኘት ላይ..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"አውታረ መረብ የእረፍት ጊዜ ወስዷል፣ እንደገና መሞከር ይፈልጋሉ?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"አውታረ መረብ አይገኝም"</string>
+ <string name="quit" msgid="4392968039488794590">"አቁም"</string>
+ <string name="wait" msgid="7902715035629500128">"ይጠብቁ"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ar/strings.xml b/packages/CarrierDefaultApp/res/values-ar/strings.xml
new file mode 100644
index 0000000..3903c35
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-ar/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"تنشيط الخدمة"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"ليست هناك خدمة بيانات"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"انقر لتنشيط الخدمة"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"ليست هناك خدمة، يرجى الاتصال بمقدم الخدمة"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"جارٍ الاتصال بالمدخل المقيد الوصول..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"انتهت مهلة الشبكة، هل ترغب في إعادة المحاولة؟"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"الشبكة غير متاحة"</string>
+ <string name="quit" msgid="4392968039488794590">"إنهاء"</string>
+ <string name="wait" msgid="7902715035629500128">"انتظار"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..57f8bf5
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Aktivirajte uslugu"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Nema usluge prenosa podataka"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Dodirnite da biste aktivirali uslugu"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Nema usluge. Kontaktirajte dobavljača usluge"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Povezuje se sa ulaznim portalom…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Vremensko ograničenje mreže je isteklo. Želite li da probate ponovo?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Mreža nije dostupna"</string>
+ <string name="quit" msgid="4392968039488794590">"Zatvori"</string>
+ <string name="wait" msgid="7902715035629500128">"Čekaj"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-be/strings.xml b/packages/CarrierDefaultApp/res/values-be/strings.xml
new file mode 100644
index 0000000..e6d1cd9
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-be/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Актывуйце сэрвіс"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Няма сэрвісу перадачы даных"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Дакраніцеся, каб актываваць сэрвіс"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Няма абслугоўвання, звярніцеся да свайго пастаўшчыка паслуг"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Ідзе падключэнне да партала ўзаемадзеяння..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Час чакання сеткі скончыўся, хочаце паўтарыць спробу?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Сетка недаступная"</string>
+ <string name="quit" msgid="4392968039488794590">"Выйсці"</string>
+ <string name="wait" msgid="7902715035629500128">"Пачакайце"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-bg/strings.xml b/packages/CarrierDefaultApp/res/values-bg/strings.xml
new file mode 100644
index 0000000..b6bdd5d
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-bg/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Активирайте услугата си"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Няма услуга за данни"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Докоснете, за да активирате услугата си"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Няма покритие. Моля, свържете се с доставчика си"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Установява се връзка с портал за удостоверяване..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Времето за изчакване на мрежата изтече. Искате ли да опитате отново?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Мрежата не е налице"</string>
+ <string name="quit" msgid="4392968039488794590">"Изход"</string>
+ <string name="wait" msgid="7902715035629500128">"Изчакване"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-bn/strings.xml b/packages/CarrierDefaultApp/res/values-bn/strings.xml
new file mode 100644
index 0000000..138a2b3
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-bn/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"আপনার পরিষেবা সক্রিয় করুন"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"ডেটা পরিষেবা উপলব্ধ নয়"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"আপনার পরিষেবা সক্রিয় করতে আলতো চাপুন"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"পরিষেবা উপলব্ধ নয়, অনুগ্রহ করে আপনার পরিষেবা প্রদানকারীর সঙ্গে যোগাযোগ করুন"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"অন্তরীণ পোর্টালের সঙ্গে সংযুক্ত করা হচ্ছে..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"নেটওয়ার্কের সময় সমাপ্ত হয়েছে, আপনি কি পুনরায় চেষ্টা করতে চান?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"নেটওয়ার্ক অনুপলব্ধ"</string>
+ <string name="quit" msgid="4392968039488794590">"প্রস্থান করুন"</string>
+ <string name="wait" msgid="7902715035629500128">"অপেক্ষা করুন"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-bs/strings.xml b/packages/CarrierDefaultApp/res/values-bs/strings.xml
new file mode 100644
index 0000000..b43e766
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-bs/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Aktivirajte uslugu"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Nema mobilnog interneta"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Dodirnite da aktivirate uslugu"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Nema usluge. Obratite se pružaocu usluge."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Povezivanje na zaštitni portal..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Isteklo je vrijeme za odziv mreže. Želite li ponoviti?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Mreža nije dostupna"</string>
+ <string name="quit" msgid="4392968039488794590">"Odustani"</string>
+ <string name="wait" msgid="7902715035629500128">"Sačekaj"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ca/strings.xml b/packages/CarrierDefaultApp/res/values-ca/strings.xml
new file mode 100644
index 0000000..0730a01
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-ca/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Activa el servei"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"No hi ha servei de dades"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Toca per activar el servei"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"No hi ha servei. Contacta amb el proveïdor del servei."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"S\'està connectant al portal captiu…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"S\'ha esgotat el temps d\'espera de la xarxa. Vols tornar-ho a provar?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Xarxa no disponible"</string>
+ <string name="quit" msgid="4392968039488794590">"Surt"</string>
+ <string name="wait" msgid="7902715035629500128">"Espera"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-de/strings.xml b/packages/CarrierDefaultApp/res/values-de/strings.xml
new file mode 100644
index 0000000..2713560
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-de/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Dienst aktivieren"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Kein Datendienst"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Zum Aktivieren des Dienstes tippen"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Kein Dienst, kontaktiere bitte deinen Internetanbieter"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Verbindung mit Captive Portal wird hergestellt…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Zeitüberschreitung für Netzwerk – möchtest du es noch einmal versuchen?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Netzwerk nicht verfügbar"</string>
+ <string name="quit" msgid="4392968039488794590">"Beenden"</string>
+ <string name="wait" msgid="7902715035629500128">"Warten"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-el/strings.xml b/packages/CarrierDefaultApp/res/values-el/strings.xml
new file mode 100644
index 0000000..6e9b2a4
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-el/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Ενεργοποιήστε την υπηρεσία σας"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Δεν υπάρχει υπηρεσία δεδομένων"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Πατήστε για να ενεργοποιήσετε την υπηρεσία σας"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Η υπηρεσία δεν είναι διαθέσιμη. Επικοινωνήστε με τον παροχέα υπηρεσιών που χρησιμοποιείτε."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Σύνδεση στην πύλη υποδοχής…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Λήξη χρονικού ορίου δικτύου, θέλετε να δοκιμάσετε ξανά;"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Το δίκτυο δεν είναι διαθέσιμο"</string>
+ <string name="quit" msgid="4392968039488794590">"Έξοδος"</string>
+ <string name="wait" msgid="7902715035629500128">"Αναμονή"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..735b255
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Activa tu servicio"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Sin servicio de datos"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Presiona para activar tu servicio"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"No hay servicio, por lo que debes comunicarte con tu proveedor de servicios"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Conectando al portal cautivo…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Se agotó el tiempo de espera de la red, ¿quieres volver a intentarlo?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Red no disponible"</string>
+ <string name="quit" msgid="4392968039488794590">"Salir"</string>
+ <string name="wait" msgid="7902715035629500128">"Esperar"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-es/strings.xml b/packages/CarrierDefaultApp/res/values-es/strings.xml
new file mode 100644
index 0000000..49be628
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-es/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Activar el servicio"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Sin servicio de datos"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Toca aquí para activar tu servicio"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Sin servicio. Ponte en contacto con el proveedor de servicios"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Conectando al portal cautivo…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Se ha agotado el tiempo de espera de la red. ¿Quieres volver a intentarlo?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Red no disponible"</string>
+ <string name="quit" msgid="4392968039488794590">"Salir"</string>
+ <string name="wait" msgid="7902715035629500128">"Esperar"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-et/strings.xml b/packages/CarrierDefaultApp/res/values-et/strings.xml
new file mode 100644
index 0000000..0cdf5bb
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-et/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Aktiveerige teenus"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Andmesideteenus puudub"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Puudutage teenuse aktiveerimiseks"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Teenus puudub. Võtke ühendust oma teenusepakkujaga"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Hõiveportaaliga ühendamine …"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Võrgu ajalõpp. Kas soovite uuesti proovida?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Võrk ei ole saadaval"</string>
+ <string name="quit" msgid="4392968039488794590">"Välju"</string>
+ <string name="wait" msgid="7902715035629500128">"Oodake"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-eu/strings.xml b/packages/CarrierDefaultApp/res/values-eu/strings.xml
new file mode 100644
index 0000000..c1f7fa9
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-eu/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Aktibatu zerbitzua"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Datu-zerbitzua ez dago erabilgarri"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Sakatu zerbitzua aktibatzeko"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Zerbitzua ez dago erabilgarri. Jarri operadorearekin harremanetan."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Sare-zerbitzuaren atarira konektatzen…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Denbora-muga gainditu du sareak. Berriro saiatu nahi duzu?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Sarea ez dago erabilgarri"</string>
+ <string name="quit" msgid="4392968039488794590">"Irten"</string>
+ <string name="wait" msgid="7902715035629500128">"Itxaron"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-gu/strings.xml b/packages/CarrierDefaultApp/res/values-gu/strings.xml
new file mode 100644
index 0000000..c6d7f96
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-gu/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"કૅરિઅર ડિફૉલ્ટ ઍપ્લિકેશન"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"તમારી સેવા સક્રિય કરો"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"કોઈ ડેટા સેવા ઉપલબ્ધ નથી"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"તમારી સેવા સક્રિય કરવા માટે ટૅપ કરો"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"કોઈ સેવા ઉપલબ્ધ નથી, કૃપા કરીને તમારા સેવા પ્રદાતાનો સંપર્ક કરો"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"કૅપ્ટિવ પોર્ટલ સાથે કનેક્ટ થઈ રહ્યું છે..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"નેટવર્કનો સમય સમાપ્ત થયો, શું તમે ફરીથી પ્રયાસ કરશો?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"નેટવર્ક અનુપલબ્ધ"</string>
+ <string name="quit" msgid="4392968039488794590">"છોડી દો"</string>
+ <string name="wait" msgid="7902715035629500128">"રાહ જુઓ"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hi/strings.xml b/packages/CarrierDefaultApp/res/values-hi/strings.xml
new file mode 100644
index 0000000..a1de4bc
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-hi/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"अपनी सेवा सक्रिय करें"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"कोई डेटा सेवा नहीं है"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"अपनी सेवा सक्रिय करने के लिए टैप करें"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"कोई सेवा नहीं है, कृपया आपको सेवा प्रदान करने वाले से संपर्क करें"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"कैप्टिव पोर्टल से कनेक्ट हो रहा है..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"नेटवर्क रुक गया है, क्या आप फिर से कोशिश करना चाहेंगे?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"नेटवर्क उपलब्ध नहीं है"</string>
+ <string name="quit" msgid="4392968039488794590">"बाहर निकलें"</string>
+ <string name="wait" msgid="7902715035629500128">"प्रतीक्षा करें"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hr/strings.xml b/packages/CarrierDefaultApp/res/values-hr/strings.xml
new file mode 100644
index 0000000..d5c05de
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-hr/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"Zadana aplikacija mobilnog operatera"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Aktivirajte uslugu"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Podatkovna usluga nije dostupna"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Dodirnite da biste aktivirali uslugu"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Usluga nije dostupna. Obratite se davatelju usluga."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Povezivanje sa zaštitnim portalom..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Isteklo je vremensko ograničenje mreže. Želite li pokušati ponovo?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Mreža nije dostupna"</string>
+ <string name="quit" msgid="4392968039488794590">"Izlaz"</string>
+ <string name="wait" msgid="7902715035629500128">"Pričekajte"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hu/strings.xml b/packages/CarrierDefaultApp/res/values-hu/strings.xml
new file mode 100644
index 0000000..c204ee0
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-hu/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"A szolgáltatás aktiválása"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Az adatszolgáltatás szünetel"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Koppintson a szolgáltatás aktiválásához"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"A szolgáltatás szünetel. Kérjük, vegye fel a kapcsolatot szolgáltatójával."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Kapcsolódás a hitelesítési portálhoz…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Hálózati időtúllépés. Megpróbálja újra?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"A hálózat nem áll rendelkezésre"</string>
+ <string name="quit" msgid="4392968039488794590">"Kilépés"</string>
+ <string name="wait" msgid="7902715035629500128">"Várakozás"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hy/strings.xml b/packages/CarrierDefaultApp/res/values-hy/strings.xml
new file mode 100644
index 0000000..ac53574
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-hy/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Ակտիվացրեք ձեր ծառայությունը"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Տվյալների ծառայությունն անհասանելի է"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Հպեք՝ ծառայությունն ակտիվացնելու համար"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Ծառայությունն անհասանելի է։ Դիմեք ձեր ծառայություններ մատուցողին"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Միանում է մուտքի էջին…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Ցանցի սպասման ժամանակը սպառվել է։ Փորձե՞լ կրկին։"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Ցանցն անհասանելի է"</string>
+ <string name="quit" msgid="4392968039488794590">"Դուրս գալ"</string>
+ <string name="wait" msgid="7902715035629500128">"Սպասել"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-in/strings.xml b/packages/CarrierDefaultApp/res/values-in/strings.xml
new file mode 100644
index 0000000..15b6849
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-in/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"AplikasiDefaultOperator"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Aktifkan layanan"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Tidak ada layanan data"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Tap untuk mengaktifkan layanan"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Tidak Ada Layanan, hubungi penyedia layanan"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Menyambungkan ke captive portal..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Waktu tunggu jaringan habis, ingin mencoba lagi?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Jaringan tidak tersedia"</string>
+ <string name="quit" msgid="4392968039488794590">"Keluar"</string>
+ <string name="wait" msgid="7902715035629500128">"Tunggu"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-is/strings.xml b/packages/CarrierDefaultApp/res/values-is/strings.xml
new file mode 100644
index 0000000..fb6ca57
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-is/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Virkja þjónustuna"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Ekkert gagnasamband"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Ýttu til að virkja þjónustuna"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Ekkert samband, hafðu samband við þjónustuaðila"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Tengist innskráningarsíðu..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Tímamörk nettengingar runnu út, viltu reyna aftur?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Ekkert net til staðar"</string>
+ <string name="quit" msgid="4392968039488794590">"Hætta"</string>
+ <string name="wait" msgid="7902715035629500128">"Bíða"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-it/strings.xml b/packages/CarrierDefaultApp/res/values-it/strings.xml
new file mode 100644
index 0000000..fca6eb9
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-it/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Attiva il servizio"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Nessun servizio dati"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Tocca per attivare il servizio"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Nessun servizio. Contatta il provider Internet."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Connessione al captive portal…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Timeout di rete. Vuoi riprovare?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Rete non disponibile"</string>
+ <string name="quit" msgid="4392968039488794590">"Esci"</string>
+ <string name="wait" msgid="7902715035629500128">"Attendi"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-iw/strings.xml b/packages/CarrierDefaultApp/res/values-iw/strings.xml
new file mode 100644
index 0000000..60b9168
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-iw/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"הפעל את השירות"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"אין שירות נתונים"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"הקש כדי להפעיל את השירות"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"שירות הנתונים לא זמין. צור קשר עם ספק השירות."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"מתחבר לפורטל שבוי..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"הזמן הקצוב לתפוגת הרשת עבר. תרצה לנסות שוב?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"הרשת אינה זמינה"</string>
+ <string name="quit" msgid="4392968039488794590">"צא"</string>
+ <string name="wait" msgid="7902715035629500128">"המתן"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ka/strings.xml b/packages/CarrierDefaultApp/res/values-ka/strings.xml
new file mode 100644
index 0000000..c191575
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-ka/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"თქვენი სერვისის გააქტიურება"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"მონაცემთა სერვისი არ არის"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"შეეხეთ თქვენი სერვისის გასააქტიურებლად"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"სერვისი არ არის — გთხოვთ, დაუკავშირდეთ სერვისის პროვაიდერს"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"მიმდინარეობს ავტორიზაციის პორტალთან დაკავშირება…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"ქსელის დროის ლიმიტი ამოიწურა. გსურთ ხელახლა ცდა?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"ქსელი მიუწვდომელია"</string>
+ <string name="quit" msgid="4392968039488794590">"გასვლა"</string>
+ <string name="wait" msgid="7902715035629500128">"მოცდა"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-kk/strings.xml b/packages/CarrierDefaultApp/res/values-kk/strings.xml
new file mode 100644
index 0000000..2285fda
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-kk/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Қызметті іске қосу"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Деректер қызметі көрсетілмейді"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Қызметті іске қосу үшін түртіңіз"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Қызмет көрсетілмейді, қызмет провайдеріне хабарласыңыз"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Адаптивті порталға қосылуда…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Желі мерзімі аяқталды, әрекетті қайталайсыз ба?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Желі қолжетімді емес"</string>
+ <string name="quit" msgid="4392968039488794590">"Шығу"</string>
+ <string name="wait" msgid="7902715035629500128">"Күту"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-kn/strings.xml b/packages/CarrierDefaultApp/res/values-kn/strings.xml
new file mode 100644
index 0000000..42fe9a0
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-kn/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"ನಿಮ್ಮ ಸೇವೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"ಯಾವುದೇ ಡೇಟಾ ಸೇವೆ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"ನಿಮ್ಮ ಸೇವೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"ಸೇವೆಯು ಲಭ್ಯವಿಲ್ಲ, ನಿಮಗೆ ಸೇವೆ ಒದಗಿಸುವವರನ್ನು ಸಂಪರ್ಕಿಸಿ"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"ವೈ-ಫೈ ಪ್ರಾರಂಭ ಪೋರ್ಟಲ್ಗೆ ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"ನೆಟ್ವರ್ಕ್ ಕಾಲಾವಧಿ ಮುಗಿದಿದೆ, ನೀವು ಮತ್ತೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಲು ಬಯಸುವಿರಾ?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"ನೆಟ್ವರ್ಕ್ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="quit" msgid="4392968039488794590">"ತ್ಯಜಿಸಿ"</string>
+ <string name="wait" msgid="7902715035629500128">"ನಿರೀಕ್ಷಿಸಿ"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ky/strings.xml b/packages/CarrierDefaultApp/res/values-ky/strings.xml
new file mode 100644
index 0000000..54c9575
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-ky/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"ОператордунДемейкиКолдонмосу"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Кызматты жандырыңыз"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Мобилдик туташуу кызматы жок"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Кызматты жандыруу үчүн таптаңыз"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Байланыш жок, кызмат көрсөтүүчүңүзгө кайрылыңыз"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Кирүү бетине туташууда…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Тармактын күтүү убакыты аяктады, кайра аракет кыласызбы?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Тармак жеткиликтүү эмес"</string>
+ <string name="quit" msgid="4392968039488794590">"Чыгуу"</string>
+ <string name="wait" msgid="7902715035629500128">"Күтүү"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-lo/strings.xml b/packages/CarrierDefaultApp/res/values-lo/strings.xml
new file mode 100644
index 0000000..205d9b6
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-lo/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Activate your service"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"No data service"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Tap to activate your service"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"No Service, please contact your service provider"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Connecting to captive portal..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Network timeout, would you like to retry?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Network unavailable"</string>
+ <string name="quit" msgid="4392968039488794590">"ອອກ"</string>
+ <string name="wait" msgid="7902715035629500128">"ລໍຖ້າ"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-lt/strings.xml b/packages/CarrierDefaultApp/res/values-lt/strings.xml
new file mode 100644
index 0000000..61f6d59
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-lt/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Suaktyvinkite paslaugas"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Duomenų paslaugos neteikiamos"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Palieskite, kad suaktyvintumėte paslaugą"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Paslauga neteikiama. Susisiekite su paslaugos teikėju"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Prisijungiama prie fiksuotojo portalo..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Baigėsi skirtasis tinklo laikas. Ar norite bandyti dar kartą?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Tinklas nepasiekiamas"</string>
+ <string name="quit" msgid="4392968039488794590">"Baigti"</string>
+ <string name="wait" msgid="7902715035629500128">"Laukti"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-mk/strings.xml b/packages/CarrierDefaultApp/res/values-mk/strings.xml
new file mode 100644
index 0000000..066fb31
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-mk/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Активирајте ја услугата"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Нема услуга за подотоци"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Допрете за да ја активирате услугата"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Нема услуга, контактирајте со давателот на услуги"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Се поврзува на порталот за автентикација…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Истече времето на мрежата, дали сакате да се обидете повторно?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Мрежата е недостапна"</string>
+ <string name="quit" msgid="4392968039488794590">"Излези"</string>
+ <string name="wait" msgid="7902715035629500128">"Почекај"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ml/strings.xml b/packages/CarrierDefaultApp/res/values-ml/strings.xml
new file mode 100644
index 0000000..b72e877
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-ml/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"നിങ്ങളുടെ സേവനം ആക്റ്റിവേറ്റ് ചെയ്യുക"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"ഡാറ്റ സേവനം ലഭ്യമല്ല"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"നിങ്ങളുടെ സേവനം ആക്റ്റിവേറ്റ് ചെയ്യാൻ ടാപ്പുചെയ്യുക"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"സേവനം ലഭ്യമല്ല, നിങ്ങളുടെ സേവനദാതാവിനെ ബന്ധപ്പെടുക"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"ക്യാപ്റ്റീവ് പോർട്ടലിലേയ്ക്ക് കണക്റ്റുചെയ്യുന്നു..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"നെറ്റ്വർക്ക് കാലഹരണപ്പെട്ടു, വീണ്ടും ശ്രമിക്കണോ?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"നെറ്റ്വർക്ക് ലഭ്യമല്ല"</string>
+ <string name="quit" msgid="4392968039488794590">"പുറത്തുപോവുക"</string>
+ <string name="wait" msgid="7902715035629500128">"കാത്തിരിക്കുക"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-mn/strings.xml b/packages/CarrierDefaultApp/res/values-mn/strings.xml
new file mode 100644
index 0000000..c262e8f
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-mn/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Үйлчилгээгээ идэвхжүүлнэ үү"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Дата үйлчилгээ алга"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Үйлчилгээгээ идэвхжүүлэхийн тулд товшино уу"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Үйлчилгээ алга. Үйлчилгээ үзүүлэгчтэйгээ холбогдоно уу"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Дамжих порталд холбогдож байна..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Сүлжээний хугацаа дууссан байна. Дахин оролдох уу?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Сүлжээ боломжгүй"</string>
+ <string name="quit" msgid="4392968039488794590">"Гарах"</string>
+ <string name="wait" msgid="7902715035629500128">"Хүлээх"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-mr/strings.xml b/packages/CarrierDefaultApp/res/values-mr/strings.xml
new file mode 100644
index 0000000..beff675
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-mr/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"आपली सेवा सक्रिय करा"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"डेटा सेवा नाही"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"आपली सेवा सक्रिय करण्यासाठी टॅप करा"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"सेवा नाही, कृपया आपल्या सेवा प्रदात्याशी संपर्क साधा"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"कॅप्टिव्ह पोर्टलशी कनेक्ट करत आहे..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"नेटवर्क कालबाह्य झाले, आपण पुन्हा प्रयत्न करू इच्छिता का?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"नेटवर्क अनुपलब्ध"</string>
+ <string name="quit" msgid="4392968039488794590">"बाहेर पडा"</string>
+ <string name="wait" msgid="7902715035629500128">"प्रतीक्षा करा"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-my/strings.xml b/packages/CarrierDefaultApp/res/values-my/strings.xml
new file mode 100644
index 0000000..b0e929e
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-my/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"ဝန်ဆောင်မှုကို စဖွင့်အသုံးပြုရန်"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"ဒေတာချိတ်ဆက်မှုမရှိပါ"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"ဝန်ဆောင်မှုကို စဖွင့်အသုံးပြုရန် တို့ပါ"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"လိုင်းမရှိပါ။ သင့်ဝန်ဆောင်မှုပေးသူကို ဆက်သွယ်ပါ"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"captive portal သို့ ချိတ်ဆက်နေသည်..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"ချိတ်ဆက်မှု ပြတ်တောက်သွားပါသည်။ ထပ်လုပ်လိုပါသလား။"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"ကွန်ရက်ချိတ်ဆက်မှု မရှိပါ"</string>
+ <string name="quit" msgid="4392968039488794590">"ထွက်ရန်"</string>
+ <string name="wait" msgid="7902715035629500128">"စောင့်ရန်"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-nb/strings.xml b/packages/CarrierDefaultApp/res/values-nb/strings.xml
new file mode 100644
index 0000000..6f69e9a
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-nb/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Aktiver tjenesten"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Ingen datatjeneste"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Trykk for å aktivere tjenesten"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Ingen dekning – ta kontakt med tjenesteleverandøren din."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Kobler til obligatorisk side …"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Tidsavbrudd for nettverk – vil du prøve på nytt?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Nettverket er utilgjengelig"</string>
+ <string name="quit" msgid="4392968039488794590">"Avslutt"</string>
+ <string name="wait" msgid="7902715035629500128">"Vent"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ne/strings.xml b/packages/CarrierDefaultApp/res/values-ne/strings.xml
new file mode 100644
index 0000000..aa5766b
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-ne/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"आफ्नो सेवालाई सक्रिय पार्नुहोस्"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"डेटा सेवा छैन"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"आफ्नो सेवालाई सक्रिय पार्न ट्याप गर्नुहोस्"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"सेवा उपलब्ध छैन,कृपया आफ्नो सेवा प्रदायकलाई सम्पर्क गर्नुहोस्"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"क्याप्टिभ पोर्टलमा जडान हुँदैछ..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"नेटवर्कमा जडान हुने समय समाप्त भयो, के तपाईँ पुनः प्रयास गर्न चाहानुहुन्छ?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"नेटवर्क उपलब्ध छैन"</string>
+ <string name="quit" msgid="4392968039488794590">"बाहिरिनुहोस्"</string>
+ <string name="wait" msgid="7902715035629500128">"पर्खनुहोस्"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-nl/strings.xml b/packages/CarrierDefaultApp/res/values-nl/strings.xml
new file mode 100644
index 0000000..b7da335
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-nl/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"De service activeren"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Geen gegevensservice"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Tik om de service te activeren"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Geen service. Neem contact op met je serviceprovider."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Verbinding maken met captive portal..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Time-out voor het netwerk. Wil je het opnieuw proberen?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Netwerk niet beschikbaar"</string>
+ <string name="quit" msgid="4392968039488794590">"Stoppen"</string>
+ <string name="wait" msgid="7902715035629500128">"Wachten"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pa/strings.xml b/packages/CarrierDefaultApp/res/values-pa/strings.xml
new file mode 100644
index 0000000..ea07e88
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-pa/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"ਆਪਣੀ ਸੇਵਾ ਨੂੰ ਸਰਗਰਮ ਕਰੋ"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"ਕੋਈ ਡੈਟਾ ਸੇਵਾ ਨਹੀਂ"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"ਆਪਣੀ ਸੇਵਾ ਨੂੰ ਸਰਗਰਮ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"ਕੋਈ ਸੇਵਾ ਨਹੀਂ, ਕਿਰਪਾ ਕਰਕੇ ਆਪਣੇ ਸੇਵਾ ਪ੍ਰਦਾਨਕ ਨੂੰ ਸੰਪਰਕ ਕਰੋ"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"ਕੈਪਟਿਵ ਪੋਰਟਲ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"ਨੈੱਟਵਰਕ ਦਾ ਸਮਾਂ ਸਮਾਪਤ ਹੋ ਗਿਆ, ਕੀ ਤੁਸੀਂ ਮੁੜ-ਕੋਸ਼ਿਸ਼ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+ <string name="quit" msgid="4392968039488794590">"ਛੱਡੋ"</string>
+ <string name="wait" msgid="7902715035629500128">"ਉਡੀਕ ਕਰੋ"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..e730dc6
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Ative seu serviço"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Sem serviço de dados"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Toque para ativar seu serviço"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Sem serviço. Entre em contato com seu provedor de serviços"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Conectando-se ao portal cativo…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Tempo limite de rede. Deseja tentar novamente?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Rede indisponível"</string>
+ <string name="quit" msgid="4392968039488794590">"Sair"</string>
+ <string name="wait" msgid="7902715035629500128">"Esperar"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml b/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..a2c10e9
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Ativar o seu serviço"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Sem serviço de dados"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Toque para ativar o seu serviço"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Sem serviço. Contacte o seu fornecedor de serviços"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"A ligar ao portal cativo…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"O limite de tempo da rede foi excedido. Pretende tentar novamente?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Rede não disponível"</string>
+ <string name="quit" msgid="4392968039488794590">"Sair"</string>
+ <string name="wait" msgid="7902715035629500128">"Aguardar"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt/strings.xml b/packages/CarrierDefaultApp/res/values-pt/strings.xml
new file mode 100644
index 0000000..e730dc6
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-pt/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Ative seu serviço"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Sem serviço de dados"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Toque para ativar seu serviço"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Sem serviço. Entre em contato com seu provedor de serviços"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Conectando-se ao portal cativo…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Tempo limite de rede. Deseja tentar novamente?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Rede indisponível"</string>
+ <string name="quit" msgid="4392968039488794590">"Sair"</string>
+ <string name="wait" msgid="7902715035629500128">"Esperar"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ro/strings.xml b/packages/CarrierDefaultApp/res/values-ro/strings.xml
new file mode 100644
index 0000000..234c410
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-ro/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"AplicațiePrestabilităOperator"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Activați serviciul"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Fără serviciu de date"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Atingeți pentru a activa serviciul"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Fără serviciu. Contactați furnizorul de servicii."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Se conectează la portalul captiv..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Timpul limită pentru rețea a expirat. Doriți să încercați din nou?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Rețea indisponibilă"</string>
+ <string name="quit" msgid="4392968039488794590">"Ieșiți"</string>
+ <string name="wait" msgid="7902715035629500128">"Așteptați"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ru/strings.xml b/packages/CarrierDefaultApp/res/values-ru/strings.xml
new file mode 100644
index 0000000..b038f0f
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-ru/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Активируйте сервис"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Нет подключения"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Нажмите, чтобы активировать сервис"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Обратитесь к своему поставщику услуг Интернета."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Подключение к странице входа…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Время ожидания для сети истекло. Повторить попытку?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Сеть недоступна"</string>
+ <string name="quit" msgid="4392968039488794590">"Закрыть"</string>
+ <string name="wait" msgid="7902715035629500128">"Подождать"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sk/strings.xml b/packages/CarrierDefaultApp/res/values-sk/strings.xml
new file mode 100644
index 0000000..a73e55c
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-sk/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"Predvolená aplikácia operátora"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Aktivujte službu"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Žiadna dátová služba"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Klepnutím aktivujete službu"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Žiadna služba, kontaktujte svojho poskytovateľa služieb"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Pripájanie k prihlasovaciemu portálu…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Vypršal časový limit siete. Chcete to skúsiť znova?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Sieť nie je k dispozícii"</string>
+ <string name="quit" msgid="4392968039488794590">"Ukončiť"</string>
+ <string name="wait" msgid="7902715035629500128">"Počkať"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sq/strings.xml b/packages/CarrierDefaultApp/res/values-sq/strings.xml
new file mode 100644
index 0000000..316fe1d
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-sq/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Aktivizo shërbimin"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Nuk ka shërbim për të dhënat"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Trokit për të aktivizuar shërbimin"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Nuk ka shërbim, kontakto me ofruesin e shërbimit"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Po lidhet me portalin e izoluar..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Koha e pritjes së rrjetit përfundoi. Dëshiron të provosh sërish?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Rrjeti është i padisponueshëm"</string>
+ <string name="quit" msgid="4392968039488794590">"Dil"</string>
+ <string name="wait" msgid="7902715035629500128">"Prit"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sr/strings.xml b/packages/CarrierDefaultApp/res/values-sr/strings.xml
new file mode 100644
index 0000000..5c381f4
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-sr/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Активирајте услугу"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Нема услуге преноса података"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Додирните да бисте активирали услугу"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Нема услуге. Контактирајте добављача услуге"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Повезује се са улазним порталом…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Временско ограничење мреже је истекло. Желите ли да пробате поново?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Мрежа није доступна"</string>
+ <string name="quit" msgid="4392968039488794590">"Затвори"</string>
+ <string name="wait" msgid="7902715035629500128">"Чекај"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sw/strings.xml b/packages/CarrierDefaultApp/res/values-sw/strings.xml
new file mode 100644
index 0000000..dfcc5c4
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-sw/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Anzisha huduma yako"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Hakuna huduma ya data"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Gonga ili uanzishe huduma yako"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Hakuna Huduma, tafadhali wasiliana na mtoa huduma wako"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Inaunganisha kwenye ukurasa wa mwanzo..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Muda wa kutumia mtandao umekwisha, ungependa kujaribu tena?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Mtandao haupatikani"</string>
+ <string name="quit" msgid="4392968039488794590">"Ondoka"</string>
+ <string name="wait" msgid="7902715035629500128">"Subiri"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-te/strings.xml b/packages/CarrierDefaultApp/res/values-te/strings.xml
new file mode 100644
index 0000000..70fe499
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-te/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"మీ సేవని సక్రియం చేయండి"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"డేటా సేవ లేదు"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"మీ సేవని సక్రియం చేయడానికి నొక్కండి"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"సేవ లేదు, దయచేసి మీ సేవా ప్రదాతను సంప్రదించండి"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"క్యాప్టివ్ పోర్టల్కు కనెక్ట్ చేస్తోంది..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"నెట్వర్క్ గడువు సమయం ముగిసింది, మీరు మళ్లీ ప్రయత్నించాలనుకుంటున్నారా?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"నెట్వర్క్ అందుబాటులో లేదు"</string>
+ <string name="quit" msgid="4392968039488794590">"నిష్క్రమించు"</string>
+ <string name="wait" msgid="7902715035629500128">"వేచి ఉండండి"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-th/strings.xml b/packages/CarrierDefaultApp/res/values-th/strings.xml
new file mode 100644
index 0000000..66526ea
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-th/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"เปิดใช้งานบริการของคุณ"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"ไม่มีบริการข้อมูล"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"แตะเพื่อเปิดใช้งานบริการ"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"ไม่มีบริการ โปรดติดต่อผู้ให้บริการ"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"กำลังเชื่อมต่อแคพทีฟพอร์ทัล..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"หมดเวลาเชื่อมต่อเครือข่ายแล้ว ลองอีกครั้งไหม"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"เครือข่ายใช้ไม่ได้"</string>
+ <string name="quit" msgid="4392968039488794590">"ปิด"</string>
+ <string name="wait" msgid="7902715035629500128">"รอ"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ur/strings.xml b/packages/CarrierDefaultApp/res/values-ur/strings.xml
new file mode 100644
index 0000000..3002d40
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-ur/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"اپنی سروس فعال کریں"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"ڈیٹا سروس موجود نہیں"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"اپنی سروس فعال کرنے کے لیے تھپتھپائیں"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"سروس موجود نہیں، براہ کرم اپنے خدمت کے فراہم کنندہ سے رابطہ کریں"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"کیپٹو پورٹل سے منسلک ہو رہا ہے..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"نیٹ ورک ٹائم آؤٹ، کیا آپ دوبارہ کوشش کرنا چاہیں گے؟"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"نیٹ ورک غیر دستیاب ہے"</string>
+ <string name="quit" msgid="4392968039488794590">"چھوڑ دیں"</string>
+ <string name="wait" msgid="7902715035629500128">"انتظار کریں"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-uz/strings.xml b/packages/CarrierDefaultApp/res/values-uz/strings.xml
new file mode 100644
index 0000000..92a0af0
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-uz/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Xizmatni faollashtiring"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Aloqa yo‘q"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Xizmatni faollashtirish uchun bosing"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Xizmat yo‘q. Xizmat ta’minotchisi bilan bog‘laning."</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Kirish sahifasida haqiqiylik tekshiruvi bor tarmoqqa ulanilmoqda…"</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Tarmoq uchun kutish vaqti tugadi. Qayta urinilsinmi?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Tarmoq mavjud emas"</string>
+ <string name="quit" msgid="4392968039488794590">"Chiqish"</string>
+ <string name="wait" msgid="7902715035629500128">"Kutish"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-vi/strings.xml b/packages/CarrierDefaultApp/res/values-vi/strings.xml
new file mode 100644
index 0000000..06ea6f0
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-vi/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Kích hoạt dịch vụ của bạn"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Không có dịch vụ dữ liệu"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Nhấn để kích hoạt dịch vụ của bạn"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Không có dịch vụ, vui lòng liên hệ với nhà cung cấp dịch vụ của bạn"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Đang kết nối với cổng cố định..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Hết thời gian chờ mạng, bạn có muốn thử lại không?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Mạng không khả dụng"</string>
+ <string name="quit" msgid="4392968039488794590">"Thoát"</string>
+ <string name="wait" msgid="7902715035629500128">"Đợi"</string>
+</resources>
diff --git a/packages/CarrierDefaultApp/res/values-zu/strings.xml b/packages/CarrierDefaultApp/res/values-zu/strings.xml
new file mode 100644
index 0000000..12e8a7d
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/values-zu/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
+ <string name="portal_notification_id" msgid="267536768510843288">"Sebenzisa isevisi yakho"</string>
+ <string name="no_data_notification_id" msgid="5216950045164991172">"Ayikho isevisi yedatha"</string>
+ <string name="portal_notification_detail" msgid="2860620550281695686">"Thepha ukuze usebenzise isevisi yakho"</string>
+ <string name="no_data_notification_detail" msgid="1757413358517680719">"Ayikho isevisi, sicela uxhumane nomhlinzeki wakho wesevisi"</string>
+ <string name="progress_dialogue_network_connection" msgid="4964125154591905581">"Ixhuma kuphothali yabathunjiweyo..."</string>
+ <string name="alert_dialogue_network_timeout" msgid="4515760047815901797">"Ukuphela kwesikhathi senethiwekhi, ungathanda ukuzama futhi?"</string>
+ <string name="alert_dialogue_network_timeout_title" msgid="3117252205484891837">"Inethiwekhi ayitholakali"</string>
+ <string name="quit" msgid="4392968039488794590">"Yeka"</string>
+ <string name="wait" msgid="7902715035629500128">"Linda"</string>
+</resources>
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/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
index 91c943d..8cdb906 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -290,6 +290,7 @@
monitor.reportFailedStrongAuthUnlockAttempt(userId);
mLockPatternUtils.reportFailedPasswordAttempt(userId);
if (timeoutMs > 0) {
+ mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
showTimeoutDialog(userId, timeoutMs);
}
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0a7bdbf..80d4a26 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -26,7 +26,6 @@
import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.BatteryManager.EXTRA_STATUS;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import android.app.ActivityManager;
import android.app.AlarmManager;
@@ -106,12 +105,6 @@
private static final String ACTION_FACE_UNLOCK_STOPPED
= "com.android.facelock.FACE_UNLOCK_STOPPED";
- private static final String ACTION_STRONG_AUTH_TIMEOUT =
- "com.android.systemui.ACTION_STRONG_AUTH_TIMEOUT";
- private static final String USER_ID = "com.android.systemui.USER_ID";
-
- private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-
// Callback messages
private static final int MSG_TIME_UPDATE = 301;
private static final int MSG_BATTERY_UPDATE = 302;
@@ -203,7 +196,6 @@
private boolean mDeviceInteractive;
private boolean mScreenOn;
private SubscriptionManager mSubscriptionManager;
- private AlarmManager mAlarmManager;
private List<SubscriptionInfo> mSubscriptionInfo;
private TrustManager mTrustManager;
private UserManager mUserManager;
@@ -588,26 +580,12 @@
}
public void reportSuccessfulStrongAuthUnlockAttempt() {
- scheduleStrongAuthTimeout();
if (mFpm != null) {
byte[] token = null; /* TODO: pass real auth token once fp HAL supports it */
mFpm.resetTimeout(token);
}
}
- private void scheduleStrongAuthTimeout() {
- final DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- long when = SystemClock.elapsedRealtime() + dpm.getRequiredStrongAuthTimeout(null,
- sCurrentUser);
- Intent intent = new Intent(ACTION_STRONG_AUTH_TIMEOUT);
- intent.putExtra(USER_ID, sCurrentUser);
- PendingIntent sender = PendingIntent.getBroadcast(mContext,
- sCurrentUser, intent, PendingIntent.FLAG_CANCEL_CURRENT);
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, sender);
- notifyStrongAuthStateChanged(sCurrentUser);
- }
-
private void notifyStrongAuthStateChanged(int userId) {
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -723,17 +701,6 @@
}
};
- private final BroadcastReceiver mStrongAuthTimeoutReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_STRONG_AUTH_TIMEOUT.equals(intent.getAction())) {
- int userId = intent.getIntExtra(USER_ID, -1);
- mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, userId);
- notifyStrongAuthStateChanged(userId);
- }
- }
- };
-
private final FingerprintManager.LockoutResetCallback mLockoutResetCallback
= new FingerprintManager.LockoutResetCallback() {
@Override
@@ -1034,7 +1001,6 @@
private KeyguardUpdateMonitor(Context context) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
- mAlarmManager = context.getSystemService(AlarmManager.class);
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context);
@@ -1094,10 +1060,6 @@
e.rethrowAsRuntimeException();
}
- IntentFilter strongAuthTimeoutFilter = new IntentFilter();
- strongAuthTimeoutFilter.addAction(ACTION_STRONG_AUTH_TIMEOUT);
- context.registerReceiver(mStrongAuthTimeoutReceiver, strongAuthTimeoutFilter,
- PERMISSION_SELF, null /* handler */);
mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE);
mTrustManager.registerTrustListener(this);
mLockPatternUtils = new LockPatternUtils(context);
diff --git a/packages/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml
index d108c20..f36167e 100644
--- a/packages/PrintSpooler/res/values-hi/strings.xml
+++ b/packages/PrintSpooler/res/values-hi/strings.xml
@@ -26,10 +26,10 @@
<string name="label_color" msgid="1108690305218188969">"रंग"</string>
<string name="label_duplex" msgid="5370037254347072243">"दो-तरफ़ा"</string>
<string name="label_orientation" msgid="2853142581990496477">"अभिविन्यास"</string>
- <string name="label_pages" msgid="7768589729282182230">"पृष्ठ"</string>
+ <string name="label_pages" msgid="7768589729282182230">"पेज"</string>
<string name="destination_default_text" msgid="5422708056807065710">"कोई प्रिंटर चुनें"</string>
<string name="template_all_pages" msgid="3322235982020148762">"सभी <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
- <string name="template_page_range" msgid="428638530038286328">"पृष्ठ संख्या <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
+ <string name="template_page_range" msgid="428638530038286328">"पेज संख्या <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
<string name="pages_range_example" msgid="8558694453556945172">"उदा. 1—5,8,11—13"</string>
<string name="print_preview" msgid="8010217796057763343">"प्रिंट पूर्वावलोकन"</string>
<string name="install_for_print_preview" msgid="6366303997385509332">"पूर्वावलोकन के लिए PDF व्यूअर इंस्टॉल करें"</string>
@@ -39,7 +39,7 @@
<string name="all_printers" msgid="5018829726861876202">"सभी प्रिंटर..."</string>
<string name="print_dialog" msgid="32628687461331979">"प्रिंट डॉयलॉग"</string>
<string name="current_page_template" msgid="5145005201131935302">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g>/<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
- <string name="page_description_template" msgid="6831239682256197161">"<xliff:g id="PAGE_COUNT">%2$d</xliff:g> में से पृष्ठ <xliff:g id="CURRENT_PAGE">%1$d</xliff:g>"</string>
+ <string name="page_description_template" msgid="6831239682256197161">"<xliff:g id="PAGE_COUNT">%2$d</xliff:g> में से पेज <xliff:g id="CURRENT_PAGE">%1$d</xliff:g>"</string>
<string name="summary_template" msgid="8899734908625669193">"सारांश, प्रतियां <xliff:g id="COPIES">%1$s</xliff:g>, काग़ज़ का आकार <xliff:g id="PAPER_SIZE">%2$s</xliff:g>"</string>
<string name="expand_handle" msgid="7282974448109280522">"हैंडल विस्तृत करें"</string>
<string name="collapse_handle" msgid="6886637989442507451">"हैंडल संक्षिप्त करें"</string>
diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk
index ad07283..67ef40a 100644
--- a/packages/SettingsLib/Android.mk
+++ b/packages/SettingsLib/Android.mk
@@ -12,6 +12,8 @@
android-support-v7-appcompat \
android-support-v14-preference
+LOCAL_STATIC_JAVA_LIBRARY := legacy-android-test
+
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_JAR_EXCLUDE_FILES := none
@@ -21,4 +23,4 @@
include $(BUILD_STATIC_JAVA_LIBRARY)
# For the test package.
-include $(call all-makefiles-under, $(LOCAL_PATH))
\ No newline at end of file
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_root.xml b/packages/SettingsLib/res/layout/access_point_friction_widget.xml
similarity index 62%
copy from packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_root.xml
copy to packages/SettingsLib/res/layout/access_point_friction_widget.xml
index 85573e0..7409686 100644
--- a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_root.xml
+++ b/packages/SettingsLib/res/layout/access_point_friction_widget.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 The Android Open Source Project
+<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,11 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.3"
- android:valueTo="1.0"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
+
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/friction_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:contentDescription="@null" />
diff --git a/packages/SettingsLib/res/layout/settings_with_drawer.xml b/packages/SettingsLib/res/layout/settings_with_drawer.xml
index 24a5422..b659cee 100644
--- a/packages/SettingsLib/res/layout/settings_with_drawer.xml
+++ b/packages/SettingsLib/res/layout/settings_with_drawer.xml
@@ -26,12 +26,12 @@
android:orientation="vertical"
android:fitsSystemWindows="true">
<FrameLayout
+ style="?android:attr/actionBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?android:attr/actionBarTheme">
<Toolbar
android:id="@+id/action_bar"
- style="?android:attr/actionBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:navigationContentDescription="@*android:string/action_bar_up_description"/>
diff --git a/packages/SettingsLib/res/values-af/arrays.xml b/packages/SettingsLib/res/values-af/arrays.xml
index 4cb349c..b120ffe 100644
--- a/packages/SettingsLib/res/values-af/arrays.xml
+++ b/packages/SettingsLib/res/values-af/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Gebruik HDCP-kontrolering net vir DRM-inhoud"</item>
<item msgid="45075631231212732">"Gebruik altyd HDCP-kontrolering"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Verstek"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Verstek"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Verstek"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Verstek"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Verstek"</item>
- <item msgid="5618929009984956469">"16 bis per voorbeeld"</item>
- <item msgid="3412640499234627248">"24 bis per voorbeeld"</item>
- <item msgid="121583001492929387">"32 bis per voorbeeld"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Verstek"</item>
- <item msgid="4726688794884191540">"16 bis per voorbeeld"</item>
- <item msgid="305344756485516870">"24 bis per voorbeeld"</item>
- <item msgid="244568657919675099">"32 bis per voorbeeld"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Verstek"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Verstek"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Klankgehalte verkies (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Standaard (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Verbinding verkies (330 kbps/303 kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Klankgehalte verkies (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Standaard (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Verbinding verkies (330 kbps/303 kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Af"</item>
<item msgid="1593289376502312923">"64 K"</item>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 3672fa9..a097b18 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Sellulêre data altyd aktief"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Deaktiveer absolute volume"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth-oudiokodek"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Kies Bluetooth-A2DP-kodek wat verkies word"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth-oudiovoorbeeldkoers"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Kies Bluetooth-A2DP-kodekvoorbeeldkoers wat verkies word"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth-oudiobisse per voorbeeld"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Kies die Bluetooth-A2DP-kodekbisse per voorbeeld wat verkies word"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth-oudiokanaalmodus"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Kies Bluetooth-A2DP-kodekkanaalmodus wat verkies word"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth-oudio-LDAC-speelgehalte"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Kies Bluetooth-A2DP-kodek-LDAC-speelgehalte wat verkies word"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Wys opsies vir draadlose skermsertifisering"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Verhoog Wi-Fi-aantekeningvlak, wys per SSID RSSI in Wi‑Fi-kieser"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Wanneer dit geaktiveer is, sal Wi-Fi meer aggressief wees om die dataverbinding na selfoon oor te dra wanneer die Wi-Fi-sein swak is"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Hulp en terugvoer"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Kieslys"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"MGT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Voer wagwoord in om fabriekterugstelling in demonstrasiemodus uit te voer"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Volgende"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Wagwoord word benodig"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-am/arrays.xml b/packages/SettingsLib/res/values-am/arrays.xml
index 0b424b3..8746cc8 100644
--- a/packages/SettingsLib/res/values-am/arrays.xml
+++ b/packages/SettingsLib/res/values-am/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"ሁልጊዜ የHDCP ምልከታ ተጠቀም"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ነባሪ"</item>
+ <item msgid="7065842274271279580">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ነባሪ"</item>
+ <item msgid="5062108632402595000">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ነባሪ"</item>
+ <item msgid="3093023430402746802">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item>
<item msgid="8895532488906185219">"44.1 ኪኸ"</item>
<item msgid="2909915718994807056">"48.0 ኪኸ"</item>
<item msgid="3347287377354164611">"88.2 ኪኸ"</item>
<item msgid="1234212100239985373">"96.0 ኪኸ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ነባሪ"</item>
+ <item msgid="3214516120190965356">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item>
<item msgid="4482862757811638365">"44.1 ኪኸ"</item>
<item msgid="354495328188724404">"48.0 ኪኸ"</item>
<item msgid="7329816882213695083">"88.2 ኪኸ"</item>
<item msgid="6967397666254430476">"96.0 ኪኸ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ነባሪ"</item>
+ <item msgid="2684127272582591429">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item>
<item msgid="5618929009984956469">"16 ቢት/ናሙና"</item>
<item msgid="3412640499234627248">"24 ቢት/ናሙና"</item>
<item msgid="121583001492929387">"32 ቢት/ናሙና"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ነባሪ"</item>
+ <item msgid="1081159789834584363">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item>
<item msgid="4726688794884191540">"16 ቢት/ናሙና"</item>
<item msgid="305344756485516870">"24 ቢት/ናሙና"</item>
<item msgid="244568657919675099">"32 ቢት/ናሙና"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ነባሪ"</item>
+ <item msgid="5226878858503393706">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item>
<item msgid="4106832974775067314">"ሞኖ"</item>
<item msgid="5571632958424639155">"ስቲሪዮ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ነባሪ"</item>
+ <item msgid="4118561796005528173">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item>
<item msgid="8900559293912978337">"ሞኖ"</item>
<item msgid="8883739882299884241">"ስቲሪዮ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"የሚመረጠው የድምጽ ጥራት (990ኪቢ/ሴ/909ኪቢ/ሴ)"</item>
- <item msgid="138837449700903545">"መደበኛ (660ኪቢ/ሴ/606ኪቢ/ሴ)"</item>
- <item msgid="4777177307869441982">"የሚመረጠው ግንኙነት (330ኪቢ/ሴ/303ኪቢ/ሴ)"</item>
+ <item msgid="3411577996960199959">"ለኦዲዮ ጥራት አትባ (990 ኪቢ/ሴ / 909 ኪቢ/ሴ)"</item>
+ <item msgid="2921767058740704969">"ለኦዲዮ ጥራት አትባ (660 ኪቢ/ሴ / 606 ኪቢ/ሴ)"</item>
+ <item msgid="3682554248829489641">"ለኦዲዮ ጥራት አትባ (330 ኪቢ/ሴ / 303 ኪቢ/ሴ)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"የሚመረጠው የድምጽ ጥራት (990ኪቢ/ሴ/909ኪቢ/ሴ)"</item>
- <item msgid="9091111147684472529">"መደበኛ (660ኪቢ/ሴ/606ኪቢ/ሴ)"</item>
- <item msgid="3367904477834831032">"የሚመረጠው ግንኙነት (330ኪቢ/ሴ/303ኪቢ/ሴ)"</item>
+ <item msgid="7668834469173465015">"የኦዲዮ ጥራት አትባ"</item>
+ <item msgid="4327143584633311908">"የተመጣጠነ የኦዲዮ እና ግንኙነት ጥራት"</item>
+ <item msgid="6155648878105378550">"ለግንኙነት ጥራት አትባ"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ጠፍቷል"</item>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 42783ec..7725827 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"የተንቀስቃሽ ስልክ ውሂብ ሁልጊዜ ንቁ"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"ፍጹማዊ ድምፅን አሰናክል"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"የብሉቱዝ ኦዲዮ ኮዴክ"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"የሚመረጠውን የብሉቱዝ A2DP ኮዴክ ምረጥ"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"የብሉቱዝ ኦዲዮ ኮዴክ ይምረጡ"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"የብሉቱዝ ኦዲዮ ናሙና ፍጥነት"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"የሚመረጠውን የብሉቱዝ A2DP ኮዴክ ናሙና ፍጥነት ምረጥ"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"የብሉቱዝ ኦዲዮ ኮዴክ ይምረጡ፦\nየናሙና ፍጥነት"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"የብሉቱዝ ኦዲዮ ቢት በናሙና"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"የሚመረጠውን የብሉቱዝ A2DP ኮዴክ ቢት በናሙና ምረጥ"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"የብሉቱዝ ኦዲዮ ኮዴክ ይምረጡ፦\nቢት በናሙና"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"የብሉቱዝ ኦዲዮ ሰርጥ ሁነታ"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"የሚመረጠውን የብሉቱዝ A2DP ኮዴክ ሰርጥ ሁነታ ምረጥ"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"የብሉቱዝ ኦዲዮ LDAC መልሶ ማጫወት ጥራት"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"የሚመረጠውን የብሉቱዝ A2DP ኮዴክ LDAC መልሶ ማጫወት ጥራት ይምረጡ"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"የብሉቱዝ ኦዲዮ ኮዴክ ይምረጡ፦\nየሰርጥ ሁነታ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"የብሉቱዝ ኦዲዮ LDAC ኮዴክ ይምረጡ፦ የመልሶ ማጫወት ጥራት"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"የብሉቱዝ ኦዲዮ LDAC ኮዴክ ይምረጡ፦\nየመልሶ ማጫወት ጥራት"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ዥረት፦ <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"የገመድ አልባ ማሳያ እውቅና ማረጋገጫ አማራጮችን አሳይ"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"የWi‑Fi ምዝግብ ማስታወሻ አያያዝ ደረጃ ጨምር፣ በWi‑Fi መምረጫ ውስጥ በአንድ SSID RSSI አሳይ"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"ሲነቃ የWi‑Fi ምልክት ዝቅተኛ ሲሆን Wi‑Fi የውሂብ ግንኙነት ለተንቀሳቃሽ ማስረከብ ላይ ይበልጥ አስገዳጅ ይሆናል"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"እገዛ እና ግብረመልስ"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"ምናሌ"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"ጂኤምቲ"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"የፋብሪካ ዳግም ማስጀመር በማሳያ ሁነታ ውስጥ ለማከናወን የይለፍ ቃል ያስገቡ"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"ቀጣይ"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"የይለፍ ቃል ያስፈልጋል"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ar/arrays.xml b/packages/SettingsLib/res/values-ar/arrays.xml
index ae61f64..e7b778c 100644
--- a/packages/SettingsLib/res/values-ar/arrays.xml
+++ b/packages/SettingsLib/res/values-ar/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"استخدام التحقق من HDCP دومًا"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"افتراضي"</item>
+ <item msgid="7065842274271279580">"استخدام اختيار النظام (افتراضي)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"افتراضي"</item>
+ <item msgid="5062108632402595000">"استخدام اختيار النظام (افتراضي)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"افتراضي"</item>
+ <item msgid="3093023430402746802">"استخدام اختيار النظام (افتراضي)"</item>
<item msgid="8895532488906185219">"44.1 كيلو هرتز"</item>
<item msgid="2909915718994807056">"48.0 كيلو هرتز"</item>
<item msgid="3347287377354164611">"88.2 كيلو هرتز"</item>
<item msgid="1234212100239985373">"96.0 كيلو هرتز"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"افتراضي"</item>
+ <item msgid="3214516120190965356">"استخدام اختيار النظام (افتراضي)"</item>
<item msgid="4482862757811638365">"44.1 كيلو هرتز"</item>
<item msgid="354495328188724404">"48.0 كيلو هرتز"</item>
<item msgid="7329816882213695083">"88.2 كيلو هرتز"</item>
<item msgid="6967397666254430476">"96.0 كيلو هرتز"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"افتراضي"</item>
+ <item msgid="2684127272582591429">"استخدام اختيار النظام (افتراضي)"</item>
<item msgid="5618929009984956469">"16 بت لكل عيّنة"</item>
<item msgid="3412640499234627248">"24 بت لكل عيّنة"</item>
<item msgid="121583001492929387">"32 بت لكل عيّنة"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"افتراضي"</item>
+ <item msgid="1081159789834584363">"استخدام اختيار النظام (افتراضي)"</item>
<item msgid="4726688794884191540">"16 بت لكل عيّنة"</item>
<item msgid="305344756485516870">"24 بت لكل عيّنة"</item>
<item msgid="244568657919675099">"32 بت لكل عيّنة"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"افتراضي"</item>
+ <item msgid="5226878858503393706">"استخدام اختيار النظام (افتراضي)"</item>
<item msgid="4106832974775067314">"أحادي"</item>
<item msgid="5571632958424639155">"استريو"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"افتراضي"</item>
+ <item msgid="4118561796005528173">"استخدام اختيار النظام (افتراضي)"</item>
<item msgid="8900559293912978337">"أحادي"</item>
<item msgid="8883739882299884241">"استريو"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"جودة الصوت المفضّلة (990/909 كيلوبت في الثانية)"</item>
- <item msgid="138837449700903545">"قياسي (660/606 كيلوبت في الثانية)"</item>
- <item msgid="4777177307869441982">"الاتصال المفضّل (330/303 كيلوبت في الثانية)"</item>
+ <item msgid="3411577996960199959">"تحسين جودة الصوت (990 كيلوبت في الثانية/909 كيلوبت في الثانية)"</item>
+ <item msgid="2921767058740704969">"جودة متوازنة للصوت والاتصال (660 كيلوبت في الثانية/606 كيلوبت في الثانية)"</item>
+ <item msgid="3682554248829489641">"تحسين جودة الاتصال (330 كيلوبت في الثانية/303 كيلوبت في الثانية)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"جودة الصوت المفضّلة (990/909 كيلوبت في الثانية)"</item>
- <item msgid="9091111147684472529">"قياسي (660/606 كيلوبت في الثانية)"</item>
- <item msgid="3367904477834831032">"الاتصال المفضّل (330/303 كيلوبت في الثانية)"</item>
+ <item msgid="7668834469173465015">"تحسين جودة الصوت"</item>
+ <item msgid="4327143584633311908">"جودة متوازنة للصوت والاتصال"</item>
+ <item msgid="6155648878105378550">"تحسين جودة الاتصال"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"إيقاف"</item>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index e1b39c11..fa7e253 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"بيانات الجوّال نشطة دائمًا"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"تعطيل مستوى الصوت المطلق"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"ترميز صوت بلوتوث"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"حدّد ترميز بلوتوث A2DP المفضّل"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"اختيار برنامج الترميز لصوت البلوتوث"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"معدّل عيّنة صوت بلوتوث"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"حدّد معدّل عيّنة ترميز A2DP بلوتوث المفضّل"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"اختيار برنامج ترميز صوت البلوتوث:\nمعدل العينة"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"وحدات البت لكل عيّنة في صوت بلوتوث"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"حدّد عدد وحدات البت لكل عيّنة المفضّل في ترميز بلوتوث A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"اختيار برنامج ترميز صوت البلوتوث:\nوحدات بت لكل عينة"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"وضع قناة صوت بلوتوث"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"حدّد وضع القناة المفضّل في ترميز بلوتوث A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"جودة تشغيل صوت بلوتوث LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"حدّد جودة التشغيل المفضّلة في ترميز بلوتوث A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"اختيار برنامج ترميز صوت البلوتوث:\nوضع القناة"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"برنامج ترميز LDAC لصوت البلوتوث: جودة التشغيل"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"اختيار برنامج ترميز LDAC لصوت البلوتوث:\nجودة التشغيل"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"البث: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"عرض خيارات شهادة عرض شاشة لاسلكي"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"زيادة مستوى تسجيل Wi-Fi، وعرض لكل SSID RSSI في منتقي Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"عند تمكينه، سيكون Wi-Fi أكثر حدة في تسليم اتصال البيانات إلى الشبكة الخلوية، وذلك عندما تكون إشارة WiFi منخفضة"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"المساعدة والتعليقات"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"القائمة"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"غرينيتش"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"إدخال كلمة المرور لإعادة الضبط بحسب بيانات المصنع في الوضع التجريبي"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"التالي"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"يلزم توفر كلمة مرور"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-az/arrays.xml b/packages/SettingsLib/res/values-az/arrays.xml
index 38b2dc0..9ad0c28 100644
--- a/packages/SettingsLib/res/values-az/arrays.xml
+++ b/packages/SettingsLib/res/values-az/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Yalnız DRM məzmun oxumaq üçün HDCP istifadə edin"</item>
<item msgid="45075631231212732">"Həmişə HDCP yoxlama istifadə edin"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Defolt"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Defolt"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Defolt"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Defolt"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Defolt"</item>
- <item msgid="5618929009984956469">"16 bit/nümunə"</item>
- <item msgid="3412640499234627248">"24 bit/nümunə"</item>
- <item msgid="121583001492929387">"32 bit/nümunə"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Defolt"</item>
- <item msgid="4726688794884191540">"16 bit/nümunə"</item>
- <item msgid="305344756485516870">"24 bit/nümunə"</item>
- <item msgid="244568657919675099">"32 bit/nümunə"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Defolt"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Defolt"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Səs keyfiyyəti tərcih edildi (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Standart (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Bağlantı tərcih edildi (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Səs keyfiyyəti tərcih edildi (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Standart (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Bağlantı tərcih edildi (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Deaktiv"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index e625e32..db58c5c 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobil data həmişə aktivdir"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Mütləq səs həcmi deaktiv edin"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Audio Kodek"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Tərcih edilmiş Bluetooth A2DP Kodek seçin"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth Audio Nümunə Göstəricisi"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Tərcih edilmiş Bluetooth A2DP Kodek Nümunə Göstəricisi seçin"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Hər Nümunə Üçün Bluetooth Audio Bit"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Hər nümunə üçün tərcih edilmiş Bluetooth A2DP Kodek Bit seçin"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth Audio Kanal Rejimi"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Tərcih edilmiş Bluetooth A2DP Kodek Kanal Rejimi seçin"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth Audio LDAC Oxutma Keyfiyyəti"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Tərcih edilmiş Bluetooth A2DP Kodek LDAC Oxutma Keyfiyyəti seçin"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Simsiz displey sertifikatlaşması üçün seçimləri göstərir"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi giriş səviyyəsini qaldırın, Wi‑Fi seçəndə hər SSID RSSI üzrə göstərin"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Aktiv olanda, Wi‑Fi sianqlı zəif olan zaman, Mobil şəbəkə data bağlantısına nisbətən, Wi‑Fi daha aqressiv olacaq"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Yardım və rəy"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menyu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Demo rejimində sıfırlamaq üçün parol daxil edin"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Növbəti"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Parol tələb olunur"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
index 9f7cf56..ab3b8b9 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Uvek koristi HDCP proveru"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Podrazumevano"</item>
+ <item msgid="7065842274271279580">"Koristi izbor sistema (podrazumevano)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Podrazumevano"</item>
+ <item msgid="5062108632402595000">"Koristi izbor sistema (podrazumevano)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Podrazumevano"</item>
+ <item msgid="3093023430402746802">"Koristi izbor sistema (podrazumevano)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Podrazumevano"</item>
+ <item msgid="3214516120190965356">"Koristi izbor sistema (podrazumevano)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Podrazumevano"</item>
+ <item msgid="2684127272582591429">"Koristi izbor sistema (podrazumevano)"</item>
<item msgid="5618929009984956469">"16 bitova po uzorku"</item>
<item msgid="3412640499234627248">"24 bita po uzorku"</item>
<item msgid="121583001492929387">"32 bita po uzorku"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Podrazumevano"</item>
+ <item msgid="1081159789834584363">"Koristi izbor sistema (podrazumevano)"</item>
<item msgid="4726688794884191540">"16 bitova po uzorku"</item>
<item msgid="305344756485516870">"24 bita po uzorku"</item>
<item msgid="244568657919675099">"32 bita po uzorku"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Podrazumevano"</item>
+ <item msgid="5226878858503393706">"Koristi izbor sistema (podrazumevano)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Podrazumevano"</item>
+ <item msgid="4118561796005528173">"Koristi izbor sistema (podrazumevano)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Željeni kvalitet zvuka (990 kb/s/909 kb/s)"</item>
- <item msgid="138837449700903545">"Standardno (660 kb/s/606 kb/s)"</item>
- <item msgid="4777177307869441982">"Željena veza (330 kb/s/303 kb/s)"</item>
+ <item msgid="3411577996960199959">"Optimizuj za kvalitet zvuka (990 kb/s/909 kb/s)"</item>
+ <item msgid="2921767058740704969">"Ujednačen kvalitet zvuka i veze (660 kb/s/606 kb/s)"</item>
+ <item msgid="3682554248829489641">"Optimizuj za kvalitet veze (330 kb/s/303 kb/s)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Željeni kvalitet zvuka (990 kb/s/909 kb/s)"</item>
- <item msgid="9091111147684472529">"Standardno (660 kb/s/606 kb/s)"</item>
- <item msgid="3367904477834831032">"Željena veza (330 kb/s/303 kb/s)"</item>
+ <item msgid="7668834469173465015">"Optimizuje se za kvalitet zvuka"</item>
+ <item msgid="4327143584633311908">"Ujednačen kvalitet zvuka i veze"</item>
+ <item msgid="6155648878105378550">"Optimizuje se za kvalitet veze"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Isključeno"</item>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 3446a1a..0aa9524 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Podaci za mobilne uređaje su uvek aktivni"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Onemogući glavno podešavanje jačine zvuka"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth audio kodek"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Izaberite željeni Bluetooth A2DP kodek"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Izaberite Bluetooth audio kodek"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Brzina uzorkovanja za Bluetooth audio"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Izaberite željenu brzinu uzorkovanja za Bluetooth A2DP kodek"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Izaberite Bluetooth audio kodek:\nbrzina uzorkovanja"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bitova po uzorku za Bluetooth audio"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Izaberite željeni broj bitova po uzorku za Bluetooth A2DP kodek"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Izaberite Bluetooth audio kodek:\nbitova po uzorku"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Režim kanala za Bluetooth audio"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Izaberite željeni režim kanala za Bluetooth A2DP kodek"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Kvalitet LDAC snimka za Bluetooth audio"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Izaberite željeni kvalitet LDAC snimka za Bluetooth A2DP kodek"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Izaberite Bluetooth audio kodek:\nrežim kanala"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth audio kodek LDAC: kvalitet reprodukcije"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Izaberite Bluetooth audio kodek LDAC:\nkvalitet reprodukcije"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strimovanje: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Prikaz opcija za sertifikaciju bežičnog ekrana"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećava nivo evidentiranja za Wi‑Fi. Prikaz po SSID RSSI-u u biraču Wi‑Fi mreže"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Kada se omogući, Wi‑Fi će biti agresivniji pri prebacivanju mreže za prenos podataka na Mobilnu, kada je Wi‑Fi signal slab"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Pomoć i povratne informacije"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Meni"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Unesite lozinku da biste obavili resetovanje na fabrička podešavanja u režimu demonstracije"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Dalje"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Potrebna je lozinka"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-be/arrays.xml b/packages/SettingsLib/res/values-be/arrays.xml
index 1266d2c..f5b2bb3 100644
--- a/packages/SettingsLib/res/values-be/arrays.xml
+++ b/packages/SettingsLib/res/values-be/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Заўсёды выкарыстоўваць праверку HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Стандартная"</item>
+ <item msgid="7065842274271279580">"Выкарыстоўв. выбар сістэмы (стандартны)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Стандартная"</item>
+ <item msgid="5062108632402595000">"Выкарыстоўв. выбар сістэмы (стандартны)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Стандартная"</item>
+ <item msgid="3093023430402746802">"Выкарыстоўв. выбар сістэмы (стандартны)"</item>
<item msgid="8895532488906185219">"44,1 кГц"</item>
<item msgid="2909915718994807056">"48,0 кГц"</item>
<item msgid="3347287377354164611">"88,2 кГц"</item>
<item msgid="1234212100239985373">"96,0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Стандартная"</item>
+ <item msgid="3214516120190965356">"Выкарыстоўв. выбар сістэмы (стандартны)"</item>
<item msgid="4482862757811638365">"44,1 кГц"</item>
<item msgid="354495328188724404">"48,0 кГц"</item>
<item msgid="7329816882213695083">"88,2 кГц"</item>
<item msgid="6967397666254430476">"96,0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Стандартная"</item>
+ <item msgid="2684127272582591429">"Выкарыстоўв. выбар сістэмы (стандартны)"</item>
<item msgid="5618929009984956469">"16 бітаў/сэмпл"</item>
<item msgid="3412640499234627248">"24 біты/сэмпл"</item>
<item msgid="121583001492929387">"32 біты/сэмпл"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Стандартная"</item>
+ <item msgid="1081159789834584363">"Выкарыстоўв. выбар сістэмы (стандартны)"</item>
<item msgid="4726688794884191540">"16 бітаў/сэмпл"</item>
<item msgid="305344756485516870">"24 біты/сэмпл"</item>
<item msgid="244568657919675099">"32 біты/сэмпл"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Стандартная"</item>
+ <item msgid="5226878858503393706">"Выкарыстоўв. выбар сістэмы (стандартны)"</item>
<item msgid="4106832974775067314">"Мона"</item>
<item msgid="5571632958424639155">"Стэрэа"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Стандартная"</item>
+ <item msgid="4118561796005528173">"Выкарыстоўв. выбар сістэмы (стандартны)"</item>
<item msgid="8900559293912978337">"Мона"</item>
<item msgid="8883739882299884241">"Стэрэа"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Прыярытэтная якасць гуч. (990/909кбіт/с)"</item>
- <item msgid="138837449700903545">"Стандартная (660/606кбіт/с)"</item>
- <item msgid="4777177307869441982">"Прыярытэтнае падключэнне (330/303кбіт/с)"</item>
+ <item msgid="3411577996960199959">"Аптымізацыя якасці гуку (990 кбіт/c / 909 кбіт/c)"</item>
+ <item msgid="2921767058740704969">"Збалансаваная якасць аўдыя і падключэння (660кбіт/c / 606 кбіт/c)"</item>
+ <item msgid="3682554248829489641">"Аптымізацыя якасці падключэння (330 кбіт/c / 303 кбіт/c)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Прыярытэтная якасць гуч. (990/909кбіт/с)"</item>
- <item msgid="9091111147684472529">"Стандартная (660/606кбіт/с)"</item>
- <item msgid="3367904477834831032">"Прыярытэтнае падключэнне (330/303кбіт/с)"</item>
+ <item msgid="7668834469173465015">"Аптымізацыя якасці гуку"</item>
+ <item msgid="4327143584633311908">"Збалансаваная якасць аўдыя і падключэння"</item>
+ <item msgid="6155648878105378550">"Аптымізацыя якасці падключэння"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Выкл."</item>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index f2e1cf6..6fd82eb 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Перадача даных мабільнай сувязі заўсёды актыўна"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Адключыць абсалютны гук"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Кодэк Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Выберыце прыярытэтны кодэк Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Выбраць аўдыякодэк Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Частата дыскрэтызацыі Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Выберыце прыярытэтную частату дыскрэтызацыі для кодэка Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Выбраць аўдыякодэк Bluetooth:\nчастата дыскрэтызацыі"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Біты на сэмпл для Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Выберыце прыярытэтную колькасць бітаў на сэмпл для кодэка Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Выбраць аўдыякодэк Bluetooth:\nбіты на сэмпл"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Канальны рэжым Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Выберыце прыярытэтны рэжым для кодэка Bluetooth A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Якасць прайгравання для Bluetooth Audio LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Выберыце прыярытэтную якасць прайгравання для кодэка Bluetooth A2DP LDAC"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Выбраць аўдыякодэк Bluetooth:\nканальны рэжым"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Аўдыякодэк Bluetooth Audio LDAC: якасць прайгравання"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Выбраць аўдыякодэк Bluetooth Audio LDAC:\nякасць прайгравання"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Перадача плынню: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Паказаць опцыі сертыфікацыі бесправаднога дысплея"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Падвыс. узровень дэтал-цыі журнала Wi‑Fi у залежн. ад SSID RSSI у Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Калі ўкл., прылада будзе больш інтэнсіўна імкнуцца перайсці з падлуч. да Wi-Fi на падлуч. да маб. сеткі, калі сігнал Wi‑Fi слабы"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Даведка і водгукі"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Меню"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Каб выканаць скід да заводскіх налад у дэманстрацыйным рэжыме, увядзіце пароль"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Далей"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Патрабуецца пароль"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bg/arrays.xml b/packages/SettingsLib/res/values-bg/arrays.xml
index 93d547d..4ada334 100644
--- a/packages/SettingsLib/res/values-bg/arrays.xml
+++ b/packages/SettingsLib/res/values-bg/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Винаги да се използва проверка с HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Стандартно"</item>
+ <item msgid="7065842274271279580">"Използване на сист. избор (стандартно)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Стандартно"</item>
+ <item msgid="5062108632402595000">"Използване на сист. избор (стандартно)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Стандартно"</item>
+ <item msgid="3093023430402746802">"Използване на сист. избор (стандартно)"</item>
<item msgid="8895532488906185219">"44,1 кХц"</item>
<item msgid="2909915718994807056">"48 кХц"</item>
<item msgid="3347287377354164611">"88,2 кХц"</item>
<item msgid="1234212100239985373">"96 кХц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Стандартно"</item>
+ <item msgid="3214516120190965356">"Използване на сист. избор (стандартно)"</item>
<item msgid="4482862757811638365">"44,1 кХц"</item>
<item msgid="354495328188724404">"48 кХц"</item>
<item msgid="7329816882213695083">"88,2 кХц"</item>
<item msgid="6967397666254430476">"96 кХц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Стандартно"</item>
+ <item msgid="2684127272582591429">"Използване на сист. избор (стандартно)"</item>
<item msgid="5618929009984956469">"16 бита/дискрет"</item>
<item msgid="3412640499234627248">"24 бита/дискрет"</item>
<item msgid="121583001492929387">"32 бита/дискрет"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Стандартно"</item>
+ <item msgid="1081159789834584363">"Използване на сист. избор (стандартно)"</item>
<item msgid="4726688794884191540">"16 бита/дискрет"</item>
<item msgid="305344756485516870">"24 бита/дискрет"</item>
<item msgid="244568657919675099">"32 бита/дискрет"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Стандартно"</item>
+ <item msgid="5226878858503393706">"Използване на сист. избор (стандартно)"</item>
<item msgid="4106832974775067314">"Моно"</item>
<item msgid="5571632958424639155">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Стандартно"</item>
+ <item msgid="4118561796005528173">"Използване на сист. избор (стандартно)"</item>
<item msgid="8900559293912978337">"Моно"</item>
<item msgid="8883739882299884241">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Предпоч. кач. на звука (990 или 909 кб/сек)QM"</item>
- <item msgid="138837449700903545">"Стандартно (660 или 606 кб/сек)"</item>
- <item msgid="4777177307869441982">"Предпоч. връзка (330 или 303 кб/сек)"</item>
+ <item msgid="3411577996960199959">"Оптимизиране за качество на звука (990 или 909 кб/сек)"</item>
+ <item msgid="2921767058740704969">"Балансирано качество на звука и връзката (660 или 606 кб/сек)"</item>
+ <item msgid="3682554248829489641">"Оптимизиране за качество на връзката (330 или 303 кб/сек)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Предпоч. кач. на звука (990 или 909 кб/сек)The QM tool is not responding at the moment, and I am not able to check the status of the query."</item>
- <item msgid="9091111147684472529">"Стандартно (660 или 606 кб/сек)"</item>
- <item msgid="3367904477834831032">"Предпоч. връзка (330 или 303 кб/сек)"</item>
+ <item msgid="7668834469173465015">"Оптимизиране за качество на звука"</item>
+ <item msgid="4327143584633311908">"Балансирано качество на звука и връзката"</item>
+ <item msgid="6155648878105378550">"Оптимизиране за качество на връзката"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Изключено"</item>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 21d1a98..351b489 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Винаги активни клетъчни данни"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Деактивиране на пълната сила на звука"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Аудиокодек за Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Изберете предпочитания кодек за Bluetooth с профил A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Изберете аудиокодек за Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Честота на дискретизация за звука през Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Изберете предпочитаната честота на дискретизация за кодека за Bluetooth с профил A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Изберете аудиокодек за Bluetooth:\nЧестота на дискретизация"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Битове на дискрет за звука през Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Изберете предпочитание за битове на дискрет за кодека за Bluetooth с профил A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Изберете аудиокодек за Bluetooth:\nБитове на дискрет"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Режим на канала на звука през Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Изберете предпочитания режим на канала за кодека за Bluetooth с профил A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Качество на възпроизвеждане на звука през Bluetooth с технологията LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Изберете предпочитаното качество на възпроизвеждане с технологията LDAC за кодека за Bluetooth с профил A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Изберете аудиокодек за Bluetooth:\nРежим на канала"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Кодек за звука през Bluetooth с технологията LDAC: Качество на възпроизвеждане"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Изберете кодек за звука през Bluetooth с технологията LDAC:\nКачество на възпроизвеждане"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Поточно предаване: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Показване на опциите за сертифициране на безжичния дисплей"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"По-подробно регистр. на Wi‑Fi – данни за RSSI на SSID в инстр. за избор на Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"При активиране предаването на връзката за данни от Wi-Fi към мобилната мрежа ще е по-агресивно, когато Wi-Fi сигналът е слаб"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Помощ и отзиви"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Меню"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"Средно време по Гринуич (GMT)"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Въведете парола за възст. на фабр. настройки в демонстр. режим"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Напред"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Изисква се парола"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bn/arrays.xml b/packages/SettingsLib/res/values-bn/arrays.xml
index 6ce7b14..d558127 100644
--- a/packages/SettingsLib/res/values-bn/arrays.xml
+++ b/packages/SettingsLib/res/values-bn/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"সর্বদা HDCP পরীক্ষণ ব্যবহার করুন"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ডিফল্ট"</item>
+ <item msgid="7065842274271279580">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ডিফল্ট"</item>
+ <item msgid="5062108632402595000">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ডিফল্ট"</item>
+ <item msgid="3093023430402746802">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
<item msgid="8895532488906185219">"৪৪.১ kHz"</item>
<item msgid="2909915718994807056">"৪৮.০ kHz"</item>
<item msgid="3347287377354164611">"৮৮.২ kHz"</item>
<item msgid="1234212100239985373">"৯৬.০ kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ডিফল্ট"</item>
+ <item msgid="3214516120190965356">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
<item msgid="4482862757811638365">"৪৪.১ kHz"</item>
<item msgid="354495328188724404">"৪৮.০ kHz"</item>
<item msgid="7329816882213695083">"৮৮.২ kHz"</item>
<item msgid="6967397666254430476">"৯৬.০ kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ডিফল্ট"</item>
+ <item msgid="2684127272582591429">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
<item msgid="5618929009984956469">"১৬ বিট/নমুনা"</item>
<item msgid="3412640499234627248">"২৪ বিট/নমুনা"</item>
<item msgid="121583001492929387">"৩২ বিট/নমুনা"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ডিফল্ট"</item>
+ <item msgid="1081159789834584363">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
<item msgid="4726688794884191540">"১৬ বিট/নমুনা"</item>
<item msgid="305344756485516870">"২৪ বিট/নমুনা"</item>
<item msgid="244568657919675099">"৩২ বিট/নমুনা"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ডিফল্ট"</item>
+ <item msgid="5226878858503393706">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
<item msgid="4106832974775067314">"মোনো"</item>
<item msgid="5571632958424639155">"স্টিরিও"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ডিফল্ট"</item>
+ <item msgid="4118561796005528173">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
<item msgid="8900559293912978337">"মোনো"</item>
<item msgid="8883739882299884241">"স্টিরিও"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"পছন্দের শব্দের মান (৯৯০kbps/৯০৯kbps)"</item>
- <item msgid="138837449700903545">"মানক (৬৬০kbps/৬০৬kbps)"</item>
- <item msgid="4777177307869441982">"পছন্দের সংযোগ (৩৩০kbps/৩০৩kbps)"</item>
+ <item msgid="3411577996960199959">"অডিওর গুণমানের জন্য অপটিমাইজ করুন (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"সন্তুলিত গুণমানের অডিও এবং সংযোগ (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"সংযোগের গুণমানের জন্য অপটিমাইজ করুন (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"পছন্দের শব্দের মান (৯৯০kbps/৯০৯kbps)"</item>
- <item msgid="9091111147684472529">"মানক (৬৬০kbps/৬০৬kbps)"</item>
- <item msgid="3367904477834831032">"পছন্দের সংযোগ (৩৩০kbps/৩০৩kbps)"</item>
+ <item msgid="7668834469173465015">"অডিওর গুণমানের জন্য অপটিমাইজ করুন"</item>
+ <item msgid="4327143584633311908">"সন্তুলিত গুণমানের অডিও এবং সংযোগ"</item>
+ <item msgid="6155648878105378550">"সংযোগের গুণমানের জন্য অপটিমাইজ করুন"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"বন্ধ আছে"</item>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 66fa0673..f445e8f 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"সেলুলার ডেটা সর্বদাই সক্রিয় থাকে"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"চূড়ান্ত ভলিউম অক্ষম করুন"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"ব্লুটুথ অডিও কোডেক"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"পছন্দের ব্লুটুথ A2DP কোডেক বেছে নিন"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"ব্লুটুথ অডিও কোডেক বেছে নিন"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"ব্লুটুথ অডিওর নমুনা হার"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"পছন্দের ব্লুটুথ A2DP কোডেক নমুনা হার বেছে নিন"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"ব্লুটুথ অডিও কোডেক বেছে নিন:\nস্যাম্পল রেট"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"নমুনা প্রতি ব্লুটুথ অডিও বিট"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"নমুনা প্রতি পছন্দের ব্লুটুথ A2DP কোডেক বিট বেছে নিন"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"ব্লুটুথ অডিও কোডেক বেছে নিন:\nবিটস পার স্যাম্পল"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"ব্লুটুথ অডিও চ্যানেল মোড"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"পছন্দের ব্লুটুথ A2DP কোডেক চ্যানেল মোড বেছে নিন"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"ব্লুটুথ অডিও LDAC প্লেব্যাকের মান"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"পছন্দের ব্লুটুথ A2DP কোডেক LDAC প্লেব্যাকের মান বেছে নিন"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"ব্লুটুথ অডিও কোডেক বেছে নিন:\nচ্যানেল মোড"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ব্লুটুথ অডিও LDAC কোডেক: প্লেব্যাক গুণমান"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ব্লুটুথ অডিও LDAC কোডেক বেছে নিন:\nপ্লেব্যাক গুণমান"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"স্ট্রিমিং: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ওয়্যারলেস প্রদর্শন সার্টিফিকেশন জন্য বিকল্পগুলি দেখান"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"ওয়াই-ফাই লগিং স্তর বাড়ান, ওয়াই-ফাই চয়নকারীতে SSID RSSI অনুযায়ী দেখান"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"সক্ষম করা থাকলে, নিম্নমানের ওয়াই-ফাই সিগন্যালের ক্ষেত্রে, সেলুলার-এ ডেটা সংযোগ প্রদান করতে ওয়াই-ফাই আরো বেশি শক্তিশালীভাবে কাজ করবে"</string>
@@ -352,4 +353,10 @@
<string name="help_feedback_label" msgid="6815040660801785649">"সহায়তা ও মতামত"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"মেনু"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <!-- no translation found for retail_demo_reset_message (118771671364131297) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_next (8356731459226304963) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_title (696589204029930100) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-bs/arrays.xml b/packages/SettingsLib/res/values-bs/arrays.xml
index 93e0678..d9c10f8 100644
--- a/packages/SettingsLib/res/values-bs/arrays.xml
+++ b/packages/SettingsLib/res/values-bs/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Uvijek koristi HDCP provjeru"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Podrazumijevano"</item>
+ <item msgid="7065842274271279580">"Koristi odabir sistema (Zadano)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Podrazumijevano"</item>
+ <item msgid="5062108632402595000">"Koristi odabir sistema (Zadano)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Zadano"</item>
+ <item msgid="3093023430402746802">"Koristi odabir sistema (Zadano)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Zadano"</item>
+ <item msgid="3214516120190965356">"Koristi odabir sistema (Zadano)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Podrazumijevano"</item>
+ <item msgid="2684127272582591429">"Koristi odabir sistema (Zadano)"</item>
<item msgid="5618929009984956469">"16 bitova/uzorak"</item>
<item msgid="3412640499234627248">"24 bitova/uzorak"</item>
<item msgid="121583001492929387">"32 bitova/uzorak"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Podrazumijevano"</item>
+ <item msgid="1081159789834584363">"Koristi odabir sistema (Zadano)"</item>
<item msgid="4726688794884191540">"16 bitova/uzorak"</item>
<item msgid="305344756485516870">"24 bitova/uzorak"</item>
<item msgid="244568657919675099">"32 bitova/uzorak"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Podrazumijevano"</item>
+ <item msgid="5226878858503393706">"Koristi odabir sistema (Zadano)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Podrazumijevano"</item>
+ <item msgid="4118561796005528173">"Koristi odabir sistema (Zadano)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Željena kvaliteta zvuka (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Standardni (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Željena veza (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"Optimiziraj za kvalitet zvuka (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"Uravnotežen kvalitet zvuka i veze (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"Optimiziraj za kvalitet veze (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Željena kvaliteta zvuka (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Standardni (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Željena veza (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"Optimiziraj za kvalitet zvuka"</item>
+ <item msgid="4327143584633311908">"Uravnotežen kvalitet zvuka i veze"</item>
+ <item msgid="6155648878105378550">"Optimiziraj za kvalitet veze"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Isključeno"</item>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index f2ae338..aaf9713 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobilni podaci uvijek aktivni"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Onemogućite apsolutnu jačinu zvuka"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Audio kodek"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Izaberite željeni Bluetooth A2DP kodek"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Odaberite Bluetooth Audio kodek"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Brzina uzorkovanja za Bluetooth audio"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Izaberite željenu brzinu uzorkovanja za Bluetooth A2DP kodek"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Odaberite Bluetooth Audio kodek:\nBrzina uzorka"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth audio bitovi po uzorku"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Izaberite željeni broj bitova po uzorku za Bluetooth A2DP kodek"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Odaberite Bluetooth Audio kodek:\nBita po uzorku"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Način Bluetooth audio kanala"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Izaberite željeni način kanala Bluetooth A2DP kodeka"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Kvaliteta reprodukcije za Bluetooth Audio LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Izaberite željenu kvalitetu LDAC reprodukcije za Bluetooth A2DP kodek"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Odaberite Bluetooth Audio kodek:\nNačin rada kanala"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC kodek: Kvalitet reprodukcije"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Odaberite Bluetooth Audio LDAC kodek:\nKvalitet reprodukcije"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Prijenos: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaži opcije za certifikaciju Bežičnog prikaza"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećajte nivo Wi-Fi zapisivanja, pokazati po SSID RSSI Wi-Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Kada je omogućeno, Wi-Fi će biti agresivniji u predavanju podatkovne veze mobilnoj, kada je Wi-Fi signal slab"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Pomoć i povratne informacije"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Meni"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Unesite lozinku da izvršite vraćanje na fabričke postavke u načinu demonstracije"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Naprijed"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Potrebna je lozinka"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ca/arrays.xml b/packages/SettingsLib/res/values-ca/arrays.xml
index 6cb4217..5c8c4d7 100644
--- a/packages/SettingsLib/res/values-ca/arrays.xml
+++ b/packages/SettingsLib/res/values-ca/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Utilitza sempre la comprovació HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Predeterminat"</item>
+ <item msgid="7065842274271279580">"Selecció del sistema (predeterminada)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Predeterminat"</item>
+ <item msgid="5062108632402595000">"Selecció del sistema (predeterminada)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Predeterminat"</item>
+ <item msgid="3093023430402746802">"Selecció del sistema (predeterminada)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Predeterminat"</item>
+ <item msgid="3214516120190965356">"Selecció del sistema (predeterminada)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Predeterminat"</item>
+ <item msgid="2684127272582591429">"Selecció del sistema (predeterminada)"</item>
<item msgid="5618929009984956469">"16 bits/mostra"</item>
<item msgid="3412640499234627248">"24 bits/mostra"</item>
<item msgid="121583001492929387">"32 bits/mostra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Predeterminat"</item>
+ <item msgid="1081159789834584363">"Selecció del sistema (predeterminada)"</item>
<item msgid="4726688794884191540">"16 bits/mostra"</item>
<item msgid="305344756485516870">"24 bits/mostra"</item>
<item msgid="244568657919675099">"32 bits/mostra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Predeterminat"</item>
+ <item msgid="5226878858503393706">"Selecció del sistema (predeterminada)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Estèreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Predeterminat"</item>
+ <item msgid="4118561796005528173">"Selecció del sistema (predeterminada)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Estèreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Qualitat del so preferida (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Estàndard (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Connexió preferida (330 kbps/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Optimitza la qualitat de l\'àudio (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Qualitat equilibrada de l\'àudio i la connexió (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Optimitza la qualitat de la connexió (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Qualitat del so preferida (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Estàndard (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Connexió preferida (330 kbps/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Optimitza la qualitat de l\'àudio"</item>
+ <item msgid="4327143584633311908">"Qualitat equilibrada de l\'àudio i la connexió"</item>
+ <item msgid="6155648878105378550">"Optimitza la qualitat de la connexió"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"No"</item>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index ad83d6b..391782b 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Dades mòbils sempre actives"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Desactiva el volum absolut"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Còdec d\'àudio per Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Selecciona el còdec Bluetooth A2DP preferit"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Selecciona el còdec d\'àudio per Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Velocitat de mostra d’àudio per Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Selecciona la velocitat de mostra preferida del còdec Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Selecciona el còdec d\'àudio per Bluetooth:\nFreqüència de mostratge"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits per mostra de l\'àudio per Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Selecciona els bits per mostra preferits del còdec Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Selecciona el còdec d\'àudio per Bluetooth:\nBits per mostra"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Mode de canal de l\'àudio per Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Selecciona el mode de canal preferit del còdec Bluetooth A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Qualitat de reproducció LDAC de l\'àudio per Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Selecciona la qualitat de reproducció LDAC preferida del còdec Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Selecciona el còdec d\'àudio per Bluetooth:\nMode de canal"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Còdec LDAC d\'àudio per Bluetooth: qualitat de reproducció"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecciona el còdec LDAC d\'àudio per Bluetooth:\nQualitat de reproducció"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"S\'està reproduint en temps real: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostra les opcions de certificació de pantalla sense fil"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Augmenta nivell de registre Wi‑Fi i mostra\'l per SSID RSSI al Selector de Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Si s\'activa, la Wi-Fi serà més agressiva en transferir la connexió de dades al mòbil, si el senyal de la Wi-Fi no és estable"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Ajuda i suggeriments"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menú"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Introdueix la contrasenya per restablir les dades de fàbrica en mode de demostració"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Següent"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Contrasenya obligatòria"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-cs/arrays.xml b/packages/SettingsLib/res/values-cs/arrays.xml
index bba5a16..8c96ac3 100644
--- a/packages/SettingsLib/res/values-cs/arrays.xml
+++ b/packages/SettingsLib/res/values-cs/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Použít kontrolu HDCP pouze pro obsah DRM"</item>
<item msgid="45075631231212732">"Vždy používat kontrolu HDCP"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Výchozí"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Výchozí"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Výchozí"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Výchozí"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Výchozí"</item>
- <item msgid="5618929009984956469">"16 bitů / vzorek"</item>
- <item msgid="3412640499234627248">"24 bitů / vzorek"</item>
- <item msgid="121583001492929387">"32 bitů / vzorek"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Výchozí"</item>
- <item msgid="4726688794884191540">"16 bitů / vzorek"</item>
- <item msgid="305344756485516870">"24 bitů / vzorek"</item>
- <item msgid="244568657919675099">"32 bitů / vzorek"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Výchozí"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Výchozí"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Přednost kvality zvuku (990 kb/s / 909 kb/s)"</item>
- <item msgid="138837449700903545">"Standardní (660 kb/s / 606 kB/s)"</item>
- <item msgid="4777177307869441982">"Přednost připojení (330 kb/s / 303 kb/s)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Přednost kvality zvuku (990 kb/s / 909 kb/s)"</item>
- <item msgid="9091111147684472529">"Standardní (660 kb/s / 606 kB/s)"</item>
- <item msgid="3367904477834831032">"Přednost připojení (330 kb/s / 303 kb/s)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Vypnuto"</item>
<item msgid="1593289376502312923">"64 kB"</item>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 706aa91..ee318ad 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobilní data jsou vždy aktivní"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Zakázat absolutní hlasitost"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Audio – kodek"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Vyberte preferovaný kodek Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth Audio – vzorkovací frekvence"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Vybrat vzorkovací frekvenci pro preferovaný kodek Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth Audio – počet bitů na vzorek"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Vybrat počet bitů na vzorek pro preferovaný kodek Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth Audio – režim kanálu"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Vybrat režim kanálu pro preferovaný kodek Bluetooth A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth Audio LDAC – kvalita přehrávání"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Vybrat kvalitu přehrávání pro preferovaný kodek Bluetooth A2DP LDAC"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Zobrazit možnosti certifikace bezdrátového displeje"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Zvýšit úroveň protokolování Wi‑Fi zobrazenou v SSID a RSSI při výběru sítě Wi‑Fi."</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Pokud je tato možnost zapnuta, bude síť Wi-Fi agresivnější při předávání datového připojení mobilní síti při slabém signálu Wi-Fi."</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Nápověda a zpětná vazba"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Nabídka"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Chcete-li v ukázkovém režimu obnovit zařízení do továrního nastavení, zadejte heslo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Další"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Je třeba zadat heslo"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-da/arrays.xml b/packages/SettingsLib/res/values-da/arrays.xml
index 6ae0aa8..1b54d4a 100644
--- a/packages/SettingsLib/res/values-da/arrays.xml
+++ b/packages/SettingsLib/res/values-da/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Brug kun HDCP-kontrol ved DRM-indhold"</item>
<item msgid="45075631231212732">"Brug altid HDCP-kontrol"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Standard"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Standard"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Standard"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Standard"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Standard"</item>
- <item msgid="5618929009984956469">"16 bit pr. eksempel"</item>
- <item msgid="3412640499234627248">"24 bit pr. eksempel"</item>
- <item msgid="121583001492929387">"32 bit pr. eksempel"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Standard"</item>
- <item msgid="4726688794884191540">"16 bit pr. eksempel"</item>
- <item msgid="305344756485516870">"24 bit pr. eksempel"</item>
- <item msgid="244568657919675099">"32 bit pr. eksempel"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Standard"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Standard"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Lydkvalitet foretrækkes (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Standard (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Forbindelse foretrækkes (330 kbps/303 kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Lydkvalitet foretrækkes (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Forbindelse foretrækkes (330 kbps/303 kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Fra"</item>
<item msgid="1593289376502312923">"64 kB"</item>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index c6e6fb7..3cd0a6b 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobildata altid aktiveret"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Deaktiver absolut lydstyrke"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth-lydcodec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Vælg foretrukken Bluetooth A2DP-codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Eksempelfrekvens for Bluetooth-lyd"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Vælg eksempelfrekvens for foretrukken Bluetooth A2DP-codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bit pr. eksempel for Bluetooth-lyd"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Vælg bit pr. eksempel for foretrukken Bluetooth A2DP-codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Kanaltilstand for Bluetooth-lyd"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Vælg kanaltilstand for foretrukken Bluetooth A2DP-codec"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"LDAC-afspilningskvalitet for Bluetooth-lyd"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Vælg LDAC-afspilningskvalitet for foretrukken Bluetooth A2DP-codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Vis valgmuligheder for certificering af trådløs skærm"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Øg mængden af Wi‑Fi-logføring. Vis opdelt efter SSID RSSI i Wi‑Fi-vælgeren"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Når dette er aktiveret, gennemtvinges en overdragelse af dataforbindelsen fra Wi-Fi til mobilnetværk, når Wi-Fi-signalet er svagt"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Hjælp og feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Angiv adgangskode for at gendanne fabriksdata i demotilstand"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Næste"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Du skal angive en adgangskode"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-de/arrays.xml b/packages/SettingsLib/res/values-de/arrays.xml
index 935c2b4..dec52b2 100644
--- a/packages/SettingsLib/res/values-de/arrays.xml
+++ b/packages/SettingsLib/res/values-de/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"HDCP-Prüfung immer verwenden"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Standard"</item>
+ <item msgid="7065842274271279580">"Systemauswahl verwenden (Standard)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Standard"</item>
+ <item msgid="5062108632402595000">"Systemauswahl verwenden (Standard)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Standard"</item>
+ <item msgid="3093023430402746802">"Systemauswahl verwenden (Standard)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Standard"</item>
+ <item msgid="3214516120190965356">"Systemauswahl verwenden (Standard)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Standard"</item>
+ <item msgid="2684127272582591429">"Systemauswahl verwenden (Standard)"</item>
<item msgid="5618929009984956469">"16 Bits pro Sample"</item>
<item msgid="3412640499234627248">"24 Bits pro Sample"</item>
<item msgid="121583001492929387">"32 Bits pro Sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Standard"</item>
+ <item msgid="1081159789834584363">"Systemauswahl verwenden (Standard)"</item>
<item msgid="4726688794884191540">"16 Bits pro Sample"</item>
<item msgid="305344756485516870">"24 Bits pro Sample"</item>
<item msgid="244568657919675099">"32 Bits pro Sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Standard"</item>
+ <item msgid="5226878858503393706">"Systemauswahl verwenden (Standard)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Standard"</item>
+ <item msgid="4118561796005528173">"Systemauswahl verwenden (Standard)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Bevorzugte Tonqualität (990 kbit/s / 909 kbit/s)"</item>
- <item msgid="138837449700903545">"Standard (660 kbit/s / 606 kbit/s)"</item>
- <item msgid="4777177307869441982">"Bevorzugte Verbindung (330 kbit/s / 303 kbit/s)"</item>
+ <item msgid="3411577996960199959">"Für Audioqualität optimieren (990 kbit/s/909 kbit/s)"</item>
+ <item msgid="2921767058740704969">"Ausgeglichene Audio- und Verbindungsqualität (660 kbit/s/606 kbit/s)"</item>
+ <item msgid="3682554248829489641">"Für Verbindungsqualität optimieren (330 kbit/s/303 kbit/s)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Bevorzugte Tonqualität (990 kbit/s / 909 kbit/s)"</item>
- <item msgid="9091111147684472529">"Standard (660 kbit/s / 606 kbit/s)"</item>
- <item msgid="3367904477834831032">"Bevorzugte Verbindung (330 kbit/s / 303 kbit/s)"</item>
+ <item msgid="7668834469173465015">"Für Audioqualität optimieren"</item>
+ <item msgid="4327143584633311908">"Ausgeglichene Audio- und Verbindungsqualität"</item>
+ <item msgid="6155648878105378550">"Für Verbindungsqualität optimieren"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Aus"</item>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 0ce1384..8dd4c1db 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobile Datennutzung immer aktiviert"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Maximallautstärke deaktivieren"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth-Audio-Codec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Bevorzugten Bluetooth-A2DP-Codec auwählen"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth-Audio-Codec auswählen"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth-Audio-Abtastrate"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Bevorzugten Bluetooth-A2DP-Codec auswählen/Abtastrate"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth-Audio-Codec auswählen:\nAbtastrate"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth-Audio/Bits pro Sample"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Bevorzugten Bluetooth-A2DP-Codec auswählen/Bits Pro Sample"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth-Audio-Codec auswählen:\nBits pro Sample"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Modus des Bluetooth-Audiokanals"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Bevorzugten Bluetooth-A2DP-Codec auswählen/Modus des Kanals"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"LDAC-Wiedergabequalität von Bluetooth-Audio"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Bevorzugten Bluetooth-A2DP-Codec auswählen/LDAC-Wiedergabequalität"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth-Audio-Codec auswählen:\nKanalmodus"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth-Audio-LDAC-Codec: Wiedergabequalität"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth-Audio-LDAC-Codec auswählen:\nWiedergabequalität"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Optionen zur Zertifizierung für kabellose Übertragung anzeigen"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Level für WLAN-Protokollierung erhöhen, in WiFi Picker pro SSID-RSSI anzeigen"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Wenn diese Option aktiviert ist, ist WLAN bei schwachem Signal bei der Übergabe der Datenverbindung an den Mobilfunk aggressiver."</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Hilfe & Feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menü"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Passwort eingeben, um Gerät im Demomodus auf Werkseinstellungen zurückzusetzen"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Weiter"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Passwort erforderlich"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-el/arrays.xml b/packages/SettingsLib/res/values-el/arrays.xml
index 04e1a8c..12a05b9 100644
--- a/packages/SettingsLib/res/values-el/arrays.xml
+++ b/packages/SettingsLib/res/values-el/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Να χρησιμοποιείται πάντα έλεγχος HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Προεπιλογή"</item>
+ <item msgid="7065842274271279580">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Προεπιλογή"</item>
+ <item msgid="5062108632402595000">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Προεπιλογή"</item>
+ <item msgid="3093023430402746802">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Προεπιλογή"</item>
+ <item msgid="3214516120190965356">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Προεπιλογή"</item>
+ <item msgid="2684127272582591429">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
<item msgid="5618929009984956469">"16 bit/δείγμα"</item>
<item msgid="3412640499234627248">"24 bit/δείγμα"</item>
<item msgid="121583001492929387">"32 bit/δείγμα"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Προεπιλογή"</item>
+ <item msgid="1081159789834584363">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
<item msgid="4726688794884191540">"16 bit/δείγμα"</item>
<item msgid="305344756485516870">"24 bit/δείγμα"</item>
<item msgid="244568657919675099">"32 bit/δείγμα"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Προεπιλογή"</item>
+ <item msgid="5226878858503393706">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
<item msgid="4106832974775067314">"Μονοφωνικό"</item>
<item msgid="5571632958424639155">"Στερεοφωνικό"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Προεπιλογή"</item>
+ <item msgid="4118561796005528173">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
<item msgid="8900559293912978337">"Μονοφωνικό"</item>
<item msgid="8883739882299884241">"Στερεοφωνικό"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Προτιμ. ποιότητα ήχου (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Τυπική (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Προτιμώμενη σύνδεση (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"Βελτιστοποίηση για ποιότητα ήχου (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"Ισορροπημένος ήχος και ποιότητα σύνδεσης (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"Βελτιστοποίηση για ποιότητα σύνδεσης (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Προτιμ. ποιότητα ήχου (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Τυπική (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Προτιμώμενη σύνδεση (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"Βελτιστοποίηση για ποιότητα ήχου"</item>
+ <item msgid="4327143584633311908">"Ισορροπημένος ήχος και ποιότητα σύνδεσης"</item>
+ <item msgid="6155648878105378550">"Βελτιστοποίηση για ποιότητα σύνδεσης"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Ανενεργό"</item>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 8d3df55..1ee6db8 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Πάντα ενεργά δεδομένα κινητής τηλεφωνίας"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Απενεργοποίηση απόλυτης έντασης"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Κωδικοποιητής ήχου Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Επιλέξτε τον προτιμώμενο κωδικοποιητή A2DP Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Επιλογή κωδικοποιητή ήχου Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Ρυθμός δειγματοληψίας ήχου Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Επιλέξτε τον προτιμώμενο ρυθμό δειγματοληψίας του κωδικοποιητή A2DP Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Επιλογή κωδικοποιητή ήχου Bluetooth:\nΠοσοστό δείγματος"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bit ανά δείγμα ήχου Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Επιλέξτε τα προτιμώμενα bit ανά δείγμα για τον κωδικοποιητή A2DP Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Επιλογή κωδικοποιητή ήχου Bluetooth:\nBit ανά δείγμα"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Λειτουργία καναλιού ήχου Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Επιλέξτε την προτιμώμενη λειτουργία καναλιού κωδικοποιητή A2DP Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Ποιότητα αναπαραγωγής LDAC ήχου Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Επιλέξτε την προτιμώμενη ποιότητα αναπαραγωγής LDAC του κωδικοποιητή A2DP Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Επιλογή κωδικοποιητή ήχου Bluetooth:\nΛειτουργία καναλιού"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Κωδικοποιητής LDAC ήχου Bluetooth: Ποιότητα αναπαραγωγής"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Επιλογή κωδικοποιητή LDAC ήχου Bluetooth:\nΠοιότητα αναπαραγωγής"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Ροή: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Εμφάνιση επιλογών για πιστοποίηση ασύρματης οθόνης"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Αύξηση επιπέδου καταγ. Wi-Fi, εμφάνιση ανά SSID RSSI στο εργαλείο επιλογής Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Όταν είναι ενεργό, το Wi-Fi θα μεταβιβάζει πιο επιθετικά τη σύνδ.δεδομένων σε δίκτυο κινητής τηλ., όταν το σήμα Wi-Fi είναι χαμηλό"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Βοήθεια και σχόλια"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Μενού"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Εισαγάγετε κωδικό πρόσβασης για επαναφορά εργοστασιακών ρυθμίσεων στη λειτουργία επίδειξης"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Επόμενο"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Απαιτείται κωδικός πρόσβασης"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/arrays.xml b/packages/SettingsLib/res/values-en-rAU/arrays.xml
index 5242be3..4758019 100644
--- a/packages/SettingsLib/res/values-en-rAU/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rAU/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Use HDCP checking for DRM content only"</item>
<item msgid="45075631231212732">"Always use HDCP checking"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Default"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Default"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Default"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Default"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Default"</item>
- <item msgid="5618929009984956469">"16 bits/sample"</item>
- <item msgid="3412640499234627248">"24 bits/sample"</item>
- <item msgid="121583001492929387">"32 bits/sample"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Default"</item>
- <item msgid="4726688794884191540">"16 bits/sample"</item>
- <item msgid="305344756485516870">"24 bits/sample"</item>
- <item msgid="244568657919675099">"32 bits/sample"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Default"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Default"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Sound quality preferred (990 Kbps/909 Kbps)"</item>
- <item msgid="138837449700903545">"Standard (660 Kbps/606 Kbps)"</item>
- <item msgid="4777177307869441982">"Connection preferred (330 Kbps/303 Kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Sound quality preferred (990 Kbps/909 Kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660 Kbps/606 Kbps)"</item>
- <item msgid="3367904477834831032">"Connection preferred (330 Kbps/303 Kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Off"</item>
<item msgid="1593289376502312923">"64 K"</item>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index f09206d..af9895e 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobile data always active"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Disable absolute volume"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Audio Codec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Select Preferred Bluetooth A2DP Codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth Audio Sample Rate"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Select Preferred Bluetooth A2DP Codec Sample Rate"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth Audio Bits Per Sample"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Select Preferred Bluetooth A2DP Codec Bits Per Sample"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth Audio Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Select Preferred Bluetooth A2DP Codec Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth Audio LDAC Playback Quality"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Select Preferred Bluetooth A2DP Codec LDAC Playback Quality"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to Mobile, when Wi‑Fi signal is low"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Help & feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Enter password to perform factory reset in demo mode"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Next"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Password required"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/arrays.xml b/packages/SettingsLib/res/values-en-rGB/arrays.xml
index 5242be3..4758019 100644
--- a/packages/SettingsLib/res/values-en-rGB/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rGB/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Use HDCP checking for DRM content only"</item>
<item msgid="45075631231212732">"Always use HDCP checking"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Default"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Default"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Default"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Default"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Default"</item>
- <item msgid="5618929009984956469">"16 bits/sample"</item>
- <item msgid="3412640499234627248">"24 bits/sample"</item>
- <item msgid="121583001492929387">"32 bits/sample"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Default"</item>
- <item msgid="4726688794884191540">"16 bits/sample"</item>
- <item msgid="305344756485516870">"24 bits/sample"</item>
- <item msgid="244568657919675099">"32 bits/sample"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Default"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Default"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Sound quality preferred (990 Kbps/909 Kbps)"</item>
- <item msgid="138837449700903545">"Standard (660 Kbps/606 Kbps)"</item>
- <item msgid="4777177307869441982">"Connection preferred (330 Kbps/303 Kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Sound quality preferred (990 Kbps/909 Kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660 Kbps/606 Kbps)"</item>
- <item msgid="3367904477834831032">"Connection preferred (330 Kbps/303 Kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Off"</item>
<item msgid="1593289376502312923">"64 K"</item>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index f09206d..af9895e 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobile data always active"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Disable absolute volume"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Audio Codec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Select Preferred Bluetooth A2DP Codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth Audio Sample Rate"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Select Preferred Bluetooth A2DP Codec Sample Rate"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth Audio Bits Per Sample"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Select Preferred Bluetooth A2DP Codec Bits Per Sample"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth Audio Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Select Preferred Bluetooth A2DP Codec Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth Audio LDAC Playback Quality"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Select Preferred Bluetooth A2DP Codec LDAC Playback Quality"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to Mobile, when Wi‑Fi signal is low"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Help & feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Enter password to perform factory reset in demo mode"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Next"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Password required"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/arrays.xml b/packages/SettingsLib/res/values-en-rIN/arrays.xml
index 5242be3..4758019 100644
--- a/packages/SettingsLib/res/values-en-rIN/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rIN/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Use HDCP checking for DRM content only"</item>
<item msgid="45075631231212732">"Always use HDCP checking"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Default"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Default"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Default"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Default"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Default"</item>
- <item msgid="5618929009984956469">"16 bits/sample"</item>
- <item msgid="3412640499234627248">"24 bits/sample"</item>
- <item msgid="121583001492929387">"32 bits/sample"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Default"</item>
- <item msgid="4726688794884191540">"16 bits/sample"</item>
- <item msgid="305344756485516870">"24 bits/sample"</item>
- <item msgid="244568657919675099">"32 bits/sample"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Default"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Default"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Sound quality preferred (990 Kbps/909 Kbps)"</item>
- <item msgid="138837449700903545">"Standard (660 Kbps/606 Kbps)"</item>
- <item msgid="4777177307869441982">"Connection preferred (330 Kbps/303 Kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Sound quality preferred (990 Kbps/909 Kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660 Kbps/606 Kbps)"</item>
- <item msgid="3367904477834831032">"Connection preferred (330 Kbps/303 Kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Off"</item>
<item msgid="1593289376502312923">"64 K"</item>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index f09206d..af9895e 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobile data always active"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Disable absolute volume"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Audio Codec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Select Preferred Bluetooth A2DP Codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth Audio Sample Rate"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Select Preferred Bluetooth A2DP Codec Sample Rate"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth Audio Bits Per Sample"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Select Preferred Bluetooth A2DP Codec Bits Per Sample"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth Audio Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Select Preferred Bluetooth A2DP Codec Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth Audio LDAC Playback Quality"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Select Preferred Bluetooth A2DP Codec LDAC Playback Quality"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to Mobile, when Wi‑Fi signal is low"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Help & feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Enter password to perform factory reset in demo mode"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Next"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Password required"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/arrays.xml b/packages/SettingsLib/res/values-es-rUS/arrays.xml
index d574edd..09480a5 100644
--- a/packages/SettingsLib/res/values-es-rUS/arrays.xml
+++ b/packages/SettingsLib/res/values-es-rUS/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Siempre utilizar comprobación HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Predeterminado"</item>
+ <item msgid="7065842274271279580">"Usar selección del sistema (predeterminado)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Predeterminado"</item>
+ <item msgid="5062108632402595000">"Usar selección del sistema (predeterminado)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Predeterminado"</item>
+ <item msgid="3093023430402746802">"Usar selección del sistema (predeterminado)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Predeterminado"</item>
+ <item msgid="3214516120190965356">"Usar selección del sistema (predeterminado)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Predeterminado"</item>
+ <item msgid="2684127272582591429">"Usar selección del sistema (predeterminado)"</item>
<item msgid="5618929009984956469">"16 bits/muestra"</item>
<item msgid="3412640499234627248">"24 bits/muestra"</item>
<item msgid="121583001492929387">"32 bits/muestra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Predeterminado"</item>
+ <item msgid="1081159789834584363">"Usar selección del sistema (predeterminado)"</item>
<item msgid="4726688794884191540">"16 bits/muestra"</item>
<item msgid="305344756485516870">"24 bits/muestra"</item>
<item msgid="244568657919675099">"32 bits/muestra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Predeterminado"</item>
+ <item msgid="5226878858503393706">"Usar selección del sistema (predeterminado)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Predeterminado"</item>
+ <item msgid="4118561796005528173">"Usar selección del sistema (predeterminado)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Calidad de sonido (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Estándar (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Conexión preferida (330 kbps/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Optimizar para calidad de audio (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Calidad de audio y conexión equilibrada (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Optimizar para calidad de conexión (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Calidad de sonido (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Estándar (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Conexión preferida (330 kbps/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Optimizar para la calidad de audio"</item>
+ <item msgid="4327143584633311908">"Calidad de audio y conexión equilibrada"</item>
+ <item msgid="6155648878105378550">"Optimizar para calidad de conexión"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Desactivado"</item>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 59eb2eb..25ee5a8 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Datos móviles siempre activos"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Inhabilitar volumen absoluto"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Códec del audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Selecciona el códec A2DP de Bluetooth preferido"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Seleccionar códec del audio Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Frecuencia de muestreo del audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Selecciona la frecuencia de muestreo preferida del códec A2DP de Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Seleccionar códec del audio Bluetooth:\nVelocidad de la muestra"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits por muestra del audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Selecciona los bits por muestra del códec A2DP de Bluetooth preferidos"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Seleccionar códec del audio Bluetooth:\nBits por muestra"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Modo de canal del audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Selecciona el modo de canal del códec A2DP de Bluetooth preferido"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Calidad de reproducción LDAC del audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Selecciona la calidad de reproducción LDAC preferida del códec A2DP del audio Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Seleccionar códec del audio Bluetooth:\nModo de canal"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Códec del audio Bluetooth LDAC: calidad de reproducción"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Seleccionar códec del audio Bluetooth LDAC:\nCalidad de reproducción"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmitiendo: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opciones de certificación de pantalla inalámbrica"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar nivel de registro Wi-Fi; mostrar por SSID RSSI en el selector de Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Si está habilitada, la conexión Wi‑Fi será más intensa al transferir la conexión de datos al celular (si la señal Wi‑Fi es débil)."</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Ayuda y comentarios"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menú"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Ingresa contraseña y restablece en demo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Siguiente"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Contraseña obligatoria"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-es/arrays.xml b/packages/SettingsLib/res/values-es/arrays.xml
index a2ce809..b5160a4 100644
--- a/packages/SettingsLib/res/values-es/arrays.xml
+++ b/packages/SettingsLib/res/values-es/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Utilizar siempre comprobación de HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Predeterminado"</item>
+ <item msgid="7065842274271279580">"Usar preferencia del sistema (predeter.)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Predeterminado"</item>
+ <item msgid="5062108632402595000">"Usar preferencia del sistema (predeter.)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Predeterminado"</item>
+ <item msgid="3093023430402746802">"Usar preferencia del sistema (predeter.)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Predeterminado"</item>
+ <item msgid="3214516120190965356">"Usar preferencia del sistema (predeter.)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Predeterminado"</item>
+ <item msgid="2684127272582591429">"Usar preferencia del sistema (predeter.)"</item>
<item msgid="5618929009984956469">"16 bits por muestra"</item>
<item msgid="3412640499234627248">"24 bits por muestra"</item>
<item msgid="121583001492929387">"32 bits por muestra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Predeterminado"</item>
+ <item msgid="1081159789834584363">"Usar preferencia del sistema (predeter.)"</item>
<item msgid="4726688794884191540">"16 bits por muestra"</item>
<item msgid="305344756485516870">"24 bits por muestra"</item>
<item msgid="244568657919675099">"32 bits por muestra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Predeterminado"</item>
+ <item msgid="5226878858503393706">"Usar preferencia del sistema (predeter.)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Predeterminado"</item>
+ <item msgid="4118561796005528173">"Usar preferencia del sistema (predeter.)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Calidad sonido pref. (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Estándar (660 kbps / 606 kbps)"</item>
- <item msgid="4777177307869441982">"Conexión preferida (330 kbps / 303 kbps)"</item>
+ <item msgid="3411577996960199959">"Optimizar la calidad del audio (990/909&nbsp;kbps)"</item>
+ <item msgid="2921767058740704969">"Equilibrar la calidad del audio y de la conexión (660/606&nbsp;kbps)"</item>
+ <item msgid="3682554248829489641">"Optimizar la calidad de la conexión (330/303&nbsp;kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Calidad sonido pref. (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Estándar (660 kbps / 606 kbps)"</item>
- <item msgid="3367904477834831032">"Conexión preferida (330 kbps / 303 kbps)"</item>
+ <item msgid="7668834469173465015">"Optimizar la calidad del audio"</item>
+ <item msgid="4327143584633311908">"Equilibrar la calidad del audio y la de la conexión"</item>
+ <item msgid="6155648878105378550">"Optimizar la calidad de la conexión"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"No"</item>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index e4c9ba9..9d98096 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Datos móviles siempre activos"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Inhabilitar volumen absoluto"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Códec de audio por Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Selecciona el códec A2DP de Bluetooth preferido"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Selecciona el códec de audio por Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Porcentaje de muestreo de audio por Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Selecciona el porcentaje de muestreo del códec A2DP de Bluetooth preferido"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Selecciona el códec de audio por Bluetooth:\nFrecuencia de muestreo"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits de audio por Bluetooth por muestra"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Selecciona los bits del códec A2DP de Bluetooth preferidos por muestra"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Selecciona el códec de audio por Bluetooth:\nBits por muestra"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Modo de canal de audio por Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Selecciona el modo de canal del códec A2DP de Bluetooth preferido"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Calidad de la reproducción LDAC de audio por Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Selecciona la calidad de la reproducción LDAC del códec A2DP de Bluetooth preferida"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Selecciona el códec de audio por Bluetooth:\nModo de canal"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Selecciona el códec LDAC por Bluetooth: calidad de reproducción"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecciona el códec LDAC de audio por Bluetooth:\nCalidad de reproducción"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opciones para la certificación de la pantalla inalámbrica"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar el nivel de logging de Wi-Fi, mostrar por SSID RSSI en el selector Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Si está habilitada, la conexión Wi‑Fi será más agresiva al transferir la conexión de datos al móvil (si la señal Wi‑Fi no es estable)"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Ayuda y sugerencias"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menú"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Escribe una contraseña para restablecer datos de fábrica en modo demostración"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Siguiente"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Contraseña obligatoria"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-et/arrays.xml b/packages/SettingsLib/res/values-et/arrays.xml
index ba8e8de..f26543e 100644
--- a/packages/SettingsLib/res/values-et/arrays.xml
+++ b/packages/SettingsLib/res/values-et/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Kasuta alati HDCP-kontrollimist"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Vaikeseade"</item>
+ <item msgid="7065842274271279580">"Süsteemi valiku kasutamine (vaikeseade)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Vaikeseade"</item>
+ <item msgid="5062108632402595000">"Süsteemi valiku kasutamine (vaikeseade)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Vaikeseade"</item>
+ <item msgid="3093023430402746802">"Süsteemi valiku kasutamine (vaikeseade)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Vaikeseade"</item>
+ <item msgid="3214516120190965356">"Süsteemi valiku kasutamine (vaikeseade)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Vaikeseade"</item>
+ <item msgid="2684127272582591429">"Süsteemi valiku kasutamine (vaikeseade)"</item>
<item msgid="5618929009984956469">"16 bitti diskreedi kohta"</item>
<item msgid="3412640499234627248">"24 bitti diskreedi kohta"</item>
<item msgid="121583001492929387">"32 bitti diskreedi kohta"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Vaikeseade"</item>
+ <item msgid="1081159789834584363">"Süsteemi valiku kasutamine (vaikeseade)"</item>
<item msgid="4726688794884191540">"16 bitti diskreedi kohta"</item>
<item msgid="305344756485516870">"24 bitti diskreedi kohta"</item>
<item msgid="244568657919675099">"32 bitti diskreedi kohta"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Vaikeseade"</item>
+ <item msgid="5226878858503393706">"Süsteemi valiku kasutamine (vaikeseade)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Vaikeseade"</item>
+ <item msgid="4118561796005528173">"Süsteemi valiku kasutamine (vaikeseade)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Eelist. helikvaliteeti (990/909 kbit/s)"</item>
- <item msgid="138837449700903545">"Standardne (660/606 kbit/s)"</item>
- <item msgid="4777177307869441982">"Eelistatakse ühendust (330/303 kbit/s)"</item>
+ <item msgid="3411577996960199959">"Helikvaliteedi jaoks optimeerimine (990/909 kbit/s)"</item>
+ <item msgid="2921767058740704969">"Tasakaalustatud heli- ja ühenduskvaliteet (660/606 kbit/s)"</item>
+ <item msgid="3682554248829489641">"Ühenduskvaliteedi jaoks optimeerimine (330/303 kbit/s)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Eelist. helikvaliteeti (990/909 kbit/s)"</item>
- <item msgid="9091111147684472529">"Standardne (660/606 kbit/s)"</item>
- <item msgid="3367904477834831032">"Eelistatakse ühendust (330/303 kbit/s)"</item>
+ <item msgid="7668834469173465015">"Helikvaliteedi jaoks optimeerimine"</item>
+ <item msgid="4327143584633311908">"Tasakaalustatud heli- ja ühenduskvaliteet"</item>
+ <item msgid="6155648878105378550">"Ühenduskvaliteedi jaoks optimeerimine"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Väljas"</item>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index a7b066a..507bd20 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobiilne andmeside on alati aktiivne"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Keela absoluutne helitugevus"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetoothi heli kodek"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Eelistatud Bluetooth A2DP kodeki valimine"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Valige Bluetoothi helikodek"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetoothi heli diskreetimissagedus"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Bluetooth A2DP kodeki eelistatud diskreetimissageduse valimine"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Valige Bluetoothi helikodek:\ndiskreetimissagedus"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetoothi heli bitte diskreedi kohta"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Bluetooth A2DP kodeki eelistatud bittide arvu valimine diskreedi kohta"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Valige Bluetoothi helikodek:\nbitte diskreetimise kohta"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetoothi heli kanalirežiim"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Bluetooth A2DP kodeki eelistatud kanalirežiimi valimine"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetoothi heli LDAC esituskvaliteet"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Bluetooth A2DP kodeki eelistatud LDAC esituskvaliteedi valimine"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Valige Bluetoothi helikodek:\nkanalirežiim"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetoothi LDAC-helikodek: taasesituskvaliteet"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Valige Bluetoothi LDAC-helikodek:\ntaasesituskvaliteet"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Voogesitus: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Juhtmeta ekraaniühenduse sertifitseerimisvalikute kuvamine"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Suurenda WiFi logimistaset, kuva WiFi valijas SSID RSSI järgi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Kui see on lubatud, siis püüab WiFi nõrga WiFi-signaali korral agressiivsemalt anda andmeside ühenduse üle mobiilsele andmesidele"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Abi ja tagasiside"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menüü"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Sisestage parool, et demorežiimis tehaseseadetele lähtestada"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Järgmine"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Parool on kohustuslik"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index 222aba0..4c4e7e7 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Erabili beti HDCP egiaztapena"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Lehenetsia"</item>
+ <item msgid="7065842274271279580">"Erabili sistema-hautapena (lehenetsia)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Lehenetsia"</item>
+ <item msgid="5062108632402595000">"Erabili sistema-hautapena (lehenetsia)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Lehenetsia"</item>
+ <item msgid="3093023430402746802">"Erabili sistema-hautapena (lehenetsia)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Lehenetsia"</item>
+ <item msgid="3214516120190965356">"Erabili sistema-hautapena (lehenetsia)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Lehenetsia"</item>
+ <item msgid="2684127272582591429">"Erabili sistema-hautapena (lehenetsia)"</item>
<item msgid="5618929009984956469">"16 bit lagin bakoitzeko"</item>
<item msgid="3412640499234627248">"24 bit lagin bakoitzeko"</item>
<item msgid="121583001492929387">"32 bit lagin bakoitzeko"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Lehenetsia"</item>
+ <item msgid="1081159789834584363">"Erabili sistema-hautapena (lehenetsia)"</item>
<item msgid="4726688794884191540">"16 bit lagin bakoitzeko"</item>
<item msgid="305344756485516870">"24 bit lagin bakoitzeko"</item>
<item msgid="244568657919675099">"32 bit lagin bakoitzeko"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Lehenetsia"</item>
+ <item msgid="5226878858503393706">"Erabili sistema-hautapena (lehenetsia)"</item>
<item msgid="4106832974775067314">"Monoa"</item>
<item msgid="5571632958424639155">"Estereoa"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Lehenetsia"</item>
+ <item msgid="4118561796005528173">"Erabili sistema-hautapena (lehenetsia)"</item>
<item msgid="8900559293912978337">"Monoa"</item>
<item msgid="8883739882299884241">"Estereoa"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Soinu-kalitate hobetsia (990 kb/s edo 909 kb/s)"</item>
- <item msgid="138837449700903545">"Arrunta (660 kb/s edo 606 kb/s)"</item>
- <item msgid="4777177307869441982">"Konexio hobetsia (330 kb/s edo 303 kb/s)"</item>
+ <item msgid="3411577996960199959">"Optimizatu audioaren kalitatea areagotzeko (990 kbps / 909kbps)"</item>
+ <item msgid="2921767058740704969">"Orekatu audioaren eta konexioaren kalitateak (660 kbps / 606 kbps)"</item>
+ <item msgid="3682554248829489641">"Optimizatu konexioaren kalitatea areagotzeko (330 kbps / 303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Soinu-kalitate hobetsia (990 kb/s edo 909 kb/s)"</item>
- <item msgid="9091111147684472529">"Arrunta (660 kb/s edo 606 kb/s)"</item>
- <item msgid="3367904477834831032">"Konexio hobetsia (330 kb/s edo 303 kb/s)"</item>
+ <item msgid="7668834469173465015">"Optimizatu audioaren kalitatea areagotzeko"</item>
+ <item msgid="4327143584633311908">"Orekatu audioaren eta konexioaren kalitateak"</item>
+ <item msgid="6155648878105378550">"Optimizatu konexioaren kalitatea areagotzeko"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Desaktibatuta"</item>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 514d136..d8b2496 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mugikorreko datuak beti aktibo"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Desgaitu bolumen absolutua"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth bidezko audioaren kodeka"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Hautatu Bluetooth A2DP kodek hobetsia"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Hautatu Bluetooth audioaren kodeka"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth bidezko audioaren lagin-abiadura"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Hautatu Bluetooth A2DP kodekaren lagin-abiadura hobetsia"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Hautatu Bluetooth audioaren LDAC kodeka:\nlaginaren abiadura"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth bidezko audioaren lagin bakoitzeko bit kopurua"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Hautatu Bluetooth A2DP kodekaren lagin bakoitzeko bit kopuru hobetsia"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Hautatu Bluetooth audioaren kodeka:\nlagin bakoitzeko bitak"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth bidezko audioaren kanalaren modua"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Hautatu Bluetooth A2DP kodekaren kanalaren modu hobetsia"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth bidezko audioaren LDAC erreprodukzioaren kalitatea"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Hautatu Bluetooth A2DP kodekaren LDAC erreprodukzioaren kalitate hobetsia"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Hautatu Bluetooth audioaren kodeka:\nkanal modua"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth audioaren LDAC kodeka: erreprodukzioaren kalitatea"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Hautatu Bluetooth audioaren LDAC kodeka:\nerreprodukzioaren kalitatea"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Igortzean: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Erakutsi hari gabeko bistaratze-egiaztapenaren aukerak"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Erakutsi datu gehiago Wi-Fi sareetan saioa hasterakoan. Erakutsi sarearen identifikatzailea eta seinalearen indarra Wi‑Fi sareen hautagailuan."</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Aukera hori gaituz gero, gailua errazago aldatuko da datu mugikorren konexiora Wi-Fi seinalea ahultzen dela nabaritutakoan"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Laguntza eta iritziak"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menua"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Idatzi pasahitza jatorrizko ezarpenak demo moduan berrezartzeko"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Hurrengoa"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Pasahitza behar da"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fa/arrays.xml b/packages/SettingsLib/res/values-fa/arrays.xml
index 1ff0d25..293639c 100644
--- a/packages/SettingsLib/res/values-fa/arrays.xml
+++ b/packages/SettingsLib/res/values-fa/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"استفاده از بررسی HDCP فقط برای محتوای DRM"</item>
<item msgid="45075631231212732">"همیشه از بررسی HDCP استفاده شود"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"پیشفرض"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"پیشفرض"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"پیشفرض"</item>
- <item msgid="8895532488906185219">"۴۴٫۱ کیلوهرتز"</item>
- <item msgid="2909915718994807056">"۴۸٫۰ کیلوهرتز"</item>
- <item msgid="3347287377354164611">"۸۸٫۲ کیلوهرتز"</item>
- <item msgid="1234212100239985373">"۹۶٫۰ کیلوهرتز"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"پیشفرض"</item>
- <item msgid="4482862757811638365">"۴۴٫۱ کیلوهرتز"</item>
- <item msgid="354495328188724404">"۴۸٫۰ کیلوهرتز"</item>
- <item msgid="7329816882213695083">"۸۸٫۲ کیلوهرتز"</item>
- <item msgid="6967397666254430476">"۹۶٫۰ کیلوهرتز"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"پیشفرض"</item>
- <item msgid="5618929009984956469">"۱۶ بیت در هر نمونه"</item>
- <item msgid="3412640499234627248">"۲۴ بیت در هر نمونه"</item>
- <item msgid="121583001492929387">"۳۲ بیت در هر نمونه"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"پیشفرض"</item>
- <item msgid="4726688794884191540">"۱۶ بیت در هر نمونه"</item>
- <item msgid="305344756485516870">"۲۴ بیت در هر نمونه"</item>
- <item msgid="244568657919675099">"۳۲ بیت در هر نمونه"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"پیشفرض"</item>
- <item msgid="4106832974775067314">"مونو"</item>
- <item msgid="5571632958424639155">"استریو"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"پیشفرض"</item>
- <item msgid="8900559293912978337">"مونو"</item>
- <item msgid="8883739882299884241">"استریو"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"کیفیت صدای ترجیحی (۹۹۰کیلوبیت در ثانیه/۹۰۹کیلوبیت در ثانیه)"</item>
- <item msgid="138837449700903545">"استاندارد (۶۶۰کیلوبیت در ثانیه/۶۰۶کیلوبیت در ثانیه)"</item>
- <item msgid="4777177307869441982">"اتصال ترجیحی (۳۳۰کیلوبیت در ثانیه/۳۰۳کیلوبیت در ثانیه)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"کیفیت صدای ترجیحی (۹۹۰کیلوبیت در ثانیه/۹۰۹کیلوبیت در ثانیه)"</item>
- <item msgid="9091111147684472529">"استاندارد (۶۶۰کیلوبیت در ثانیه/۶۰۶کیلوبیت در ثانیه)"</item>
- <item msgid="3367904477834831032">"اتصال ترجیحی (۳۳۰کیلوبیت در ثانیه/۳۰۳کیلوبیت در ثانیه)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"خاموش"</item>
<item msgid="1593289376502312923">"۶۴ هزار"</item>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index d6518e1..61ec98e 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"داده سلولی همیشه فعال"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"غیرفعال کردن میزان صدای مطلق"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"کدک بلوتوث صوتی"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"انتخاب کدک A2DP در بلوتوث ترجیحی"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"سرعت نمونه بلوتوث صوتی"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"انتخاب سرعت نمونه کدک A2DP بلوتوث ترجیحی"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"بیتهای بلوتوث صوتی در هر نمونه"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"انتخاب بیتهای کدک A2DP بلوتوث ترجیحی هر نمونه"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"حالت کانال بلوتوث صوتی"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"انتخاب حالت کانال کدک A2DP بلوتوث ترجیحی"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"کیفیت پخش LDAC بلوتوث صوتی"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"انتخاب کیفیت پخش LDAC کدک A2DP بلوتوث ترجیحی"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"نمایش گزینهها برای گواهینامه نمایش بیسیم"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"افزایش سطح گزارشگیری Wi‑Fi، نمایش به ازای SSID RSSI در انتخابکننده Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"وقتی فعال است، در شرایط پایین بودن سیگنال، Wi‑Fi برای واگذار کردن اتصال داده به شبکه سلولی فعالتر خواهد بود."</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"راهنما و بازخورد"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"منو"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"برای انجام بازنشانی کارخانهای در حالت نمایشی، گذرواژه را وارد کنید"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"بعدی"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"وارد کردن گذرواژه الزامی است"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fi/arrays.xml b/packages/SettingsLib/res/values-fi/arrays.xml
index 5d842a4..2808bf2 100644
--- a/packages/SettingsLib/res/values-fi/arrays.xml
+++ b/packages/SettingsLib/res/values-fi/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Käytä HDCP-tarkistusta vain DRM-suojatulle sisällölle"</item>
<item msgid="45075631231212732">"Käytä aina HDCP-tarkistusta"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Oletus"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Oletus"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Oletus"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Oletus"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Oletus"</item>
- <item msgid="5618929009984956469">"16 bittiä/näyte"</item>
- <item msgid="3412640499234627248">"24 bittiä/näyte"</item>
- <item msgid="121583001492929387">"32 bittiä/näyte"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Oletus"</item>
- <item msgid="4726688794884191540">"16 bittiä/näyte"</item>
- <item msgid="305344756485516870">"24 bittiä/näyte"</item>
- <item msgid="244568657919675099">"32 bittiä/näyte"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Oletus"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Oletus"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Äänenlaatu etusijalla (990 kbps / 909 kbps)"</item>
- <item msgid="138837449700903545">"Vakio (660 kbps / 606 kbps)"</item>
- <item msgid="4777177307869441982">"Yhteys etusijalla (330 kbps / 303 kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Äänenlaatu etusijalla (990 kbps / 909 kbps)"</item>
- <item msgid="9091111147684472529">"Vakio (660 kbps / 606 kbps)"</item>
- <item msgid="3367904477834831032">"Yhteys etusijalla (330 kbps / 303 kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Ei käytössä"</item>
<item msgid="1593289376502312923">"64 kt"</item>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 2332fc5..a954b17 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobiilidata on aina käytössä"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Poista yleinen äänenvoimakkuuden säätö käytöstä"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth-äänen koodekki"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Valitse ensisijainen Bluetooth A2DP ‑koodekki"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth-ääninäytteen siirtonopeus"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Valitse ensisijainen Bluetooth A2DP ‑koodekkinäytteen siirtonopeus"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth-äänen bittiä/näyte-arvo"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Valitse ensisijainen Bluetooth A2DP ‑koodekin bittiä/näyte-arvo"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth-äänen kanavatila"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Valitse ensisijainen Bluetooth A2DP ‑koodekin kanavatila"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth-äänen LDAC-toiston laatu"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Valitse ensisijainen Bluetooth A2DP ‑koodekin LDAC-toiston laatu"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Näytä langattoman näytön sertifiointiin liittyvät asetukset"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Lisää Wi‑Fin lokikirjaustasoa, näytä SSID RSSI -kohtaisesti Wi‑Fi-valitsimessa."</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Kun asetus on käytössä, Wi-Fi siirtää datayhteyden aggressiivisemmin matkapuhelinverkolle, jos Wi-Fi-signaali on heikko."</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Ohje ja palaute"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Valikko"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Palauta tehdasasetukset antamalla salasana"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Seuraava"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Salasana vaaditaan"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/arrays.xml b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
index c50e576..7c403bf 100644
--- a/packages/SettingsLib/res/values-fr-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Utiliser la vérification HDCP uniquement pour le contenu GDN"</item>
<item msgid="45075631231212732">"Toujours utiliser la vérification HDCP"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Par défaut"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Par défaut"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Par défaut"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Par défaut"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Par défaut"</item>
- <item msgid="5618929009984956469">"16 bits par échantillon"</item>
- <item msgid="3412640499234627248">"24 bits par échantillon"</item>
- <item msgid="121583001492929387">"32 bits par échantillon"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Par défaut"</item>
- <item msgid="4726688794884191540">"16 bits par échantillon"</item>
- <item msgid="305344756485516870">"24 bits par échantillon"</item>
- <item msgid="244568657919675099">"32 bits par échantillon"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Par défaut"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stéréo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Par défaut"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stéréo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Qualité sonore préférée (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Standard (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Connexion préférée (330 kbps/303 kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Qualité sonore préférée (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Connexion préférée (330 kbps/303 kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Désactivé"</item>
<item msgid="1593289376502312923">"64 ko"</item>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index aedc9ca..973458d 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Données cellulaires toujours actives"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Désactiver le volume absolu"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Sélectionnez le codec Bluetooth A2DP préféré"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Taux d\'échantillonnage pour l\'audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Sélectionnez le taux d\'échantillonnage préféré pour le codec Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits par échantillon pour l\'audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Sélectionnez le nombre de bits par échantillon préféré pour le codec Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Mode de canal pour l\'audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Sélectionnez le mode de canal préféré pour le codec Bluetooth A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Qualité de lecture LDAC pour l\'audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Sélectionnez la qualité de lecture LDAC préférée pour le codec Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afficher les options pour la certification d\'affichage sans fil"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Détailler davantage les données Wi-Fi, afficher par SSID RSSI dans sélect. Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Si cette option est activée, le passage du Wi-Fi aux données cellulaires est forcé lorsque le signal Wi-Fi est faible"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Aide et commentaires"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Entrez m. passe pour réinit. en mode démo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Suivant"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Mot de passe obligatoire"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fr/arrays.xml b/packages/SettingsLib/res/values-fr/arrays.xml
index cda881c..c63275f 100644
--- a/packages/SettingsLib/res/values-fr/arrays.xml
+++ b/packages/SettingsLib/res/values-fr/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Utiliser la vérification HDCP uniquement pour le contenu DRM"</item>
<item msgid="45075631231212732">"Toujours utiliser la vérification HDCP"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Par défaut"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Par défaut"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Par défaut"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Par défaut"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Par défaut"</item>
- <item msgid="5618929009984956469">"16 bits par échantillon"</item>
- <item msgid="3412640499234627248">"24 bits par échantillon"</item>
- <item msgid="121583001492929387">"32 bits par échantillon"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Par défaut"</item>
- <item msgid="4726688794884191540">"16 bits par échantillon"</item>
- <item msgid="305344756485516870">"24 bits par échantillon"</item>
- <item msgid="244568657919675099">"32 bits par échantillon"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Par défaut"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stéréo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Par défaut"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stéréo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Qualité audio prioritaire (990/909 kbit/s)"</item>
- <item msgid="138837449700903545">"Standard (660/606 kbit/s)"</item>
- <item msgid="4777177307869441982">"Connexion prioritaire (330/303 kbits/s)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Qualité audio prioritaire (990/909 kbit/s)"</item>
- <item msgid="9091111147684472529">"Standard (660/606 kbit/s)"</item>
- <item msgid="3367904477834831032">"Connexion prioritaire (330/303 kbits/s)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Désactivé"</item>
<item msgid="1593289376502312923">"64 Ko"</item>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 8ba0b0f..9ce3d5a 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Données mobiles toujours actives"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Désactiver le volume absolu"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Sélectionner le codec A2DP Bluetooth prioritaire"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Taux d\'échantillonnage audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Sélectionner le taux d\'échantillonnage pour le codec A2DP Bluetooth prioritaire"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Nombre de bits par échantillon pour l\'audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Sélectionner le nombre de bits par échantillon pour le codec A2DP Bluetooth prioritaire"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Mode de chaîne de l\'audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Sélectionner le mode de chaîne pour le codec A2DP Bluetooth prioritaire"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Qualité de lecture LDAC de l\'audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Sélectionner la qualité de lecture LDAC pour le codec A2DP Bluetooth prioritaire"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afficher les options de la certification de l\'affichage sans fil"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Détailler plus infos Wi-Fi, afficher par RSSI de SSID dans outil sélection Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Si cette option est activée, le passage du Wi-Fi aux données mobiles est forcé en cas de signal Wi-Fi faible."</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Aide et commentaires"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Saisir mot de passe pour rétablir conf. d\'usine en mode démo."</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Suivant"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Veuillez saisir le mot de passe"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-gl/arrays.xml b/packages/SettingsLib/res/values-gl/arrays.xml
index e581368..2a89199 100644
--- a/packages/SettingsLib/res/values-gl/arrays.xml
+++ b/packages/SettingsLib/res/values-gl/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Utiliza a comprobación HDCP só para contido DRM"</item>
<item msgid="45075631231212732">"Utilizar sempre a comprobación HDCP"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Predeterminado"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Predeterminado"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Predeterminado"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Predeterminado"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Predeterminado"</item>
- <item msgid="5618929009984956469">"16 bits/mostra"</item>
- <item msgid="3412640499234627248">"24 bits/mostra"</item>
- <item msgid="121583001492929387">"32 bits/mostra"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Predeterminado"</item>
- <item msgid="4726688794884191540">"16 bits/mostra"</item>
- <item msgid="305344756485516870">"24 bits/mostra"</item>
- <item msgid="244568657919675099">"32 bits/mostra"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Predeterminado"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Estéreo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Predeterminado"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Estéreo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Calidade preferida (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Estándar (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Conexión preferida (330 kbps/303 kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Calidade preferida (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Estándar (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Conexión preferida (330 kbps/303 kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Desactivado"</item>
<item msgid="1593289376502312923">"64 K"</item>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 6a9a1ec..dfdb851 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Datos móbiles sempre activados"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Desactivar volume absoluto"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Códec de audio por Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Seleccionar códec A2DP de Bluetooth preferido"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Taxa de mostraxe de audio por Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Seleccionar taxa de mostraxe do códec A2DP de Bluetooth preferido"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits por mostra de audio por Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Seleccionar bits por mostra do códec A2DP de Bluetooth preferido"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Modo de canle de audio por Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Seleccionar modo de canle do códec A2DP de Bluetooth preferido"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Calidade de reprodución de LDAC de audio por Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Seleccionar calidade de reprodución de LDAC do códec A2DP de Bluetooth preferido"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostra opcións para o certificado de visualización sen fíos"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nivel de rexistro da wifi, mostrar por SSID RSSI no selector de wifi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Cando está activada esta función, a wifi será máis agresiva ao entregar a conexión de datos ao móbil, cando o sinal wifi é feble"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Axuda e suxestións"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menú"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Insire contrasinal para restablec. en demostración"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Seguinte"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"O contrasinal é obrigatorio"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-gu/arrays.xml b/packages/SettingsLib/res/values-gu/arrays.xml
index 742103b..e00c29f 100644
--- a/packages/SettingsLib/res/values-gu/arrays.xml
+++ b/packages/SettingsLib/res/values-gu/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"હંમેશા HDCP તપાસનો ઉપયોગ કરો"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ડિફૉલ્ટ"</item>
+ <item msgid="7065842274271279580">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ડિફૉલ્ટ"</item>
+ <item msgid="5062108632402595000">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ડિફૉલ્ટ"</item>
+ <item msgid="3093023430402746802">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ડિફૉલ્ટ"</item>
+ <item msgid="3214516120190965356">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ડિફૉલ્ટ"</item>
+ <item msgid="2684127272582591429">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
<item msgid="5618929009984956469">"16 બિટ/નમૂનો"</item>
<item msgid="3412640499234627248">"24 બિટ/નમૂનો"</item>
<item msgid="121583001492929387">"32 બિટ/નમૂનો"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ડિફૉલ્ટ"</item>
+ <item msgid="1081159789834584363">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
<item msgid="4726688794884191540">"16 બિટ/નમૂનો"</item>
<item msgid="305344756485516870">"24 બિટ/નમૂનો"</item>
<item msgid="244568657919675099">"32 બિટ/નમૂનો"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ડિફૉલ્ટ"</item>
+ <item msgid="5226878858503393706">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
<item msgid="4106832974775067314">"મૉનો"</item>
<item msgid="5571632958424639155">"સ્ટીરિઓ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ડિફૉલ્ટ"</item>
+ <item msgid="4118561796005528173">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
<item msgid="8900559293912978337">"મૉનો"</item>
<item msgid="8883739882299884241">"સ્ટીરિઓ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"પસંદગીની અવાજ ગુણવત્તા (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"માનક (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"પસંદગીનું કનેક્શન (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"ઑડિઓની ગુણવત્તા માટે ઑપ્ટિમાઇઝ કરો (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"સંતુલિત ઑડિઓ અને કનેક્શનની ગુણવત્તા (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"કનેક્શનની ગુણવત્તા માટે ઑપ્ટિમાઇઝ કરો (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"પસંદગીની અવાજ ગુણવત્તા (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"માનક (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"પસંદગીનું કનેક્શન (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"ઑડિઓ ગુણવત્તા માટે ઑપ્ટિમાઇઝ કરો"</item>
+ <item msgid="4327143584633311908">"સંતુલિત ઑડિઓ અને કનેક્શનની ગુણવત્તા"</item>
+ <item msgid="6155648878105378550">"કનેક્શનની ગુણવત્તા માટે ઑપ્ટિમાઇઝ કરો"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"બંધ"</item>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index e61e6e2..f6d2327 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"સેલ્યુલર ડેટા હંમેશા સક્રિય"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"ચોક્કસ વૉલ્યૂમને અક્ષમ કરો"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth ઑડિઓ કોડેક"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"પસંદગીના Bluetooth A2DP કોડેકને પસંદ કરો"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth ઑડિઓ LDAC કોડેક પસંદ કરો"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth ઑડિઓ નમૂના દર"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"પસંદગીના Bluetooth A2DP કોડેક નમૂના દરને પસંદ કરો"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth ઑડિઓ LDAC કોડેક પસંદ કરો:\nનમૂના દર"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"નમૂના દીઠ Bluetooth ઑડિઓ બિટ"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"નમૂના દીઠ પસંદગીના Bluetooth A2DP કોડેક બિટને પસંદ કરો"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth ઑડિઓ કોડેક પસંદ કરો:\nનમૂના દીઠ બિટ"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth ઑડિઓ ચેનલ મોડ"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"પસંદગીના Bluetooth A2DP કોડેક ચેનલ મોડને પસંદ કરો"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth ઑડિઓ LDAC પ્લેબેક ગુણવત્તા"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"પસંદગીની Bluetooth A2DP કોડેક LDAC પ્લેબેક ગુણવત્તા પસંદ કરો"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth ઑડિઓ કોડેક પસંદ કરો:\nચૅનલ મોડ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth ઑડિઓ LDAC કોડેક: પ્લેબૅક ગુણવત્તા"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth ઑડિઓ LDAC કોડેક પસંદ કરો:\nપ્લેબૅક ગુણવત્તા"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"સ્ટ્રીમિંગ: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"વાયરલેસ ડિસ્પ્લે પ્રમાણપત્ર માટેના વિકલ્પો બતાવો"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi લોગિંગ સ્તર વધારો, Wi‑Fi પીકરમાં SSID RSSI દીઠ બતાવો"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"જ્યારે સક્ષમ હોય, ત્યારે Wi‑Fi સિગ્નલ ઓછા હોવા પર, સેલ્યુલર પર ડેટા કનેક્શન મોકલવામાં વધુ આક્રમક હશે"</string>
@@ -352,4 +353,10 @@
<string name="help_feedback_label" msgid="6815040660801785649">"સહાય અને પ્રતિસાદ"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"મેનુ"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <!-- no translation found for retail_demo_reset_message (118771671364131297) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_next (8356731459226304963) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_title (696589204029930100) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hi/arrays.xml b/packages/SettingsLib/res/values-hi/arrays.xml
index 11b83c9..ecf101a 100644
--- a/packages/SettingsLib/res/values-hi/arrays.xml
+++ b/packages/SettingsLib/res/values-hi/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"हमेशा HDCP जांच का उपयोग करें"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"डिफ़ॉल्ट"</item>
+ <item msgid="7065842274271279580">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"डिफ़ॉल्ट"</item>
+ <item msgid="5062108632402595000">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"डिफ़ॉल्ट"</item>
+ <item msgid="3093023430402746802">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"डिफ़ॉल्ट"</item>
+ <item msgid="3214516120190965356">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"डिफ़ॉल्ट"</item>
+ <item msgid="2684127272582591429">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="5618929009984956469">"16 बिट/नमूना"</item>
<item msgid="3412640499234627248">"24 बिट/नमूना"</item>
<item msgid="121583001492929387">"32 बिट/नमूना"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"डिफ़ॉल्ट"</item>
+ <item msgid="1081159789834584363">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="4726688794884191540">"16 बिट/नमूना"</item>
<item msgid="305344756485516870">"24 बिट/नमूना"</item>
<item msgid="244568657919675099">"32 बिट/नमूना"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"डिफ़ॉल्ट"</item>
+ <item msgid="5226878858503393706">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="4106832974775067314">"मोनो"</item>
<item msgid="5571632958424639155">"स्टीरियो"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"डिफ़ॉल्ट"</item>
+ <item msgid="4118561796005528173">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="8900559293912978337">"मोनो"</item>
<item msgid="8883739882299884241">"स्टीरियो"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"पसंदीदा ध्वनि गुणवत्ता (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"मानक (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"पसंदीदा कनेक्शन (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"ऑडियो गुणवत्ता के अनुसार बदलाव करें (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"संतुलित ऑडियो और कनेक्शन गुणवत्ता (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"कनेक्शन गुणवत्ता के अनुसार बदलाव करें (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"पसंदीदा ध्वनि गुणवत्ता (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"मानक (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"पसंदीदा कनेक्शन (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"ऑडियो गुणवत्ता के अनुसार बदलाव करें"</item>
+ <item msgid="4327143584633311908">"संतुलित ऑडियो और कनेक्शन गुणवत्ता"</item>
+ <item msgid="6155648878105378550">"कनेक्शन गुणवत्ता के अनुसार बदलाव करें"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"बंद"</item>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 7ba3d54..f0cb2e3 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"सेल्युलर डेटा हमेशा सक्रिय"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"पूर्ण वॉल्यूम अक्षम करें"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"ब्लूटूथ ऑडियो कोडेक"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"पसंदीदा ब्लूटूथ A2DP कोडेक चुनें"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"ब्लूटूथ ऑडियो कोडेक चुनें"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"ब्लूटूथ ऑडियो नमूना दर"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"पसंदीदा ब्लूटूथ A2DP कोडेक नमूना दर चुनें"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"ब्लूटूथ ऑडियो कोडेक चुनें:\nनमूना दर"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"ब्लूटूथ ऑडियो बिट प्रति नमूना"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"पसंदीदा ब्लूटूथ A2DP कोडेक बिट प्रति नमूना चुनें"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"ब्लूटूथ ऑडियो कोडेक चुनें:\nबिट प्रति नमूना"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"ब्लूटूथ ऑडियो चैनल मोड"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"पसंदीदा ब्लूटूथ A2DP कोडेक चैनल मोड चुनें"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"ब्लूटूथ ऑडियो LDAC प्लेबैक गुणवत्ता"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"पसंदीदा ब्लूटूथ A2DP कोडेक LDAC प्लेबैक गुणवत्ता चुनें"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"ब्लूटूथ ऑडियो कोडेक चुनें:\nचैनल मोड"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ब्लूटूथ ऑडियो LDAC कोडेक: प्लेबैक क्वालिटी"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ब्लूटूथ ऑडियो LDAC कोडेक चुनें:\nप्लेबैक क्वालिटी"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"स्ट्रीम हो रहा है: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"वायरलेस दिखाई देने के लिए प्रमाणन विकल्प दिखाएं"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"वाई-फ़ाई प्रवेश स्तर बढ़ाएं, वाई-फ़ाई पिकर में प्रति SSID RSSI दिखाएं"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"इसके सक्षम होने पर, जब वाई-फ़ाई संकेत कमज़ोर हों तो वाई-फ़ाई, डेटा कनेक्शन को सेल्यूलर पर अधिक बलपूर्वक भेजेगा"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"सहायता और फ़ीडबैक"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"मेनू"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"डेमो मोड में फ़ैक्टरी रीसेट के लिए पासवर्ड डालें"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"आगे"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"पासवर्ड आवश्यक"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hr/arrays.xml b/packages/SettingsLib/res/values-hr/arrays.xml
index 06e3f51..2b70c78 100644
--- a/packages/SettingsLib/res/values-hr/arrays.xml
+++ b/packages/SettingsLib/res/values-hr/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Uvijek upotrebljavaj HDCP provjeru"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Zadano"</item>
+ <item msgid="7065842274271279580">"Upotreba odabira sustava (zadano)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Zadano"</item>
+ <item msgid="5062108632402595000">"Upotreba odabira sustava (zadano)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Zadano"</item>
+ <item msgid="3093023430402746802">"Upotreba odabira sustava (zadano)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Zadano"</item>
+ <item msgid="3214516120190965356">"Upotreba odabira sustava (zadano)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Zadano"</item>
+ <item msgid="2684127272582591429">"Upotreba odabira sustava (zadano)"</item>
<item msgid="5618929009984956469">"16 bitova po uzorku"</item>
<item msgid="3412640499234627248">"24 bita po uzorku"</item>
<item msgid="121583001492929387">"32 bita po uzorku"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Zadano"</item>
+ <item msgid="1081159789834584363">"Upotreba odabira sustava (zadano)"</item>
<item msgid="4726688794884191540">"16 bitova po uzorku"</item>
<item msgid="305344756485516870">"24 bita po uzorku"</item>
<item msgid="244568657919675099">"32 bita po uzorku"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Zadano"</item>
+ <item msgid="5226878858503393706">"Upotreba odabira sustava (zadano)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Zadano"</item>
+ <item msgid="4118561796005528173">"Upotreba odabira sustava (zadano)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Željena kval. zvuka (990 kb/s / 909 kb/s)"</item>
- <item msgid="138837449700903545">"Standardna (660 kb/s / 606 kb/s)"</item>
- <item msgid="4777177307869441982">"Željeno povezivanje (330 kb/s / 303 kb/s)"</item>
+ <item msgid="3411577996960199959">"Optimizacija za kvalitetu audioreprodukcije (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Uravnotežena kvaliteta audioreprodukcije i veze (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Optimizacija za kvalitetu veze (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Željena kval. zvuka (990 kb/s / 909 kb/s)"</item>
- <item msgid="9091111147684472529">"Standardna (660 kb/s / 606 kb/s)"</item>
- <item msgid="3367904477834831032">"Željeno povezivanje (330 kb/s / 303 kb/s)"</item>
+ <item msgid="7668834469173465015">"Optimizacija za kvalitetu audioreprodukcije"</item>
+ <item msgid="4327143584633311908">"Uravnotežena kvaliteta audioreprodukcije i veze"</item>
+ <item msgid="6155648878105378550">"Optimizacija za kvalitetu veze"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Isključeno"</item>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 598e5cb..4de241a 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobilni podaci uvijek aktivni"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Onemogući apsolutnu glasnoću"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Kodek za Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Odabir željenog Bluetooth A2DP kodeka"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Odaberi kodek za Bluetooth Audio"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Brzina uzorka za Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Odabir željenu brzinu uzorka Bluetooth A2DP kodeka"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Odaberi kodek za Bluetooth Audio:\nbrzina uzorkovanja"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bitovi po uzorku za Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Odabir željenog broja bitova po uzorku za Bluetooth A2DP kodek"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Odaberi kodek za Bluetooth Audio:\nbitovi po uzorku"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Način kanala za Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Odabir željenog načina kanala Bluetooth A2DP kodeka"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"LDAC kvaliteta reprodukcije za Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Odabir željene kvalitete reprodukcije Bluetooth A2DP kodeka LDAC"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Odaberi kodek za Bluetooth Audio:\nnačin kanala"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek za Bluetooth Audio LDAC: kvaliteta reprodukcije"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Odaberi kodek za Bluetooth Audio LDAC:\nkvaliteta reprodukcije"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strujanje: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Prikaži opcije za certifikaciju bežičnog prikaza"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećana razina prijave na Wi‑Fi, prikaz po SSID RSSI-ju u Biraču Wi‑Fi-ja"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Ako je omogućeno, Wi-Fi će aktivno prebacivati podatkovnu vezu mobilnoj mreži kada je Wi-Fi signal slab."</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Pomoć i povratne informacije"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Izbornik"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Unesite zaporku za resetiranje u demu"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Dalje"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Potrebna je zaporka"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hu/arrays.xml b/packages/SettingsLib/res/values-hu/arrays.xml
index 4cc67a3..99727cc 100644
--- a/packages/SettingsLib/res/values-hu/arrays.xml
+++ b/packages/SettingsLib/res/values-hu/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Mindig használjon HDCP ellenőrzést"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Alapértelmezett"</item>
+ <item msgid="7065842274271279580">"Rendszerérték (alapértelmezett)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Alapértelmezett"</item>
+ <item msgid="5062108632402595000">"Rendszerérték (alapértelmezett)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Alapértelmezett"</item>
+ <item msgid="3093023430402746802">"Rendszerérték (alapértelmezett)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Alapértelmezett"</item>
+ <item msgid="3214516120190965356">"Rendszerérték (alapértelmezett)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Alapértelmezett"</item>
+ <item msgid="2684127272582591429">"Rendszerérték (alapértelmezett)"</item>
<item msgid="5618929009984956469">"16 bit/minta"</item>
<item msgid="3412640499234627248">"24 bit/minta"</item>
<item msgid="121583001492929387">"32 bit/minta"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Alapértelmezett"</item>
+ <item msgid="1081159789834584363">"Rendszerérték (alapértelmezett)"</item>
<item msgid="4726688794884191540">"16 bit/minta"</item>
<item msgid="305344756485516870">"24 bit/minta"</item>
<item msgid="244568657919675099">"32 bit/minta"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Alapértelmezett"</item>
+ <item msgid="5226878858503393706">"Rendszerérték (alapértelmezett)"</item>
<item msgid="4106832974775067314">"Monó"</item>
<item msgid="5571632958424639155">"Sztereó"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Alapértelmezett"</item>
+ <item msgid="4118561796005528173">"Rendszerérték (alapértelmezett)"</item>
<item msgid="8900559293912978337">"Monó"</item>
<item msgid="8883739882299884241">"Sztereó"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Minőség előnyben részesítése (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Alapértelmezett (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Kapcsolat előnyben részesítése (330 kbps/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Optimalizálás hangminőséghez (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Kiegyensúlyozott hang- és kapcsolatminőség (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Optimalizálás kapcsolatminőséghez (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Minőség előnyben részesítése (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Alapértelmezett (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Kapcsolat előnyben részesítése (330 kbps/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Optimalizálás hangminőséghez"</item>
+ <item msgid="4327143584633311908">"Kiegyensúlyozott hang- és kapcsolatminőség"</item>
+ <item msgid="6155648878105378550">"Optimalizálás kapcsolatminőséghez"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Ki"</item>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index f4be3ec..15417d6 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"A mobilhálózati adatforgalom mindig aktív"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Abszolút hangerő funkció letiltása"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth hang – Kodek"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Előnyben részesített kodek kiválasztása a Bluetooth A2DP profiljához"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth hangkodek kiválasztása"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth hang – mintavételezési gyakoriság"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Kodek előnyben részesített mintavételezési gyakoriságának kiválasztása a Bluetooth A2DP profiljához"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth hangkodek kiválasztása:\nmintavételi gyakoriság"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth hang – bit/minta"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Kodek előnyben részesített bit/minta mennyiségének kiválasztása a Bluetooth A2DP profiljához"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth hangkodek kiválasztása:\nbit/minta"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth hang – Csatornamód"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Kodek előnyben részesített csatornamódjának kiválasztása a Bluetooth A2DP profiljához"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth hang – LDAC lejátszási minőség"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Kodek előnyben részesített LDAC lejátszási minőségének kiválasztása a Bluetooth A2DP profiljához"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth hangkodek kiválasztása:\ncsatornamód"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth LDAC hangkodek: lejátszási minőség"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth LDAC hangkodek kiválasztása:\nlejátszási minőség"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamelés: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Vezeték nélküli kijelző tanúsítványával kapcsolatos lehetőségek megjelenítése"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi-naplózási szint növelése, RSSI/SSID megjelenítése a Wi‑Fi-választóban"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Ha engedélyezi, a Wi-Fi agresszívebben fogja átadni az adatkapcsolatot a mobilhálózatnak gyenge Wi-Fi-jel esetén"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Súgó és visszajelzés"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menü"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Írja be a jelszót a visszaállításhoz"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Következő"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Jelszó szükséges"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hy/arrays.xml b/packages/SettingsLib/res/values-hy/arrays.xml
index 1d72787..e3b553e 100644
--- a/packages/SettingsLib/res/values-hy/arrays.xml
+++ b/packages/SettingsLib/res/values-hy/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Միշտ օգտագործել HDCP ստուգումը"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Կանխադրված"</item>
+ <item msgid="7065842274271279580">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Կանխադրված"</item>
+ <item msgid="5062108632402595000">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Կանխադրված"</item>
+ <item msgid="3093023430402746802">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item>
<item msgid="8895532488906185219">"44,1 կՀց"</item>
<item msgid="2909915718994807056">"48,0 կՀց"</item>
<item msgid="3347287377354164611">"88,2 կՀց"</item>
<item msgid="1234212100239985373">"96,0 կՀց"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Կանխադրված"</item>
+ <item msgid="3214516120190965356">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item>
<item msgid="4482862757811638365">"44,1 կՀց"</item>
<item msgid="354495328188724404">"48,0 կՀց"</item>
<item msgid="7329816882213695083">"88,2 կՀց"</item>
<item msgid="6967397666254430476">"96,0 կՀց"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Կանխադրված"</item>
+ <item msgid="2684127272582591429">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item>
<item msgid="5618929009984956469">"16 բիթ/նմուշ"</item>
<item msgid="3412640499234627248">"24 բիթ/նմուշ"</item>
<item msgid="121583001492929387">"32 բիթ/նմուշ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Կանխադրված"</item>
+ <item msgid="1081159789834584363">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item>
<item msgid="4726688794884191540">"16 բիթ/նմուշ"</item>
<item msgid="305344756485516870">"24 բիթ/նմուշ"</item>
<item msgid="244568657919675099">"32 բիթ/նմուշ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Կանխադրված"</item>
+ <item msgid="5226878858503393706">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item>
<item msgid="4106832974775067314">"Մոնո"</item>
<item msgid="5571632958424639155">"Ստերեո"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Կանխադրված"</item>
+ <item msgid="4118561796005528173">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item>
<item msgid="8900559293912978337">"Մոնո"</item>
<item msgid="8883739882299884241">"Ստերեո"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Նախընտրած ձայնի որակ (990 կբ/վ / 909 կբ/վ)"</item>
- <item msgid="138837449700903545">"Ստանդարտ (660 կբ/վ / 606 կբ/վ)"</item>
- <item msgid="4777177307869441982">"Նախընտրած կապ (330 կբ/վ / 303 կբ/վ)"</item>
+ <item msgid="3411577996960199959">"Օպտիմալացնել ձայնի որակը (990 կբ/վ / 909 կբ/վ)"</item>
+ <item msgid="2921767058740704969">"Ձայնի և կապի հավասարակշռված որակ (660 կբ/վ / 606 կբ/վ)"</item>
+ <item msgid="3682554248829489641">"Օպտիմալացնել կապի որակը (330 կբ/վ / 303 կբ/վ)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Նախընտրած ձայնի որակ (990 կբ/վ / 909 կբ/վ)"</item>
- <item msgid="9091111147684472529">"Ստանդարտ (660 կբ/վ / 606 կբ/վ)"</item>
- <item msgid="3367904477834831032">"Նախընտրած կապ (330 կբ/վ / 303 կբ/վ)"</item>
+ <item msgid="7668834469173465015">"Օպտիմալացնել ձայնի որակը"</item>
+ <item msgid="4327143584633311908">"Ձայնի և կապի հավասարակշռված որակ"</item>
+ <item msgid="6155648878105378550">"Օպտիմալացնել կապի որակը"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Անջատված է"</item>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 5ee1570..c75220f 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Բջջային տվյալները՝ միշտ ակտիվացրած"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Անջատել ձայնի բացարձակ ուժգնությունը"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth աուդիո կոդեկ"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Ընտրեք Bluetooth A2DP նախընտրելի կոդեկը"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Ընտրեք Bluetooth աուդիո կոդեկը"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth աուդիոյի Ընդհատավորման հաճախականությունը"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Ընտրեք Bluetooth A2DP կոդեկի Ընդհատավորման նախընտրելի հաճախականությունը"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Ընտրեք Bluetooth աուդիո կոդեկը՝\nընդհատավորման հաճախականություն"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth աուդիո, բիթ / նմուշ"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Ընտրեք Bluetooth A2DP կոդեկի նախընտրելի որակը, բիթ / նմուշ"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Ընտրեք Bluetooth աուդիո կոդեկը՝\nբիթ/նմուշ"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth աուդիո կապուղու ռեժիմը"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Ընտրեք Bluetooth A2DP կոդեկի կապուղու նախընտրելի ռեժիմը"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth աուդիո LDAC նվագարկման որակը"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Ընտրեք Bluetooth A2DP կոդեկի LDAC նվագարկման նախընտրելի որակը"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Ընտրեք Bluetooth աուդիո կոդեկը՝\nկապուղու ռեժիմ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth աուդիո LDAC կոդեկ՝ նվագարկման որակ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Ընտրեք Bluetooth աուդիո LDAC կոդեկը՝\nնվագարկման որակ"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Հեռարձակում՝ <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Ցույց տալ անլար էկրանի հավաստագրման ընտրանքները"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Բարձրացնել մակարդակը, Wi‑Fi ընտրիչում ամեն մի SSID-ի համար ցույց տալ RSSI"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Եթե այս գործառույթը միացված է, Wi‑Fi-ի թույլ ազդանշանի դեպքում Wi‑Fi ինտերնետից անցումը բջջային ինտերնետին ավելի կտրուկ կլինի"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Օգնություն և հետադարձ կապ"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Ընտրացանկ"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Մուտքագրեք գաղտնաբառը՝ ցուցադրական ռեժիմում գործարանային վերակայում կատարելու համար"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Հաջորդը"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Պահանջվում է գաղտնաբառ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index be3d89c..9d56ba7 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Selalu gunakan pemeriksaan HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Default"</item>
+ <item msgid="7065842274271279580">"Gunakan Pilihan Sistem (Default)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Default"</item>
+ <item msgid="5062108632402595000">"Gunakan Pilihan Sistem (Default)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Default"</item>
+ <item msgid="3093023430402746802">"Gunakan Pilihan Sistem (Default)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Default"</item>
+ <item msgid="3214516120190965356">"Gunakan Pilihan Sistem (Default)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Default"</item>
+ <item msgid="2684127272582591429">"Gunakan Pilihan Sistem (Default)"</item>
<item msgid="5618929009984956469">"16 bit/sampel"</item>
<item msgid="3412640499234627248">"24 bit/sampel"</item>
<item msgid="121583001492929387">"32 bit/sampel"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Default"</item>
+ <item msgid="1081159789834584363">"Gunakan Pilihan Sistem (Default)"</item>
<item msgid="4726688794884191540">"16 bit/sampel"</item>
<item msgid="305344756485516870">"24 bit/sampel"</item>
<item msgid="244568657919675099">"32 bit/sampel"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Default"</item>
+ <item msgid="5226878858503393706">"Gunakan Pilihan Sistem (Default)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Default"</item>
+ <item msgid="4118561796005528173">"Gunakan Pilihan Sistem (Default)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Kualitas suara yang disukai (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Standar (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Sambungan yang disukai (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"Optimalkan untuk Kualitas Audio (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"Kualitas Audio dan Sambungan Seimbang (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"Optimalkan untuk Kualitas Sambungan (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Kualitas suara yang disukai (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Standar (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Sambungan yang disukai (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"Optimalkan untuk Kualitas Audio"</item>
+ <item msgid="4327143584633311908">"Kualitas Audio dan Sambungan Seimbang"</item>
+ <item msgid="6155648878105378550">"Optimalkan untuk Kualitas Sambungan"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Nonaktif"</item>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 97eca8e..12d61fe 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Data seluler selalu aktif"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Nonaktifkan volume absolut"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Pilih Codec A2DP Bluetooth Yang Disukai"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Pilih Codec Audio Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Frekuensi Sampel Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Pilih Frekuensi Sampel Codec A2DP Bluetooth Yang Disukai"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Pilih Codec Audio Bluetooth:\nFrekuensi Sampel"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bit Per Sampel Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Pilih Bit Per Sampel Codec A2DP Bluetooth Yang Disukai"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Pilih Codec Audio Bluetooth:\nBit Per Sampel"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Mode Channel Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Pilih Mode Channel Codec A2DP Bluetooth Yang Disukai"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Kualitas Pemutaran LDAC Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Pilih Kualitas Pemutaran LDAC Codec A2DP Bluetooth Yang Disukai"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Pilih Codec Audio Bluetooth:\nMode Channel"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC Audio Bluetooth: Kualitas Pemutaran"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Pilih Codec LDAC Audio Bluetooth:\nKualitas Pemutaran"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Tampilkan opsi untuk sertifikasi layar nirkabel"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Tingkatkan level pencatatan log Wi-Fi, tampilkan per SSID RSSI di Pemilih Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Jika diaktifkan, Wi-Fi akan menjadi lebih agresif dalam mengalihkan sambungan data ke Seluler saat sinyal Wi-Fi lemah"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Bantuan & masukan"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Masukkan sandi untuk mengembalikan ke setelan pabrik dalam mode demo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Berikutnya"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Perlu sandi"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-is/arrays.xml b/packages/SettingsLib/res/values-is/arrays.xml
index dbb1636..e3f2ba3 100644
--- a/packages/SettingsLib/res/values-is/arrays.xml
+++ b/packages/SettingsLib/res/values-is/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Nota alltaf HDCP-eftirlit"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Sjálfgefið"</item>
+ <item msgid="7065842274271279580">"Nota val kerfisins (sjálfgefið)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Sjálfgefið"</item>
+ <item msgid="5062108632402595000">"Nota val kerfisins (sjálfgefið)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Sjálfgefið"</item>
+ <item msgid="3093023430402746802">"Nota val kerfisins (sjálfgefið)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Sjálfgefið"</item>
+ <item msgid="3214516120190965356">"Nota val kerfisins (sjálfgefið)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Sjálfgefið"</item>
+ <item msgid="2684127272582591429">"Nota val kerfisins (sjálfgefið)"</item>
<item msgid="5618929009984956469">"16 bitar/úrtak"</item>
<item msgid="3412640499234627248">"24 bitar/úrtak"</item>
<item msgid="121583001492929387">"32 bitar/úrtak"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Sjálfgefið"</item>
+ <item msgid="1081159789834584363">"Nota val kerfisins (sjálfgefið)"</item>
<item msgid="4726688794884191540">"16 bitar/úrtak"</item>
<item msgid="305344756485516870">"24 bitar/úrtak"</item>
<item msgid="244568657919675099">"32 bitar/úrtak"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Sjálfgefið"</item>
+ <item msgid="5226878858503393706">"Nota val kerfisins (sjálfgefið)"</item>
<item msgid="4106832974775067314">"Einóma"</item>
<item msgid="5571632958424639155">"Víðóma"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Sjálfgefið"</item>
+ <item msgid="4118561796005528173">"Nota val kerfisins (sjálfgefið)"</item>
<item msgid="8900559293912978337">"Einóma"</item>
<item msgid="8883739882299884241">"Víðóma"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Hljóðgæði í forgangi (990kb/s / 909kb/s)"</item>
- <item msgid="138837449700903545">"Venjulegt (660kb/s / 606kb/s)"</item>
- <item msgid="4777177307869441982">"Tenging í forgangi (330kb/s / 303kb/s)"</item>
+ <item msgid="3411577996960199959">"Fínstilla fyrir hljóðgæði (990 kbps / 909 kbps)"</item>
+ <item msgid="2921767058740704969">"Jafnvægi á milli gæða hljóðs og tengingar (660 kbps / 606 kbps)"</item>
+ <item msgid="3682554248829489641">"Fínstilla fyrir gæði tengingar (330 kbps / 303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Hljóðgæði í forgangi (990kb/s / 909kb/s)"</item>
- <item msgid="9091111147684472529">"Venjulegt (660kb/s / 606kb/s)"</item>
- <item msgid="3367904477834831032">"Tenging í forgangi (330kb/s / 303kb/s)"</item>
+ <item msgid="7668834469173465015">"Fínstilla fyrir hljóðgæði"</item>
+ <item msgid="4327143584633311908">"Jafnvægi á milli gæða hljóðs og tengingar"</item>
+ <item msgid="6155648878105378550">"Fínstilla fyrir gæði tengingar"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Slökkt"</item>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 4cecdbc..844a3b4 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Alltaf kveikt á farsímagögnum"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Slökkva á samstillingu hljóðstyrks"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth hljóðkóðari"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Veldu Bluetooth A2DP kóðara"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Velja Bluetooth-hljóðkóðara"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth hljóðtökutíðni"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Veldu Bluetooth A2DP kóðaratökutíðni"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Velja hljóðkóðara Bluetooth:\ntökutíðni"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth hljóðbitar í úrtaki"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Veldu Bluetooth A2DP kóðarabita í úrtaki"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Velja hljóðkóðara Bluetooth:\nbitar í úrtaki"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Hljóðrásarstilling Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Veldu Bluetooth A2DP stillingu kóðararásar"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth LDAC gæði hljóðspilunar"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Veldu Bluetooth LDAC spilunargæði A2DP kóðara"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Velja hljóðkóðara Bluetooth:\nstilling rásar"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth LDAC-hljóðkóðari: gæði spilunar"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Velja Bluetooth LDAC-hljóðkóðara:\ngæði spilunar"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streymi: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Sýna valkosti fyrir vottun þráðlausra skjáa"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Auka skráningarstig Wi-Fi, sýna RSSI fyrir hvert SSID í Wi-Fi vali"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Þegar þetta er virkt mun Wi-Fi ganga harðar fram í að færa gagnatenginguna yfir til símkerfisins þegar Wi-Fi merkið er lélegt"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Hjálp og ábendingar"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Valmynd"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Sláðu inn aðgangsorð til að framkvæma núllstillingu í sýnisútgáfu"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Áfram"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Aðgangsorðs krafist"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-it/arrays.xml b/packages/SettingsLib/res/values-it/arrays.xml
index 5349086..588b23c 100644
--- a/packages/SettingsLib/res/values-it/arrays.xml
+++ b/packages/SettingsLib/res/values-it/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Usa sempre la verifica HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Valore predefinito"</item>
+ <item msgid="7065842274271279580">"Usa selezione di sistema (predefinita)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Valore predefinito"</item>
+ <item msgid="5062108632402595000">"Usa selezione di sistema (predefinita)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Valore predefinito"</item>
+ <item msgid="3093023430402746802">"Usa selezione di sistema (predefinita)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Valore predefinito"</item>
+ <item msgid="3214516120190965356">"Usa selezione di sistema (predefinita)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Valore predefinito"</item>
+ <item msgid="2684127272582591429">"Usa selezione di sistema (predefinita)"</item>
<item msgid="5618929009984956469">"16 bit/campione"</item>
<item msgid="3412640499234627248">"24 bit/campione"</item>
<item msgid="121583001492929387">"32 bit/campione"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Valore predefinito"</item>
+ <item msgid="1081159789834584363">"Usa selezione di sistema (predefinita)"</item>
<item msgid="4726688794884191540">"16 bit/campione"</item>
<item msgid="305344756485516870">"24 bit/campione"</item>
<item msgid="244568657919675099">"32 bit/campione"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Valore predefinito"</item>
+ <item msgid="5226878858503393706">"Usa selezione di sistema (predefinita)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Valore predefinito"</item>
+ <item msgid="4118561796005528173">"Usa selezione di sistema (predefinita)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Qualità audio preferita (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Standard (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Collegamento preferito (330 kbps/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Ottimizza per qualità audio (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Audio bilanciato e qualità di connessione (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Ottimizza per qualità di connessione (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Qualità audio preferita (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Collegamento preferito (330 kbps/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Ottimizza per qualità audio"</item>
+ <item msgid="4327143584633311908">"Audio bilanciato e qualità di connessione"</item>
+ <item msgid="6155648878105378550">"Ottimizza per qualità di connessione"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Off"</item>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index a33fa87..7e3fd48 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Dati cellulare sempre attivi"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Disattiva volume assoluto"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Seleziona codec A2DP Bluetooth preferito"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Seleziona il codec audio Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Frequenza di campionamento audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Seleziona frequenza di campionamento preferita codec A2DP Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Seleziona il codec audio Bluetooth:\nFrequenza di campionamento"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bit per campione dell\'audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Seleziona bit per campione preferiti codec A2DP Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Seleziona il codec audio Bluetooth:\nBit per campione"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Modalità canale audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Seleziona modalità canale preferita codec A2DP Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Qualità di riproduzione LDAC audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Seleziona qualità di riproduzione preferita LDAC codec A2DP Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Seleziona il codec audio Bluetooth:\nModalità canale"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC audio Bluetooth: qualità di riproduzione"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Seleziona il codec LDAC audio Bluetooth:\nQualità di riproduzione"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostra opzioni per la certificazione display wireless"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumenta il livello di registrazione Wi-Fi, mostrando il SSID RSSI nel selettore Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Quando questa impostazione è attivata, il Wi-Fi sarà più aggressivo nel passare la connessione dati al cellulare, quando il segnale Wi-Fi è basso"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Guida e feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Inserisci la password per eseguire il ripristino dei dati di fabbrica in modalità demo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Avanti"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Password obbligatoria"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-iw/arrays.xml b/packages/SettingsLib/res/values-iw/arrays.xml
index f1e7be9..976eb71 100644
--- a/packages/SettingsLib/res/values-iw/arrays.xml
+++ b/packages/SettingsLib/res/values-iw/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"תמיד השתמש בבדיקת HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ברירת מחדל"</item>
+ <item msgid="7065842274271279580">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ברירת מחדל"</item>
+ <item msgid="5062108632402595000">"השתמש בבחירת המערכת (ברירת המחדל)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ברירת מחדל"</item>
+ <item msgid="3093023430402746802">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
<item msgid="8895532488906185219">"44.1 קילו-הרץ"</item>
<item msgid="2909915718994807056">"48.0 קילו-הרץ"</item>
<item msgid="3347287377354164611">"88.2 קילו-הרץ"</item>
<item msgid="1234212100239985373">"96.0 קילו-הרץ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ברירת מחדל"</item>
+ <item msgid="3214516120190965356">"השתמש בבחירת המערכת (ברירת המחדל)"</item>
<item msgid="4482862757811638365">"44.1 קילו-הרץ"</item>
<item msgid="354495328188724404">"48.0 קילו-הרץ"</item>
<item msgid="7329816882213695083">"88.2 קילו-הרץ"</item>
<item msgid="6967397666254430476">"96.0 קילו-הרץ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ברירת מחדל"</item>
+ <item msgid="2684127272582591429">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
<item msgid="5618929009984956469">"16 סיביות לדגימה"</item>
<item msgid="3412640499234627248">"24 סיביות לדגימה"</item>
<item msgid="121583001492929387">"32 סיביות לדגימה"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ברירת מחדל"</item>
+ <item msgid="1081159789834584363">"השתמש בבחירת המערכת (ברירת המחדל)"</item>
<item msgid="4726688794884191540">"16 סיביות לדגימה"</item>
<item msgid="305344756485516870">"24 סיביות לדגימה"</item>
<item msgid="244568657919675099">"32 סיביות לדגימה"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ברירת מחדל"</item>
+ <item msgid="5226878858503393706">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
<item msgid="4106832974775067314">"מונו"</item>
<item msgid="5571632958424639155">"סטריאו"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ברירת מחדל"</item>
+ <item msgid="4118561796005528173">"השתמש בבחירת המערכת (ברירת המחדל)"</item>
<item msgid="8900559293912978337">"מונו"</item>
<item msgid="8883739882299884241">"סטריאו"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"איכות צליל מועדפת (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"רגילה (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"חיבור מועדף (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"אופטימיזציה להשגת איכות אודיו מרבית (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"איזון בין איכות החיבור לאיכות אודיו (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"אופטימיזציה להשגת איכות חיבור מרבית (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"איכות צליל מועדפת (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"רגילה (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"חיבור מועדף (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"בצע אופטימיזציה לאיכות האודיו"</item>
+ <item msgid="4327143584633311908">"אזן בין איכות החיבור לאיכות אודיו"</item>
+ <item msgid="6155648878105378550">"בצע אופטימיזציה להשגת איכות חיבור מרבית"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"כבוי"</item>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 45dd7e7..3e1bfdb 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"נתונים סלולריים פעילים תמיד"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"השבת עוצמת קול מוחלטת"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec אודיו ל-Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"בחר codec A2DP מועדף ל-Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"בחירת Codec אודיו ל-Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"קצב דגימה של אודיו ל-Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"בחר קצב דגימה מועדף ב-codec A2DP ל-Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"בחירת Codec אודיו ל-Bluetooth:\nקצב דגימה"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"מספר סיביות לדגימה באודיו ל-Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"בחר מספר מועדף של סיביות לדגימה ב-codec A2DP ל-Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"בחירת codec אודיו ל-Bluetooth:\nסיביות לדגימה"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"מצב של ערוץ אודיו ל-Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"בחר מצב ערוץ מועדף ב-codec A2DP ל-Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"איכות נגינה של אודיו LDAC ל-Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"בחר איכות נגינה מועדפת ב-codec A2DP ל-Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"בחירת codec אודיו ל-Bluetooth:\nמצב ערוץ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec אודיו LDAC ל-Bluetooth: איכות נגינה"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"בחירת Codec אודיו LDAC ל-Bluetooth:\nאיכות נגינה"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"סטרימינג: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"הצג אפשרויות עבור אישור של תצוגת WiFi"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"העלה את רמת הרישום של Wi‑Fi ביומן, הצג לכל SSID RSSI ב-Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"כשתכונה זו מופעלת, Wi-Fi יתנהג בצורה אגרסיבית יותר בעת העברת חיבור הנתונים לרשת הסלולרית כשאות ה-Wi-Fi חלש."</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"עזרה ומשוב"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"תפריט"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"הזן סיסמה כדי לבצע איפוס להגדרות היצרן במצב הדגמה"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"הבא"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"דרושה סיסמה"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ja/arrays.xml b/packages/SettingsLib/res/values-ja/arrays.xml
index cc00934..c8599d2 100644
--- a/packages/SettingsLib/res/values-ja/arrays.xml
+++ b/packages/SettingsLib/res/values-ja/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"DRMコンテンツにのみHDCPチェックを使用する"</item>
<item msgid="45075631231212732">"HDCPチェックを常に使用する"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"デフォルト"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"デフォルト"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"デフォルト"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"デフォルト"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"デフォルト"</item>
- <item msgid="5618929009984956469">"16 ビット / サンプル"</item>
- <item msgid="3412640499234627248">"24 ビット / サンプル"</item>
- <item msgid="121583001492929387">"32 ビット / サンプル"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"デフォルト"</item>
- <item msgid="4726688794884191540">"16 ビット / サンプル"</item>
- <item msgid="305344756485516870">"24 ビット / サンプル"</item>
- <item msgid="244568657919675099">"32 ビット / サンプル"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"デフォルト"</item>
- <item msgid="4106832974775067314">"モノラル"</item>
- <item msgid="5571632958424639155">"ステレオ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"デフォルト"</item>
- <item msgid="8900559293912978337">"モノラル"</item>
- <item msgid="8883739882299884241">"ステレオ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"優先する音質(990 kbps / 909 kbps)"</item>
- <item msgid="138837449700903545">"標準(660 kbps / 606 kbps)"</item>
- <item msgid="4777177307869441982">"優先する接続(330 kbps / 303 kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"優先する音質(990 kbps / 909 kbps)"</item>
- <item msgid="9091111147684472529">"標準(660 kbps / 606 kbps)"</item>
- <item msgid="3367904477834831032">"優先する接続(330 kbps / 303 kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"OFF"</item>
<item msgid="1593289376502312923">"64 K"</item>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 12a8734..a7d607f 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"モバイルデータを常にON"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"絶対音量を無効にする"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth オーディオ コーデック"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"優先する Bluetooth A2DP コーデックを選択"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth オーディオ サンプルレート"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"優先する Bluetooth A2DP コーデック サンプルレートを選択"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"サンプルあたりの Bluetooth オーディオ ビット"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"優先するサンプルあたりの Bluetooth A2DP コーデック ビットを選択"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth オーディオ チャンネル モード"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"優先する Bluetooth A2DP コーデック チャンネル モードを選択"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth オーディオ LDAC 再生音質"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"優先する Bluetooth A2DP コーデック LDAC 再生音質を選択"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ワイヤレスディスプレイ認証のオプションを表示"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fiログレベルを上げて、Wi-Fi選択ツールでSSID RSSIごとに表示します"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"有効にすると、Wi-Fiの電波強度が弱い場合は強制的にモバイルデータ接続に切り替わるようになります"</string>
@@ -354,4 +362,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"ヘルプとフィードバック"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"メニュー"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"デモモードで初期状態にリセットするには、パスワードを入力してください"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"次へ"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"パスワード必須"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ka/arrays.xml b/packages/SettingsLib/res/values-ka/arrays.xml
index 0e6eb7c..ad04e15 100644
--- a/packages/SettingsLib/res/values-ka/arrays.xml
+++ b/packages/SettingsLib/res/values-ka/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"ყოველთვის გამოიყენე HDCP შემოწმება"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ნაგულისხმევი"</item>
+ <item msgid="7065842274271279580">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ნაგულისხმევი"</item>
+ <item msgid="5062108632402595000">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ნაგულისხმევი"</item>
+ <item msgid="3093023430402746802">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
<item msgid="8895532488906185219">"44,1 კჰც"</item>
<item msgid="2909915718994807056">"48,0 კჰც"</item>
<item msgid="3347287377354164611">"88,2 კჰც"</item>
<item msgid="1234212100239985373">"96,0 კჰც"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ნაგულისხმევი"</item>
+ <item msgid="3214516120190965356">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
<item msgid="4482862757811638365">"44,1 კჰც"</item>
<item msgid="354495328188724404">"48,0 კჰც"</item>
<item msgid="7329816882213695083">"88,2 კჰც"</item>
<item msgid="6967397666254430476">"96,0 კჰც"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ნაგულისხმევი"</item>
+ <item msgid="2684127272582591429">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
<item msgid="5618929009984956469">"16 ბიტი/ნიმუში"</item>
<item msgid="3412640499234627248">"24 ბიტი/ნიმუში"</item>
<item msgid="121583001492929387">"32 ბიტი/ნიმუში"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ნაგულისხმევი"</item>
+ <item msgid="1081159789834584363">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
<item msgid="4726688794884191540">"16 ბიტი/ნიმუში"</item>
<item msgid="305344756485516870">"24 ბიტი/ნიმუში"</item>
<item msgid="244568657919675099">"32 ბიტი/ნიმუში"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ნაგულისხმევი"</item>
+ <item msgid="5226878858503393706">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
<item msgid="4106832974775067314">"მონო"</item>
<item msgid="5571632958424639155">"სტერეო"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ნაგულისხმევი"</item>
+ <item msgid="4118561796005528173">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
<item msgid="8900559293912978337">"მონო"</item>
<item msgid="8883739882299884241">"სტერეო"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"სასურველია ხმის ხარისხი (990/909 კბიტი/წმ)"</item>
- <item msgid="138837449700903545">"სტანდარტული (660/606 კბიტი/წმ)"</item>
- <item msgid="4777177307869441982">"სასურველია კავშირი (330/303 კბიტი/წმ)"</item>
+ <item msgid="3411577996960199959">"აუდიოს ხარისხის ოპტიმიზაცია (990/909 კბიტი/წმ)"</item>
+ <item msgid="2921767058740704969">"აუდიოსა და კავშირის დაბალანსებული ხარისხი (660/606 კბიტი/წმ)"</item>
+ <item msgid="3682554248829489641">"კავშირის ხარისხის ოპტიმიზაცია (330/303 კბიტი/წმ)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"სასურველია ხმის ხარისხი (990/909 კბიტი/წმ)"</item>
- <item msgid="9091111147684472529">"სტანდარტული (660/606 კბიტი/წმ)"</item>
- <item msgid="3367904477834831032">"სასურველია კავშირი (330/303 კბიტი/წმ)"</item>
+ <item msgid="7668834469173465015">"აუდიოს ხარისხის ოპტიმიზაცია"</item>
+ <item msgid="4327143584633311908">"აუდიოსა და კავშირის დაბალანსებული ხარისხი"</item>
+ <item msgid="6155648878105378550">"კავშირის ხარისხის ოპტიმიზაცია"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"გამორთული"</item>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 18c2def..6838fbc 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"ფიჭური მონაცემები ყოველთვის აქტიურია"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"ხმის აბსოლუტური სიძლიერის გათიშვა"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth აუდიოს კოდეკი"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"აირჩიეთ Bluetooth A2DP-ის სასურველი კოდეკი"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"აირჩიეთ Bluetooth აუდიოს კოდეკი"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth აუდიოს დისკრეტიზაციის სიხშირე"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"აირჩიეთ Bluetooth A2DP კოდეკის დისკრეტიზაციის სასურველი სიხშირე"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"აირჩიეთ Bluetooth აუდიოს კოდეკის\nდისკრეტიზაციის სიხშირე"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth აუდიოს ბიტების რაოდენობა ნიმუშზე"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"აირჩიეთ Bluetooth A2DP კოდეკის ბიტების სასურველი რაოდენობა ნიმუშზე"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"აირჩიეთ Bluetooth აუდიოს კოდეკის\nბიტების რაოდენობა ნიმუშზე"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth აუდიოს არხის რეჟიმი"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"აირჩიეთ Bluetooth A2DP კოდეკის არხის სასურველი რეჟიმი"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth აუდიოს LDAC დაკვრის ხარისხი"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"აირჩიეთ Bluetooth A2DP კოდეკის LDAC დაკვრის სასურველი ხარისხი"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"აირჩიეთ Bluetooth აუდიოს კოდეკის\nარხის რეჟიმი"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth აუდიოს LDAC კოდეკის დაკვრის ხარისხი"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"აირჩიეთ Bluetooth აუდიოს LDAC კოდეკის\nდაკვრის ხარისხი"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"სტრიმინგი: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"უსადენო ეკრანის სერტიფიცირების ვარიანტების ჩვენება"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi-ს აღრიცხვის დონის გაზრდა, Wi‑Fi ამომრჩეველში ყოველ SSID RSSI-ზე ჩვენება"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"თუ ჩართულია, Wi‑Fi სიგნალის შესუსტების შემთხვევაში Wi-Fi უფრო აქტიურად შეეცდება გადაიყვანოს ინტერნეტ-კავშირი მობილურ ინტერნეტზე"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"დახმარება და გამოხმაურება"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"მენიუ"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"შეიყვანეთ პაროლი დემო-რეჟიმში ქარხნულ მდგომარეობაზე დასაბრუნებლად"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"შემდეგი"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"საჭიროა პაროლი"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-kk/arrays.xml b/packages/SettingsLib/res/values-kk/arrays.xml
index 4c1c819..056f17b 100644
--- a/packages/SettingsLib/res/values-kk/arrays.xml
+++ b/packages/SettingsLib/res/values-kk/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Әрқашан HDCP (жоғары кең жолақты сандық мазмұн қорғаушы) тексерулерін қолданыңыз"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Әдепкі"</item>
+ <item msgid="7065842274271279580">"Жүйені таңдау (әдепкі)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Әдепкі"</item>
+ <item msgid="5062108632402595000">"Жүйені таңдау (әдепкі)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Әдепкі"</item>
+ <item msgid="3093023430402746802">"Жүйені таңдау (әдепкі)"</item>
<item msgid="8895532488906185219">"44,1 кГц"</item>
<item msgid="2909915718994807056">"48,0 кГц"</item>
<item msgid="3347287377354164611">"88,2 кГц"</item>
<item msgid="1234212100239985373">"96,0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Әдепкі"</item>
+ <item msgid="3214516120190965356">"Жүйені таңдау (әдепкі)"</item>
<item msgid="4482862757811638365">"44,1 кГц"</item>
<item msgid="354495328188724404">"48,0 кГц"</item>
<item msgid="7329816882213695083">"88,2 кГц"</item>
<item msgid="6967397666254430476">"96,0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Әдепкі"</item>
+ <item msgid="2684127272582591429">"Жүйені таңдау (әдепкі)"</item>
<item msgid="5618929009984956469">"16 бит/үлгі"</item>
<item msgid="3412640499234627248">"24 бит/үлгі"</item>
<item msgid="121583001492929387">"32 бит/үлгі"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Әдепкі"</item>
+ <item msgid="1081159789834584363">"Жүйені таңдау (әдепкі)"</item>
<item msgid="4726688794884191540">"16 бит/үлгі"</item>
<item msgid="305344756485516870">"24 бит/үлгі"</item>
<item msgid="244568657919675099">"32 бит/үлгі"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Әдепкі"</item>
+ <item msgid="5226878858503393706">"Жүйені таңдау (әдепкі)"</item>
<item msgid="4106832974775067314">"Моно"</item>
<item msgid="5571632958424639155">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Әдепкі"</item>
+ <item msgid="4118561796005528173">"Жүйені таңдау (әдепкі)"</item>
<item msgid="8900559293912978337">"Моно"</item>
<item msgid="8883739882299884241">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Таңдаулы дыбыс сапасы (990 кбит/с не 909 кбит/с)"</item>
- <item msgid="138837449700903545">"Стандартты (660 кбит/с не 606 кбит/с)"</item>
- <item msgid="4777177307869441982">"Таңдаулы байланыс (330 кбит/с не 303 кбит/с)"</item>
+ <item msgid="3411577996960199959">"Аудиомазмұн сапасы бойынша оңтайландыру (990 кб/сек не 909 кб/сек)"</item>
+ <item msgid="2921767058740704969">"Теңгерілген аудиомазмұн мен байланыс сапасы (660 кб/сек не 606 кб/сек)"</item>
+ <item msgid="3682554248829489641">"Байланыс сапасы бойынша оңтайландыру (330 кб/сек не 303 кб/сек)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Таңдаулы дыбыс сапасы (990 кбит/с не 909 кбит/с)"</item>
- <item msgid="9091111147684472529">"Стандартты (660 кбит/с не 606 кбит/с)"</item>
- <item msgid="3367904477834831032">"Таңдаулы байланыс (330 кбит/с не 303 кбит/с)"</item>
+ <item msgid="7668834469173465015">"Аудиомазмұн сапасы бойынша оңтайландыру"</item>
+ <item msgid="4327143584633311908">"Теңгерілген аудиомазмұн мен байланыс сапасы"</item>
+ <item msgid="6155648878105378550">"Байланыс сапасы бойынша оңтайландыру"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Өшірулі"</item>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 862d06f..9372dbd 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Ұялы деректер әрқашан белсенді"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Абсолютті дыбыс деңгейін өшіру"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth аудимазмұн кодегі"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Таңдаулы Bluetooth A2DP кодегін таңдау"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth аудиокодегін таңдау"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth аудиомазмұны бойынша үлгі жиілігі"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Таңдаулы Bluetooth A2DP кодегі бойынша үлгі жиілігі"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth аудиокодегін таңдау:\nдискреттеу жылдамдығы"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth аудиомазмұны бойынша әр үлгіге келетін биттер саны"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Таңдаулы Bluetooth A2DP кодегі бойынша әр үлгіге келетін биттер санын таңдау"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth аудиокодегін таңдау:\nбит/үлгі"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth аудиомазмұны бойынша арна режимі"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Таңдаулы Bluetooth A2DP кодегі бойынша арна режимі"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth аудиомазмұны бойынша LDAC ойнату сапасы"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Таңдаулы Bluetooth A2DP кодегі бойынша LDAC ойнату сапасын таңдау"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth аудиокодегін таңдау:\nарна режимі"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth LDAC аудиокодегі: ойнату сапасы"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth LDAC аудиокодегін таңдау:\nойнату сапасы"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Трансляция: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Сымсыз дисплей растау опцияларын көрсету"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi жур. тір. дең. арт., Wi‑Fi желісін таңдағышта әр SSID RSSI бойынша көрсету"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Wi‑Fi сигналы әлсіз болғанда, деректер байланысы мәжбүрлі түрде ұялы желіге ауысады"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Анықтама және пікір"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Mәзір"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Демо режимде зауыттық мәндерге қайтару үшін құпия сөзді енгізу"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Келесі"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Құпия сөз қажет"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-km/arrays.xml b/packages/SettingsLib/res/values-km/arrays.xml
index 569603f..470112a 100644
--- a/packages/SettingsLib/res/values-km/arrays.xml
+++ b/packages/SettingsLib/res/values-km/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"ប្រើការពិនិត្យ HDCP សម្រាប់តែមាតិកា DRM ប៉ុណ្ណោះ"</item>
<item msgid="45075631231212732">"ប្រើការពិនិត្យ HDCP ជានិច្ច"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"លំនាំដើម"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"លំនាំដើម"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"លំនាំដើម"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"លំនាំដើម"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"លំនាំដើម"</item>
- <item msgid="5618929009984956469">"16 ប៊ីត/គំរូ"</item>
- <item msgid="3412640499234627248">"24 ប៊ីត/គំរូ"</item>
- <item msgid="121583001492929387">"32 ប៊ីត/គំរូ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"លំនាំដើម"</item>
- <item msgid="4726688794884191540">"16 ប៊ីត/គំរូ"</item>
- <item msgid="305344756485516870">"24 ប៊ីត/គំរូ"</item>
- <item msgid="244568657919675099">"32 ប៊ីត/គំរូ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"លំនាំដើម"</item>
- <item msgid="4106832974775067314">"ម៉ូណូ"</item>
- <item msgid="5571632958424639155">"ស្តេរ៉េអូ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"លំនាំដើម"</item>
- <item msgid="8900559293912978337">"ម៉ូណូ"</item>
- <item msgid="8883739882299884241">"ស្តេរ៉េអូ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"គុណភាពសំឡេងដែលគួរប្រើ (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"ស្តង់ដា (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"ការតភ្ជាប់ដែលគួរប្រើ (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"គុណភាពសំឡេងដែលគួរប្រើ (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"ស្តង់ដា (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"ការតភ្ជាប់ដែលគួរប្រើ (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"បិទ"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index b7bddd8..0b1ba5e 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"ទិន្នន័យចល័តសកម្មជានិច្ច"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"បិទកម្រិតសំឡេងលឺខ្លាំង"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"កូឌិកសំឡេងប៊្លូធូស"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"ជ្រើសរើសកូឌិកសម្រាប់ប៊្លូធូស A2DP ដែលគួរប្រើ"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"អត្រាគំរូសំឡេងប៊្លូធូស"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"ជ្រើសរើសអត្រាគំរូកូឌិកសម្រាប់ប៊្លូធូស A2DP ដែលគួរប្រើ"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"កម្រិតប៊ីតក្នុងមួយគំរូនៃសំឡេងប៊្លូធូស"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"ជ្រើសរើសកម្រិតប៊ីតក្នុងមួយគំរូនៃកូឌិកប៊្លូធូស A2DP ដែលគួរប្រើ"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"មុខងាររលកសញ្ញាសំឡេងប៊្លូធូស"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"ជ្រើសរើសមុខងាររលកសញ្ញាកូឌិកប៊្លូធូស A2DP ដែលគួរប្រើ"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"គុណភាពចាក់សម្រាប់សំឡេងប៊្លូធូស LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"ជ្រើសរើសគុណភាពចាក់ LDAC កូឌិក សម្រាប់ប៊្លូធូស A2DP ដែលគួរប្រើ"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"បង្ហាញជម្រើសសម្រាប់វិញ្ញាបនបត្របង្ហាញឥតខ្សែ"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"បង្កើនកម្រិតកំណត់ហេតុវ៉ាយហ្វាយបង្ហាញក្នុង SSID RSSI ក្នុងកម្មវិធីជ្រើសវ៉ាយហ្វាយ"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"ពេលបានបើក វ៉ាយហ្វាយនឹងកាន់តែបង្ខំក្នុងការបញ្ជូនការភ្ជាប់ទិន្នន័យទៅបណ្ដាញចល័ត នៅពេលសញ្ញាវ៉ាយហ្វាយយឺត"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"ជំនួយ និងមតិស្ថាបនា"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"ម៉ឺនុយ"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"បញ្ចូលពាក្យសម្ងាត់ ដើម្បីកំណត់ដូចចេញពីរោងចក្រឡើងវិញនៅក្នុងមុខងារសាកល្បង"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"បន្ទាប់"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"តម្រូវឲ្យមានពាក្យសម្ងាត់"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-kn/arrays.xml b/packages/SettingsLib/res/values-kn/arrays.xml
index ae3e97d..2cfe328 100644
--- a/packages/SettingsLib/res/values-kn/arrays.xml
+++ b/packages/SettingsLib/res/values-kn/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"HDCP ಪರಿಶೀಲನೆಯನ್ನು ಯಾವಾಗಲೂ ಬಳಸು"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ಡಿಫಾಲ್ಟ್"</item>
+ <item msgid="7065842274271279580">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ಡಿಫಾಲ್ಟ್"</item>
+ <item msgid="5062108632402595000">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ಡಿಫಾಲ್ಟ್"</item>
+ <item msgid="3093023430402746802">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ಡಿಫಾಲ್ಟ್"</item>
+ <item msgid="3214516120190965356">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ಡಿಫಾಲ್ಟ್"</item>
+ <item msgid="2684127272582591429">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item>
<item msgid="5618929009984956469">"16 ಬಿಟ್ಸ್/ಮಾದರಿ"</item>
<item msgid="3412640499234627248">"24 ಬಿಟ್ಸ್/ಮಾದರಿ"</item>
<item msgid="121583001492929387">"32 ಬಿಟ್ಸ್/ಮಾದರಿ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ಡಿಫಾಲ್ಟ್"</item>
+ <item msgid="1081159789834584363">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item>
<item msgid="4726688794884191540">"16 ಬಿಟ್ಸ್/ಮಾದರಿ"</item>
<item msgid="305344756485516870">"24 ಬಿಟ್ಸ್/ಮಾದರಿ"</item>
<item msgid="244568657919675099">"32 ಬಿಟ್ಸ್/ಮಾದರಿ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ಡಿಫಾಲ್ಟ್"</item>
+ <item msgid="5226878858503393706">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item>
<item msgid="4106832974775067314">"ಮೊನೊ"</item>
<item msgid="5571632958424639155">"ಸ್ಟೀರಿಯೊ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ಡಿಫಾಲ್ಟ್"</item>
+ <item msgid="4118561796005528173">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item>
<item msgid="8900559293912978337">"ಮೊನೊ"</item>
<item msgid="8883739882299884241">"ಸ್ಟೀರಿಯೊ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"ಧ್ವನಿಯ ಗುಣಮಟ್ಟಕ್ಕೆ ಆದ್ಯತೆ ನೀಡಲಾಗಿದೆ (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"ಪ್ರಮಾಣಿತ (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"ಸಂಪರ್ಕಕ್ಕೆ ಆದ್ಯತೆ ನೀಡಲಾಗಿದೆ (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"ಆಡಿಯೊ ಗುಣಮಟ್ಟಕ್ಕಾಗಿ (990kbps/909kbps) ಆಪ್ಟಿಮೈಸ್ ಮಾಡಿ"</item>
+ <item msgid="2921767058740704969">"ಸಂತುಲಿತ ಆಡಿಯೊ ಮತ್ತು ಸಂಪರ್ಕದ ಗುಣಮಟ್ಟ (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"ಸಂಪರ್ಕದ ಗುಣಮಟ್ಟಕ್ಕಾಗಿ (330kbps/303kbps) ಆಪ್ಟಿಮೈಸ್ ಮಾಡಿ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"ಧ್ವನಿಯ ಗುಣಮಟ್ಟಕ್ಕೆ ಆದ್ಯತೆ ನೀಡಲಾಗಿದೆ (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"ಪ್ರಮಾಣಿತ (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"ಸಂಪರ್ಕಕ್ಕೆ ಆದ್ಯತೆ ನೀಡಲಾಗಿದೆ (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"ಆಡಿಯೊ ಗುಣಮಟ್ಟಕ್ಕಾಗಿ ಆಪ್ಟಿಮೈಸ್ ಮಾಡಿ"</item>
+ <item msgid="4327143584633311908">"ಸಂತುಲಿತ ಆಡಿಯೊ ಮತ್ತು ಸಂಪರ್ಕದ ಗುಣಮಟ್ಟ"</item>
+ <item msgid="6155648878105378550">"ಸಂಪರ್ಕದ ಗುಣಮಟ್ಟಕ್ಕಾಗಿ ಆಪ್ಟಿಮೈಸ್ ಮಾಡಿ"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ಆಫ್"</item>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 5ac99d3..99577f7 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"ಸೆಲ್ಯುಲರ್ ಡೇಟಾ ಯಾವಾಗಲೂ ಸಕ್ರಿಯ"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"ಸಂಪೂರ್ಣ ವಾಲ್ಯೂಮ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೋ ಕೋಡೆಕ್"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"ಪ್ರಾಶಸ್ತ್ಯದ ಬ್ಲೂಟೂತ್ A2DP ಕೋಡೆಕ್ ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth ಆಡಿಯೊ ಕೋಡೆಕ್ ಆಯ್ಕೆ ಮಾಡಿ"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೋ ಮಾದರಿ ದರ"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"ಪ್ರಾಶಸ್ತ್ಯದ ಬ್ಲೂಟೂಥ್ A2DP ಕೋಡೆಕ್ ಮಾದರಿ ದರವನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth ಆಡಿಯೋ ಕೋಡೆಕ್:\nಮಾದರಿ ದರ ಆಯ್ಕೆ ಮಾಡಿ"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೊ ಬಿಟ್ಸ್ನ ಪ್ರತಿ ಮಾದರಿ"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"ಪ್ರಾಶಸ್ತ್ಯದ ಬ್ಲೂಟೂತ್ A2DP ಕೋಡೆಕ್ ಬಿಟ್ಸ್ನ ಪ್ರತಿ ಮಾದರಿಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth ಆಡಿಯೋ ಕೋಡೆಕ್:\nಬಿಟ್ಸ್ ಪ್ರತಿ ಮಾದರಿ ಆಯ್ಕೆ ಮಾಡಿ"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೋ ಚಾನೆಲ್ ಮೋಡ್"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"ಪ್ರಾಶಸ್ತ್ಯದ ಬ್ಲೂಟೂತ್ A2DP ಕೋಡೆಕ್ ಚಾನಲ್ ಮೋಡ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೊ LDAC ಪ್ಲೇಬ್ಯಾಕ್ ಗುಣಮಟ್ಟ"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"ಪ್ರಾಶಸ್ತ್ಯದ ಬ್ಲೂಟೂತ್ A2DP ಕೋಡೆಕ್ LDAC ಪ್ಲೇಬ್ಯಾಕ್ ಗುಣಮಟ್ಟವನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth ಆಡಿಯೋ ಕೋಡೆಕ್:\nಚಾನೆಲ್ ಮೋಡ್ ಆಯ್ಕೆ ಮಾಡಿ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth ಆಡಿಯೊ LDAC ಕೋಡೆಕ್: ಪ್ಲೇಬ್ಯಾಕ್ ಗುಣಮಟ್ಟ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth ಆಡಿಯೊ LDAC ಕೋಡೆಕ್:\nಪ್ಲೇಬ್ಯಾಕ್ ಗುಣಮಟ್ಟ ಆಯ್ಕೆ ಮಾಡಿ"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ಸ್ಟ್ರೀಮಿಂಗ್: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ವೈರ್ಲೆಸ್ ಪ್ರದರ್ಶನ ಪ್ರಮಾಣೀಕರಣಕ್ಕಾಗಿ ಆಯ್ಕೆಗಳನ್ನು ತೋರಿಸು"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ಲಾಗಿಂಗ್ ಮಟ್ಟನ್ನು ಹೆಚ್ಚಿಸಿ, Wi‑Fi ಆಯ್ಕೆಯಲ್ಲಿ ಪ್ರತಿಯೊಂದು SSID RSSI ತೋರಿಸಿ"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"ಸಕ್ರಿಯಗೊಂಡರೆ, Wi‑Fi ಸಿಗ್ನಲ್ ದುರ್ಬಲವಾಗಿದ್ದರೂ ಕೂಡ, ಸೆಲ್ಯುಲರ್ಗೆ ಡೇಟಾ ಸಂಪರ್ಕವನ್ನು ಹಸ್ತಾಂತರಿಸುವಲ್ಲಿ Wi‑Fi ಹೆಚ್ಚು ಆಕ್ರಮಣಕಾರಿಯಾಗಿರುತ್ತದೆ"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"ಸಹಾಯ ಮತ್ತು ಪ್ರತಿಕ್ರಿಯೆ"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"ಮೆನು"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"ಫ್ಯಾಕ್ಟರಿ ರಿಸೆಟ್ಗೆ ಪಾಸ್ವರ್ಡ್ ನಮೂದಿಸಿ"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"ಮುಂದೆ"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"ಪಾಸ್ವರ್ಡ್ ಅಗತ್ಯವಿದೆ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml
index f709e88..3c8424a 100644
--- a/packages/SettingsLib/res/values-ko/arrays.xml
+++ b/packages/SettingsLib/res/values-ko/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"DRM 콘텐츠에 대해서만 HDCP 확인 사용"</item>
<item msgid="45075631231212732">"항상 HDCP 확인 사용"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"기본값"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"기본값"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"기본값"</item>
- <item msgid="8895532488906185219">"44.1kHz"</item>
- <item msgid="2909915718994807056">"48.0kHz"</item>
- <item msgid="3347287377354164611">"88.2kHz"</item>
- <item msgid="1234212100239985373">"96.0kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"기본값"</item>
- <item msgid="4482862757811638365">"44.1kHz"</item>
- <item msgid="354495328188724404">"48.0kHz"</item>
- <item msgid="7329816882213695083">"88.2kHz"</item>
- <item msgid="6967397666254430476">"96.0kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"기본값"</item>
- <item msgid="5618929009984956469">"16비트/샘플"</item>
- <item msgid="3412640499234627248">"24비트/샘플"</item>
- <item msgid="121583001492929387">"32비트/샘플"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"기본값"</item>
- <item msgid="4726688794884191540">"16비트/샘플"</item>
- <item msgid="305344756485516870">"24비트/샘플"</item>
- <item msgid="244568657919675099">"32비트/샘플"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"기본값"</item>
- <item msgid="4106832974775067314">"모노"</item>
- <item msgid="5571632958424639155">"스테레오"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"기본값"</item>
- <item msgid="8900559293912978337">"모노"</item>
- <item msgid="8883739882299884241">"스테레오"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"음질 우선(990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"표준(660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"연결 우선(330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"음질 우선(990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"표준(660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"연결 우선(330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"사용 안함"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index a21fb25..3606497 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"모바일 데이터 항상 활성화"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"절대 볼륨 사용 안함"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"블루투스 오디오 코덱"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"기본 블루투스 A2DP 코덱 선택"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"블루투스 오디오 샘플링 비율"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"기본 블루투스 A2DP 코덱 샘플링 비율 선택"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"블루투스 오디오 샘플당 비트"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"기본 블루투스 A2DP 코덱 샘플당 비트 선택"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"블루투스 오디오 채널 모드"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"기본 블루투스 A2DP 코덱 채널 모드 선택"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"블루투스 오디오 LDAC 재생 품질"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"기본 블루투스 A2DP 코덱 LDAC 재생 품질 선택"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"무선 디스플레이 인증서 옵션 표시"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi 로깅 수준을 높이고, Wi‑Fi 선택도구에서 SSID RSSI당 값을 표시합니다."</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"사용 설정하면 Wi-Fi 신호가 약할 때 데이터 연결을 Wi-Fi에서 데이터 네트워크로 더욱 적극적으로 핸드오버합니다."</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"고객센터"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"메뉴"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"데모 모드에서 초기화하려면 비밀번호 입력"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"다음"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"비밀번호 입력 필요"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml
index 84d9002..4cdee97 100644
--- a/packages/SettingsLib/res/values-ky/arrays.xml
+++ b/packages/SettingsLib/res/values-ky/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Ар дайым HDCP текшерүү колдонулсун"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Демейки"</item>
+ <item msgid="7065842274271279580">"Тутум тандаганды колдонуу (демейки)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Демейки"</item>
+ <item msgid="5062108632402595000">"Тутум тандаганды колдонуу (демейки)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Демейки"</item>
+ <item msgid="3093023430402746802">"Тутум тандаганды колдонуу (демейки)"</item>
<item msgid="8895532488906185219">"44,1 кГц"</item>
<item msgid="2909915718994807056">"48,0 кГц"</item>
<item msgid="3347287377354164611">"88,2 кГц"</item>
<item msgid="1234212100239985373">"96,0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Демейки"</item>
+ <item msgid="3214516120190965356">"Тутум тандаганды колдонуу (демейки)"</item>
<item msgid="4482862757811638365">"44,1 кГц"</item>
<item msgid="354495328188724404">"48,0 кГц"</item>
<item msgid="7329816882213695083">"88,2 кГц"</item>
<item msgid="6967397666254430476">"96,0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Демейки"</item>
+ <item msgid="2684127272582591429">"Тутум тандаганды колдонуу (демейки)"</item>
<item msgid="5618929009984956469">"16 бит/үлгү"</item>
<item msgid="3412640499234627248">"24 бит/үлгү"</item>
<item msgid="121583001492929387">"32 бит/үлгү"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Демейки"</item>
+ <item msgid="1081159789834584363">"Тутум тандаганды колдонуу (демейки)"</item>
<item msgid="4726688794884191540">"16 бит/үлгү"</item>
<item msgid="305344756485516870">"24 бит/үлгү"</item>
<item msgid="244568657919675099">"32 бит/үлгү"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Демейки"</item>
+ <item msgid="5226878858503393706">"Тутум тандаганды колдонуу (демейки)"</item>
<item msgid="4106832974775067314">"Моно"</item>
<item msgid="5571632958424639155">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Демейки"</item>
+ <item msgid="4118561796005528173">"Тутум тандаганды колдонуу (демейки)"</item>
<item msgid="8900559293912978337">"Моно"</item>
<item msgid="8883739882299884241">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Тандалган добуш сапаты (990Кб/сек./909Кб/сек.)"</item>
- <item msgid="138837449700903545">"Стандарт (660Кб/сек./606Кб/сек.)"</item>
- <item msgid="4777177307869441982">"Тандалган туташуу (330Кб/сек./303Кб/сек.)"</item>
+ <item msgid="3411577996960199959">"Аудио сапаты үчүн оптималдаштыруу (990кб/сек./909кб/сек.)"</item>
+ <item msgid="2921767058740704969">"Теңделген аудио жана туташуу сапаты (660кб/сек./606кб/сек.)"</item>
+ <item msgid="3682554248829489641">"Туташуу сапаты үчүн оптималдаштыруу (330кб/сек./303кб/сек.)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Тандалган добуш сапаты (990Кб/сек./909Кб/сек.)"</item>
- <item msgid="9091111147684472529">"Стандарт (660Кб/сек./606Кб/сек.)"</item>
- <item msgid="3367904477834831032">"Тандалган туташуу (330Кб/сек./303Кб/сек.)"</item>
+ <item msgid="7668834469173465015">"Аудио сапаты үчүн оптималдаштыруу"</item>
+ <item msgid="4327143584633311908">"Теңделген аудио жана туташуу сапаты"</item>
+ <item msgid="6155648878105378550">"Туташуу сапаты үчүн оптималдаштыруу"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Өчүк"</item>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index d1320da..6608ae5 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Уюлдук дайындар ар дайым активдүү"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Үндүн абсолюттук деңгээли өчүрүлсүн"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth аудио кодек"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Керектүү Bluetooth A2DP кодекти тандаңыз"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth аудио кодегин тандаңыз"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth аудио үлгүсүнүн ылдамдыгы"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Bluetooth A2DP кодек үлгүсүнүн керектүү ылдамдыгын тандаңыз"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth аудио кодегин тандаңыз:\nҮлгү жыштыгы"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Бир үлгүдөгү Bluetooth аудио биттери"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Бир үлгү үчүн керектүү Bluetooth A2DP кодек биттерин тандаңыз"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth аудио кодегин тандаңыз:\nБир үлгүдөгү биттер"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth аудио каналынын режими"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Bluetooth A2DP кодек каналынын керектүү режимин тандаңыз"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth аудио LDAC ойнотуу сапаты"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Bluetooth A2DP кодек үчүн керектүү LDAC ойнотуу сапатын тандаңыз"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth аудио кодегин тандаңыз:\nКанал режими"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth аудио LDAC кодеги: Ойнотуу сапаты"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth аудио LDAC кодегин тандаңыз:\nОйнотуу сапаты"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Трансляция: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Зымсыз дисплейди сертификатто мүмкүнчүлүктөрүн көргөзүү"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi Кармагычта Wi‑Fi протокол деңгээлин жогорулатуу жана ар бир SSID RSSI үчүн көрсөтүү."</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Иштетилгенде, Wi-Fi байланышы үзүл-кесил болуп жатканда, Wi-Fi дайындарды уюктук операторго өжөрлүк менен өткөрөт."</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Жардам жана жооп пикир"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Меню"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Демо режиминде демейки жөндөөлөргө кайтаруу үчүн сырсөздү киргизиңиз"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Кийинки"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Сырсөз талап кылынат"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lo/arrays.xml b/packages/SettingsLib/res/values-lo/arrays.xml
index 45bb579..0967128 100644
--- a/packages/SettingsLib/res/values-lo/arrays.xml
+++ b/packages/SettingsLib/res/values-lo/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"ໃຊ້ການກວດສອບ HDCP ສະເໝີ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ຄ່າເລີ່ມຕົ້ນ"</item>
+ <item msgid="7065842274271279580">"Use System Selection (Default)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX-HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ຄ່າເລີ່ມຕົ້ນ"</item>
+ <item msgid="5062108632402595000">"Use System Selection (Default)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX-HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ຄ່າເລີ່ມຕົ້ນ"</item>
+ <item msgid="3093023430402746802">"Use System Selection (Default)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ຄ່າເລີ່ມຕົ້ນ"</item>
+ <item msgid="3214516120190965356">"Use System Selection (Default)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ຄ່າເລີ່ມຕົ້ນ"</item>
+ <item msgid="2684127272582591429">"Use System Selection (Default)"</item>
<item msgid="5618929009984956469">"16 bits/sample"</item>
<item msgid="3412640499234627248">"24 bits/sample"</item>
<item msgid="121583001492929387">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ຄ່າເລີ່ມຕົ້ນ"</item>
+ <item msgid="1081159789834584363">"Use System Selection (Default)"</item>
<item msgid="4726688794884191540">"16 bits/sample"</item>
<item msgid="305344756485516870">"24 bits/sample"</item>
<item msgid="244568657919675099">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ຄ່າເລີ່ມຕົ້ນ"</item>
+ <item msgid="5226878858503393706">"Use System Selection (Default)"</item>
<item msgid="4106832974775067314">"ໂທນດຽວ"</item>
<item msgid="5571632958424639155">"ສະເຕຣິໂອ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ຄ່າເລີ່ມຕົ້ນ"</item>
+ <item msgid="4118561796005528173">"Use System Selection (Default)"</item>
<item msgid="8900559293912978337">"ໂທນດຽວ"</item>
<item msgid="8883739882299884241">"ສະເຕຣິໂອ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Sound quality preferred (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Standard (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Connection preferred (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"Optimize for Audio Quality (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"Balanced Audio And Connection Quality (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"Optimize for Connection Quality (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Sound quality preferred (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Connection preferred (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"Optimize for Audio Quality"</item>
+ <item msgid="4327143584633311908">"Balanced Audio And Connection Quality"</item>
+ <item msgid="6155648878105378550">"Optimize for Connection Quality"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ປິດ"</item>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 639769a..9c493f2 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"ຂໍ້ມູນມືຖືເປີດຢູ່ສະເໝີ"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"ປິດໃຊ້ລະດັບສຽງສົມບູນ"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Audio Codec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Select Preferred Bluetooth A2DP Codec"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Select Bluetooth Audio Codec"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth Audio Sample Rate"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Select Preferred Bluetooth A2DP Codec Sample Rate"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Select Bluetooth Audio Codec:\nSample Rate"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth Audio Bits Per Sample"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Select Preferred Bluetooth A2DP Codec Bits Per Sample"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Select Bluetooth Audio Codec:\nBits Per Sample"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth Audio Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Select Preferred Bluetooth A2DP Codec Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth Audio LDAC Playback Quality"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Select Preferred Bluetooth A2DP Codec LDAC Playback Quality"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Select Bluetooth Audio Codec:\nChannel Mode"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ສະແດງໂຕເລືອກສຳລັບການສະແດງການຮັບຮອງລະບົບໄຮ້ສາຍ"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"ເພີ່ມລະດັບການເກັບປະຫວັດ Wi‑Fi, ສະແດງຕໍ່ SSID RSSI ໃນ Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"ເມື່ອເປີດນຳໃຊ້ແລ້ວ, ເຄືອຂ່າຍ Wi-Fi ຈະຖືກປ່ຽນໄປໃຊ້ເຄືອຂ່າຍໂທລະສັບແທນຫາກສັນຍານ Wi-Fi ອ່ອນ"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"ຊ່ວຍເຫຼືອ & ຄຳຕິຊົມ"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"ເມນູ"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Enter password to perform factory reset in demo mode"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"ຕໍ່ໄປ"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"ຕ້ອງໃສ່ລະຫັດຜ່ານ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lt/arrays.xml b/packages/SettingsLib/res/values-lt/arrays.xml
index b61b6d8..29a4671 100644
--- a/packages/SettingsLib/res/values-lt/arrays.xml
+++ b/packages/SettingsLib/res/values-lt/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Visada naudoti HDCP tikrinimą"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Numatytasis"</item>
+ <item msgid="7065842274271279580">"Naudoti sistemos pasirink. (numatytasis)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Numatytasis"</item>
+ <item msgid="5062108632402595000">"Naudoti sistemos pasirink. (numatytasis)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Numatytasis"</item>
+ <item msgid="3093023430402746802">"Naudoti sistemos pasirink. (numatytasis)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Numatytasis"</item>
+ <item msgid="3214516120190965356">"Naudoti sistemos pasirink. (numatytasis)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Numatytasis"</item>
+ <item msgid="2684127272582591429">"Naudoti sistemos pasirink. (numatytasis)"</item>
<item msgid="5618929009984956469">"16 bitų pavyzdyje"</item>
<item msgid="3412640499234627248">"24 bitai pavyzdyje"</item>
<item msgid="121583001492929387">"32 bitai pavyzdyje"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Numatytasis"</item>
+ <item msgid="1081159789834584363">"Naudoti sistemos pasirink. (numatytasis)"</item>
<item msgid="4726688794884191540">"16 bitų pavyzdyje"</item>
<item msgid="305344756485516870">"24 bitai pavyzdyje"</item>
<item msgid="244568657919675099">"32 bitai pavyzdyje"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Numatytasis"</item>
+ <item msgid="5226878858503393706">"Naudoti sistemos pasirink. (numatytasis)"</item>
<item msgid="4106832974775067314">"Monofoninis garsas"</item>
<item msgid="5571632958424639155">"Stereofoninis garsas"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Numatytasis"</item>
+ <item msgid="4118561796005528173">"Naudoti sistemos pasirink. (numatytasis)"</item>
<item msgid="8900559293912978337">"Monofoninis garsas"</item>
<item msgid="8883739882299884241">"Stereofoninis garsas"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Pageid. garso kok. (990 KB/s / 909 KB/s)"</item>
- <item msgid="138837449700903545">"Įprasta (660 KB/s / 606 KB/s)"</item>
- <item msgid="4777177307869441982">"Pageidautin. ryšys (330 KB/s / 303 KB/s)"</item>
+ <item msgid="3411577996960199959">"Optimizuoti garso kokybę (990 kbps / 909 kbps)"</item>
+ <item msgid="2921767058740704969">"Subalansuotą garso ir ryšio kokybė (660 kbps / 606 kbps)"</item>
+ <item msgid="3682554248829489641">"Optimizuoti ryšio kokybę (330 kbps / 303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Pageid. garso kok. (990 KB/s / 909 KB/s)"</item>
- <item msgid="9091111147684472529">"Įprasta (660 KB/s / 606 KB/s)"</item>
- <item msgid="3367904477834831032">"Pageidautin. ryšys (330 KB/s / 303 KB/s)"</item>
+ <item msgid="7668834469173465015">"Optimizuoti garso kokybę"</item>
+ <item msgid="4327143584633311908">"Subalansuota garso ir ryšio kokybė"</item>
+ <item msgid="6155648878105378550">"Optimizuoti ryšio kokybę"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Išjungta"</item>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 4616b86..3090d30 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Korinio ryšio duomenys visada aktyvūs"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Išjungti didžiausią garsą"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"„Bluetooth“ garso kodekas"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Pasirinkite pageidaujamą „Bluetooth“ A2DP kodeką"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Pasirinkite „Bluetooth“ garso kodeką"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"„Bluetooth“ garso pavyzdžio dažnis"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Pasirinkite pageidaujamą „Bluetooth“ A2DP kodeko pavyzdžio dažnį"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Pasirinkite „Bluetooth“ garso kodekas:\nimties dydis"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"„Bluetooth“ garso įrašo bitų skaičius pavyzdyje"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Pasirinkite pageidaujamą „Bluetooth“ A2DP kodeko bitų skaičių pavyzdyje"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Pasirinkite „Bluetooth“ garso kodeką:\nbitų skaičius viename pavyzdyje"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"„Bluetooth“ garso kanalo režimas"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Pasirinkite pageidaujamą „Bluetooth“ A2DP kodeko kanalo režimą"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"„Bluetooth“ garso LDAC atkūrimo kokybė"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Pasirinkite pageidaujamą „Bluetooth“ A2DP kodeko LDAC atkūrimo kokybę"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Pasirinkite „Bluetooth“ garso kodeką:\n kanalo režimas"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"„Bluetooth“ garso LDAC kodekas: atkūrimo kokybė"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Pasirinkite „Bluetooth“ garso LDAC kodeką:\natkūrimo kokybė"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Srautinis perdavimas: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Rodyti belaidžio rodymo sertifikavimo parinktis"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Padidinti „Wi‑Fi“ įrašymo į žurnalą lygį, rodyti SSID RSSI „Wi-Fi“ rinkiklyje"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Jei įgalinta ši parinktis, „Wi‑Fi“ agresyviau perduos duomenų ryšį į mobiliojo ryšio tinklą, kai „Wi‑Fi“ signalas bus silpnas"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Pagalba ir atsiliepimai"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Meniu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Įv. slapt. ir atk. gam. nust. dem. rež."</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Kitas"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Būtina nurodyti slaptažodį"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lv/arrays.xml b/packages/SettingsLib/res/values-lv/arrays.xml
index 1d8a501..cf8b2c9 100644
--- a/packages/SettingsLib/res/values-lv/arrays.xml
+++ b/packages/SettingsLib/res/values-lv/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Izmantot HDCP pārbaudi tikai DRM saturam"</item>
<item msgid="45075631231212732">"Vienmēr izmantot HDCP pārbaudi"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Noklusējums"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Noklusējums"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Noklusējums"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Noklusējums"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Noklusējums"</item>
- <item msgid="5618929009984956469">"16 biti iztvērumā"</item>
- <item msgid="3412640499234627248">"24 biti iztvērumā"</item>
- <item msgid="121583001492929387">"32 biti iztvērumā"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Noklusējums"</item>
- <item msgid="4726688794884191540">"16 biti iztvērumā"</item>
- <item msgid="305344756485516870">"24 biti iztvērumā"</item>
- <item msgid="244568657919675099">"32 biti iztvērumā"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Noklusējums"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Noklusējums"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Vēlamā skaņas kvalitāte (990/909 Kb/s)"</item>
- <item msgid="138837449700903545">"Standarta (660/606 Kb/s)"</item>
- <item msgid="4777177307869441982">"Vēlamais savienojums (330/303 Kb/s)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Vēlamā skaņas kvalitāte (990/909 Kb/s)"</item>
- <item msgid="9091111147684472529">"Standarta (660/606 Kb/s)"</item>
- <item msgid="3367904477834831032">"Vēlamais savienojums (330/303 Kb/s)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Izslēgts"</item>
<item msgid="1593289376502312923">"64 KB"</item>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index dd17509..070583e 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Vienmēr aktīvs mobilo datu savienojums"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Atspējot absolūto skaļumu"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth audio kodeks"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Atlasīt vēlamo Bluetooth A2DP kodeku"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth audio iztveršanas ātrums"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Atlasīt vēlamo Bluetooth A2DP kodeka iztveršanas ātrumu"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth audio bitu skaits iztvērumā"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Atlasīt vēlamo Bluetooth A2DP kodeka bitu skaitu iztvērumā"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth audio kanāla režīms"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Atlasīt vēlamo Bluetooth A2DP kodeka kanāla režīmu"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth audio LDAC atskaņošanas kvalitāte"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Atlasīt vēlamo Bluetooth A2DP kodeka LDAC atskaņošanas kvalitāti"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Rādīt bezvadu attēlošanas sertifikācijas iespējas"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Palieliniet Wi‑Fi reģistrēšanas līmeni; rādīt katram SSID RSSI Wi‑Fi atlasītājā."</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Ja opcija ir iespējota un Wi‑Fi signāls ir vājš, datu savienojuma pāreja no Wi-Fi uz mobilo tīklu tiks veikta agresīvāk."</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Palīdzība un atsauksmes"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Izvēlne"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Iev. paroli, lai atiest. rūpnīcas iest. dem. režīmā"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Tālāk"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Nepieciešama parole"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mk/arrays.xml b/packages/SettingsLib/res/values-mk/arrays.xml
index 55e60a7..e27d02d 100644
--- a/packages/SettingsLib/res/values-mk/arrays.xml
+++ b/packages/SettingsLib/res/values-mk/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Секогаш користи ХДЦП проверка"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Стандардно"</item>
+ <item msgid="7065842274271279580">"Користи избор на системот (стандардно)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Стандардно"</item>
+ <item msgid="5062108632402595000">"Користи избор на системот (стандардно)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Стандардно"</item>
+ <item msgid="3093023430402746802">"Користи избор на системот (стандардно)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Стандардно"</item>
+ <item msgid="3214516120190965356">"Користи избор на системот (стандардно)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Стандардно"</item>
+ <item msgid="2684127272582591429">"Користи избор на системот (стандардно)"</item>
<item msgid="5618929009984956469">"16 бита/семпл"</item>
<item msgid="3412640499234627248">"24 бита/семпл"</item>
<item msgid="121583001492929387">"32 бита/семпл"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Стандардно"</item>
+ <item msgid="1081159789834584363">"Користи избор на системот (стандардно)"</item>
<item msgid="4726688794884191540">"16 бита/семпл"</item>
<item msgid="305344756485516870">"24 бита/семпл"</item>
<item msgid="244568657919675099">"32 бита/семпл"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Стандардно"</item>
+ <item msgid="5226878858503393706">"Користи избор на системот (стандардно)"</item>
<item msgid="4106832974775067314">"Моно"</item>
<item msgid="5571632958424639155">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Стандардно"</item>
+ <item msgid="4118561796005528173">"Користи избор на системот (стандардно)"</item>
<item msgid="8900559293912978337">"Моно"</item>
<item msgid="8883739882299884241">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Претпочитан квалитет (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Стандардно (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Претпочитана врска (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"Оптимизирај за квалитет на аудиото (990 kb/s/909 kb/s)"</item>
+ <item msgid="2921767058740704969">"Балансиран квалитет на звукот и врската (660 kb/s/606 kb/s)"</item>
+ <item msgid="3682554248829489641">"Оптимизирај за квалитет на врската (330 kb/s/303 kb/s)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Претпочитан квалитет (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Стандардно (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Претпочитана врска (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"Оптимизирај за квалитет на аудиото"</item>
+ <item msgid="4327143584633311908">"Балансиран квалитет на звукот и врската"</item>
+ <item msgid="6155648878105378550">"Оптимизирај за квалитет на врската"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Исклучено"</item>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index e248804..0662c8f 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Мобилниот интернет е секогаш активен"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Оневозможете апсолутна јачина на звук"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Кодек за аудио преку Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Изберете претпочитан A2DP кодек преку Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Изберете кодек за аудио преку Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Стапка на семпл преку Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Изберете претпочитана стапка на семпл за A2DP кодекот преку Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Изберете кодек за аудио преку Bluetooth:\nСтапка на примерок"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Аудио бит-по-семпл преку Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Изберете претпочитан бит-по-семпл за A2DP кодекот преку Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Изберете кодек за аудио преку Bluetooth:\nБитови/примерок"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Режим на канал за аудио преку Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Изберете претпочитан режим на канал за кодек за A2DP преку Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Квалитет на LDAC-аудио репродукција преку Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Изберете претпочитан квалитет на LDAC-репродукција на A2DP кодекот преку Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Изберете кодек за аудио преку Bluetooth:\nРежим на канал"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Кодек за LDAC-аудио преку Bluetooth: квалитет на репродукција"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Изберете кодек за LDAC-аудио преку Bluetooth:\nКвалитет на репродукција"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Емитување: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Покажи ги опциите за безжичен приказ на сертификат"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Зголеми Wi‑Fi ниво на пријавување, прикажи по SSID RSSI во Wi‑Fi бирач"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Кога е вклучено, Wi-Fi ќе биде поагресивно при предавање на поврзувањето со податоци на мобилната мрежа при слаб сигнал на Wi-Fi."</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Помош и повратни информации"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Мени"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Внесете лозинка за фаб. ресет. во демо"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Следно"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Потребна е лозинка"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ml/arrays.xml b/packages/SettingsLib/res/values-ml/arrays.xml
index 57755ed..eb7ef5f 100644
--- a/packages/SettingsLib/res/values-ml/arrays.xml
+++ b/packages/SettingsLib/res/values-ml/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"എല്ലായ്പ്പോഴും HDCP പരിശോധന ഉപയോഗിക്കുക"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ഡിഫോൾട്ട്"</item>
+ <item msgid="7065842274271279580">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ഡിഫോൾട്ട്"</item>
+ <item msgid="5062108632402595000">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ഡിഫോൾട്ട്"</item>
+ <item msgid="3093023430402746802">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ഡിഫോൾട്ട്"</item>
+ <item msgid="3214516120190965356">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ഡിഫോൾട്ട്"</item>
+ <item msgid="2684127272582591429">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
<item msgid="5618929009984956469">"16 ബിറ്റ്/സാമ്പിൾ"</item>
<item msgid="3412640499234627248">"24 ബിറ്റ്/സാമ്പിൾ"</item>
<item msgid="121583001492929387">"32 ബിറ്റ്/സാമ്പിൾ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ഡിഫോൾട്ട്"</item>
+ <item msgid="1081159789834584363">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
<item msgid="4726688794884191540">"16 ബിറ്റ്/സാമ്പിൾ"</item>
<item msgid="305344756485516870">"16 ബിറ്റ്/സാമ്പിൾ"</item>
<item msgid="244568657919675099">"32 ബിറ്റ്/സാമ്പിൾ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ഡിഫോൾട്ട്"</item>
+ <item msgid="5226878858503393706">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
<item msgid="4106832974775067314">"മോണോ"</item>
<item msgid="5571632958424639155">"സ്റ്റീരിയോ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ഡിഫോൾട്ട്"</item>
+ <item msgid="4118561796005528173">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
<item msgid="8900559293912978337">"മോണോ"</item>
<item msgid="8883739882299884241">"സ്റ്റീരിയോ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"നിർദ്ദേശിക്കുന്ന ശബ്ദ നിലവാരം (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"അടിസ്ഥാനം (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"നിർദ്ദേശിക്കുന്ന കണക്ഷൻ (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"ഓഡിയോ നിലവാരമുയർത്താൻ ഒപ്റ്റിമൈസ് ചെയ്യുക (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"സന്തുലിതമായ ഓഡിയോ/കണക്ഷൻ നിലവാരം (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"കണക്ഷൻ നിലവാരമുയർത്താൻ ഒപ്റ്റിമൈസ് ചെയ്യുക (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"നിർദ്ദേശിക്കുന്ന ശബ്ദ നിലവാരം (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"അടിസ്ഥാനം (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"നിർദ്ദേശിക്കുന്ന കണക്ഷൻ (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"ശബ്ദനിലവാരമുയർത്താൻ ഒപ്റ്റിമൈസ് ചെയ്യുക"</item>
+ <item msgid="4327143584633311908">"സന്തുലിതമായ ഓഡിയോ/കണക്ഷൻ നിലവാരം"</item>
+ <item msgid="6155648878105378550">"കണക്ഷൻ നിലവാരമുയർത്താൻ ഒപ്റ്റിമൈസ് ചെയ്യുക"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ഓഫ്"</item>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index b88298d..b238625 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"സെല്ലുലാർ ഡാറ്റ എല്ലായ്പ്പോഴും സജീവം"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"അബ്സൊല്യൂട്ട് വോളിയം പ്രവർത്തനരഹിതമാക്കുക"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth ഓഡിയോ കോഡെക്"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"നിർദ്ദേശിക്കുന്ന Bluetooth A2DP കോഡെക് തിരഞ്ഞെടുക്കുക"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth ഓഡിയോ കോഡെക് തിരഞ്ഞെടുക്കുക"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth ഓഡിയോ സാമ്പിൾ നിരക്ക്"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"നിർദ്ദേശിക്കുന്ന Bluetooth A2DP കോഡെക് സാമ്പിൾ നിരക്ക് തിരഞ്ഞെടുക്കുക"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth ഓഡിയോ കോഡെക് തിരഞ്ഞെടുക്കുക:\nസാമ്പിൾ നിരക്ക്"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"പ്രതി സാമ്പിളിലെ Bluetooth ഓഡിയോ ബിറ്റ് നി"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"പ്രതി സാമ്പിളിന് നിർദ്ദേശിക്കുന്ന Bluetooth A2DP കോഡെക് ബിറ്റ് നിരക്ക് തിരഞ്ഞെടുക്കുക"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth ഓഡിയോ കോഡെക് തിരഞ്ഞെടുക്കുക:\nബിറ്റ്/സാമ്പിൾ"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth ഓഡിയോ ചാനൽ മോഡ്"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"നിർദ്ദേശിക്കുന്ന Bluetooth A2DP കോഡെക് ചാനൽ മോഡ് തിരഞ്ഞെടുക്കുക"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth ഓഡിയോ LDAC പ്ലേബാക്ക് നിലവാരം"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"നിർദ്ദേശിക്കുന്ന Bluetooth A2DP കോഡെക് LDAC പ്ലേബാക്ക് നിലവാരം തിരഞ്ഞെടുക്കുക"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth ഓഡിയോ കോഡെക് തിരഞ്ഞെടുക്കുക:\nചാനൽ മോഡ്"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth ഓഡിയോ LDAC കോഡെക്: പ്ലേബാക്ക് നിലവാരം"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth ഓഡിയോ LDAC കോഡെക് തിരഞ്ഞെടുക്കുക:\nപ്ലേബാക്ക് നിലവാരം"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"സ്ട്രീമിംഗ്: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"വയർലെസ് ഡിസ്പ്ലേ സർട്ടിഫിക്കേഷനായി ഓപ്ഷനുകൾ ദൃശ്യമാക്കുക"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"വൈഫൈ പിക്കറിൽ ഓരോ SSID RSSI പ്രകാരം കാണിക്കാൻ വൈഫൈ ലോഗിംഗ് നില വർദ്ധിപ്പിക്കുക"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"പ്രവർത്തനക്ഷമമായിരിക്കുമ്പോൾ, വൈഫൈ സിഗ്നൽ കുറവായിരിക്കുന്ന സമയത്ത് സെല്ലുലാറിലേക്ക് ഡാറ്റ കണക്ഷൻ മുഖേന കൈമാറുന്നതിൽ വൈഫൈ കൂടുതൽ പ്രവർത്തനക്ഷമമാകും"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"സഹായവും പ്രതികരണവും"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"മെനു"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"ഡെമോ മോഡിൽ ഫാക്ടറി റീസെറ്റിന് പാസ്വേഡ് നൽകുക"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"അടുത്തത്"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"പാസ്വേഡ് ആവശ്യമാണ്"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mn/arrays.xml b/packages/SettingsLib/res/values-mn/arrays.xml
index 88d6517..8902946 100644
--- a/packages/SettingsLib/res/values-mn/arrays.xml
+++ b/packages/SettingsLib/res/values-mn/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Байнга HDCP шалгахыг ашиглах"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Өгөгдмөл"</item>
+ <item msgid="7065842274271279580">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Өгөгдмөл"</item>
+ <item msgid="5062108632402595000">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Өгөгдмөл"</item>
+ <item msgid="3093023430402746802">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
<item msgid="8895532488906185219">"44.1 кГц"</item>
<item msgid="2909915718994807056">"48.0 кГц"</item>
<item msgid="3347287377354164611">"88.2 кГц"</item>
<item msgid="1234212100239985373">"96.0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Өгөгдмөл"</item>
+ <item msgid="3214516120190965356">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
<item msgid="4482862757811638365">"44.1 кГц"</item>
<item msgid="354495328188724404">"48.0 кГц"</item>
<item msgid="7329816882213695083">"88.2 кГц"</item>
<item msgid="6967397666254430476">"96.0 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Өгөгдмөл"</item>
+ <item msgid="2684127272582591429">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
<item msgid="5618929009984956469">"16 бит/жишээ"</item>
<item msgid="3412640499234627248">"24 бит/жишээ"</item>
<item msgid="121583001492929387">"32 бит/жишээ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Өгөгдмөл"</item>
+ <item msgid="1081159789834584363">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
<item msgid="4726688794884191540">"16 бит/жишээ"</item>
<item msgid="305344756485516870">"24 бит/жишээ"</item>
<item msgid="244568657919675099">"32 бит/жишээ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Өгөгдмөл"</item>
+ <item msgid="5226878858503393706">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
<item msgid="4106832974775067314">"Моно"</item>
<item msgid="5571632958424639155">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Өгөгдмөл"</item>
+ <item msgid="4118561796005528173">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
<item msgid="8900559293912978337">"Моно"</item>
<item msgid="8883739882299884241">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Давуу дууны чанар (990кб/с/909кб/с)"</item>
- <item msgid="138837449700903545">"Стандарт (660кб/с/606кб/с)"</item>
- <item msgid="4777177307869441982">"Давуу холболт (330кб/с/303kbps)"</item>
+ <item msgid="3411577996960199959">"Аудио чанарт оновчлох (990кб/цаг/909кб/цаг)"</item>
+ <item msgid="2921767058740704969">"Аудио, холболтын чанарыг тэнцүүлсэн (660кб/цаг/606кб/цаг)"</item>
+ <item msgid="3682554248829489641">"Холболтын чанарт оновчлох (330кб/цаг/303кб/цаг)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Давуу дууны чанар (990кб/с/909кб/с)"</item>
- <item msgid="9091111147684472529">"Стандарт (660кб/с/606кб/с)"</item>
- <item msgid="3367904477834831032">"Давуу холболт (330кб/с/303kbps)"</item>
+ <item msgid="7668834469173465015">"Аудио чанарт оновчлох"</item>
+ <item msgid="4327143584633311908">"Аудио, холболтын чанарыг тэнцүүлсэн"</item>
+ <item msgid="6155648878105378550">"Холболтын чанарт оновчлох"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Идэвхгүй"</item>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index f412ac4..68f3df5 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Үүрэн холбооны датаг үргэлж идэвхтэй байлгана"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Үнэмлэхүй дууны түвшинг идэвхгүй болгох"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth аудио кодлогч"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Давуу Bluetooth A2DP кодлогч сонгох"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth аудио кодлогч сонгох"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth аудио жишээний үнэлгээ"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Давуу Bluetooth A2DP кодлогч жишээний үнэлгээ"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth аудио кодлогч сонгох:\nЖишээний хэмжээ"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Жишээ тутмын Bluetooth аудионы бит"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Жишээ тутмын давуу Bluetooth A2DP кодлогч битийг сонгох"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth аудио кодлогч сонгох:\nЖишээ бүрт бит"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth аудио сувгийн горим"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Давуу Bluetooth A2DP кодлогч сувгийн горимыг сонгох"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth аудио LDAC тоглуулагчийн чанар"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Давуу Bluetooth A2DP кодлогч LDAC тоглуулагчийн чанарыг сонгох"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth аудио кодлогч сонгох:\nСувгийн горим"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Аудио LDAC Кодлогч: Тоглуулагчийн чанар"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth Аудио LDAC Кодлогч сонгох:\nТоглуулагчийн чанар"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Дамжуулж байна: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Утасгүй дэлгэцийн сертификатын сонголтыг харуулах"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi лог-н түвшинг нэмэгдүүлэх, Wi‑Fi Сонгогч дээрх SSID-д ногдох RSSI-г харуулах"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Идэвхжүүлсэн үед Wi‑Fi дохио сул бол дата холболтыг Үүрэн рүү шилжүүлэхдээ илүү идэвхтэй байх болно"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Тусламж, санал хүсэлт"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Цэс"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"Гринвичийн цаг"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Үйлдвэрийн тохиргоог демо горимд ажиллуулахын тулд нууц үг оруулна уу"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Дараагийн"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Нууц үг шаардлагатай"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mr/arrays.xml b/packages/SettingsLib/res/values-mr/arrays.xml
index 9204c458..f6ebf83 100644
--- a/packages/SettingsLib/res/values-mr/arrays.xml
+++ b/packages/SettingsLib/res/values-mr/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"नेहमी HDCP तपासणी वापरा"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"डीफॉल्ट"</item>
+ <item msgid="7065842274271279580">"प्रणाली निवड वापरा (डीफॉल्ट)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX-HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"डीफॉल्ट"</item>
+ <item msgid="5062108632402595000">"प्रणाली निवड वापरा (डीफॉल्ट)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX-HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"डीफॉल्ट"</item>
+ <item msgid="3093023430402746802">"प्रणाली निवड वापरा (डीफॉल्ट)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"डीफॉल्ट"</item>
+ <item msgid="3214516120190965356">"प्रणाली निवड वापरा (डीफॉल्ट)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"डीफॉल्ट"</item>
+ <item msgid="2684127272582591429">"प्रणाली निवड वापरा (डीफॉल्ट)"</item>
<item msgid="5618929009984956469">"16 बिट/नमुना"</item>
<item msgid="3412640499234627248">"24 बिट/नमुना"</item>
<item msgid="121583001492929387">"32 बिट/नमुना"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"डीफॉल्ट"</item>
+ <item msgid="1081159789834584363">"प्रणाली निवड वापरा (डीफॉल्ट)"</item>
<item msgid="4726688794884191540">"16 बिट/नमुना"</item>
<item msgid="305344756485516870">"24 बिट/नमुना"</item>
<item msgid="244568657919675099">"32 बिट/नमुना"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"डीफॉल्ट"</item>
+ <item msgid="5226878858503393706">"प्रणाली निवड वापरा (डीफॉल्ट)"</item>
<item msgid="4106832974775067314">"मोनो"</item>
<item msgid="5571632958424639155">"स्टिरिओ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"डीफॉल्ट"</item>
+ <item msgid="4118561796005528173">"प्रणाली निवड वापरा (डीफॉल्ट)"</item>
<item msgid="8900559293912978337">"मोनो"</item>
<item msgid="8883739882299884241">"स्टिरिओ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"प्राधान्य दिलेली ध्वनी गुणवत्ता (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"मानक (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"प्राधान्य दिलेलेे कनेेक्शन (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"ऑडिओ गुणवत्तेसाठी (990kbps/909kbps) ऑप्टिमाइझ करा"</item>
+ <item msgid="2921767058740704969">"संतुलित ऑडिओ आणि कनेक्शन गुणवत्ता (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"कनेक्शन गुणवत्तेसाठी (330kbps/303kbps) ऑप्टिमाइझ करा"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"प्राधान्य दिलेली ध्वनी गुणवत्ता (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"मानक (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"प्राधान्य दिलेलेे कनेेक्शन (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"ऑडिओ गुणवत्तेसाठी ऑप्टिमाइझ करा"</item>
+ <item msgid="4327143584633311908">"संतुलित ऑडिओ आणि कनेक्शन गुणवत्ता"</item>
+ <item msgid="6155648878105378550">"कनेक्शन गुणवत्तेसाठी ऑप्टिमाइझ करा"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"बंद"</item>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 8b73d21..a91ad45 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"सेल्युलर डेटा नेहमी सक्रिय"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"संपूर्ण आवाज अक्षम करा"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"ब्लूटूथ ऑडिओ कोडेक"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"प्राधान्यीकृत ब्लूटुथ A2DP कोडेक निवडा"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth ऑडिओ कोडेक निवडा"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"ब्लूटूथ ऑडिओ नमुना दर"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"प्राधान्यीकृत ब्लूटुथ A2DP कोडेक नमुना दर निवडा"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth ऑडिओ कोडेक निवडा:\nनमुना दर"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"प्रति नमुना ब्लूटुथ ऑडिओ बिट"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"प्रति नमुना प्राधान्यीकृत ब्लूटुथ A2DP कोडेक बिट निवडा"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth ऑडिओ कोडेक निवडा:\nबिट प्रति नमुना"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"ब्लूटूथ ऑडिओ चॅनेल मोड"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"प्राधान्यीकृत ब्लूटुथ A2DP कोडेक चॅनेल मोड निवडा"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"ब्लूटुथ ऑडिओ LDAC प्लेबॅक गुणवत्ता"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"प्राधान्यीकृत ब्लूटुथ A2DP कोडेक LDAC प्लेबॅक गुणवत्ता निवडा"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth ऑडिओ कोडेक निवडा:\nचॅनेल मोड"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth ऑडिओ LDAC कोडेक: प्लेबॅक गुणवत्ता"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth ऑडिओ LDAC कोडेक निवडा:\nप्लेबॅक गुणवत्ता"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"धारावाहिक: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"वायरलेस प्रदर्शन प्रमाणिकरणासाठी पर्याय दर्शवा"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"वाय-फाय लॉगिंग स्तर वाढवा, वाय-फाय निवडकामध्ये प्रति SSID RSSI दर्शवा"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"सक्षम केल्यास, वाय-फाय सिग्नल निम्न असताना, वाय-फाय डेटा कनेक्शन सेल्युलरवर बळपूर्वक स्विच करेल."</string>
@@ -352,4 +353,10 @@
<string name="help_feedback_label" msgid="6815040660801785649">"मदत आणि अभिप्राय"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"मेनू"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <!-- no translation found for retail_demo_reset_message (118771671364131297) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_next (8356731459226304963) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_title (696589204029930100) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ms/arrays.xml b/packages/SettingsLib/res/values-ms/arrays.xml
index 7ec6fab..5feff37 100644
--- a/packages/SettingsLib/res/values-ms/arrays.xml
+++ b/packages/SettingsLib/res/values-ms/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Gunakan penyemakan HDCP untuk kandungan DRM sahaja"</item>
<item msgid="45075631231212732">"Sentiasa gunakan penyemakan HDCP"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Lalai"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Lalai"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Lalai"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Lalai"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Lalai"</item>
- <item msgid="5618929009984956469">"16 bit/sampel"</item>
- <item msgid="3412640499234627248">"24 bit/sampel"</item>
- <item msgid="121583001492929387">"32 bit/sampel"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Lalai"</item>
- <item msgid="4726688794884191540">"16 bit/sampel"</item>
- <item msgid="305344756485516870">"24 bit/sampel"</item>
- <item msgid="244568657919675099">"32 bit/sampel"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Lalai"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Lalai"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Kualiti bunyi dipilih (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Standard (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Sambungan yang dipilih (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Kualiti bunyi dipilih (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Sambungan yang dipilih (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Mati"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index e57219c..a3459772 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Data selular sentiasa aktif"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Lumpuhkan kelantangan mutlak"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Pilih Codec A2DP Bluetooth yang Diingini"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Kadar Sampel Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Pilih Kadar Sampel Codec A2DP Bluetooth yang Diingini"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bit Per Sampel Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Pilih Bit Per Sampel Codec A2DP Bluetooth yang Diingini"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Mod Saluran Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Pilih Mod Saluran Codec A2DP Bluetooth yang Diingini"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Kualiti Main Balik LDAC Audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Pilih Kualiti Main Balik LDAC Codec A2DP Bluetooth yang Diingini"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Tunjukkan pilihan untuk pensijilan paparan wayarles"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Tingkatkan tahap pengelogan Wi-Fi, tunjuk setiap SSID RSSI dalam Pemilih Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Apabila didayakan, Wi-Fi akan menjadi lebih agresif dalam menyerahkan sambungan data ke Selular, apabila isyarat Wi-Fi rendah"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Bantuan & maklum balas"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Mskkn kta laluan utk ttpn sml kilang dlm mod demo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Seterusnya"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Kata laluan diperlukan"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-my/arrays.xml b/packages/SettingsLib/res/values-my/arrays.xml
index f5e3824..ddd0a64 100644
--- a/packages/SettingsLib/res/values-my/arrays.xml
+++ b/packages/SettingsLib/res/values-my/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"HDCP checkingအားအမြဲသုံးပါ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"မူရင်း"</item>
+ <item msgid="7065842274271279580">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"မူရင်း"</item>
+ <item msgid="5062108632402595000">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"မူရင်း"</item>
+ <item msgid="3093023430402746802">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
<item msgid="8895532488906185219">"၄၄.၁ kHz"</item>
<item msgid="2909915718994807056">"၄၈.၀ kHz"</item>
<item msgid="3347287377354164611">"၈၈.၂ kHz"</item>
<item msgid="1234212100239985373">"၉၆.၀ kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"မူရင်း"</item>
+ <item msgid="3214516120190965356">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
<item msgid="4482862757811638365">"၄၄.၁ kHz"</item>
<item msgid="354495328188724404">"၄၈.၀ kHz"</item>
<item msgid="7329816882213695083">"၈၈.၂ kHz"</item>
<item msgid="6967397666254430476">"၉၆.၀ kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"မူရင်း"</item>
+ <item msgid="2684127272582591429">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
<item msgid="5618929009984956469">"၁၆ bits/နမူနာ"</item>
<item msgid="3412640499234627248">"၂၄ bits/နမူနာ"</item>
<item msgid="121583001492929387">"၃၂ bits/နမူနာ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"မူရင်း"</item>
+ <item msgid="1081159789834584363">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
<item msgid="4726688794884191540">"၁၆ bits/နမူနာ"</item>
<item msgid="305344756485516870">"၂၄ bits/နမူနာ"</item>
<item msgid="244568657919675099">"၃၂ bits/နမူနာ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"မူရင်း"</item>
+ <item msgid="5226878858503393706">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
<item msgid="4106832974775067314">"မိုနို"</item>
<item msgid="5571632958424639155">"စတီရီယို"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"မူရင်း"</item>
+ <item msgid="4118561796005528173">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
<item msgid="8900559293912978337">"မိုနို"</item>
<item msgid="8883739882299884241">"စတီရီယို"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"လိုလားသည့်အသံအရည်အသွေး (၉၉၀kbps/၉၀၉kbps)"</item>
- <item msgid="138837449700903545">"ပုံမှန် (၆၆၀kbps/၆၀၆kbps)"</item>
- <item msgid="4777177307869441982">"လိုလားသည့် မြန်နှုန်း (၃၃၀kbps/၃၀၃kbps)"</item>
+ <item msgid="3411577996960199959">"အသံအရည်အသွေးကို ပိုကောင်းအောင် ပြုလုပ်ပေးသည် (၉၉၀kbps/၉၀၉kbps)"</item>
+ <item msgid="2921767058740704969">"အသံနှင့် ချိတ်ဆက်မှု အရည်အသွေးကို မျှတအောင် ချိန်ဆပေးသည် (၆၆၀kbps/၆၀၆kbps)"</item>
+ <item msgid="3682554248829489641">"ချိတ်ဆက်မှု အရည်အသွေးကို ပိုကောင်းအောင် ပြုလုပ်ပေးသည် (၃၃၀kbps/၃၀၃kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"လိုလားသည့်အသံအရည်အသွေး (၉၉၀kbps/၉၀၉kbps)"</item>
- <item msgid="9091111147684472529">"ပုံမှန် (၆၆၀kbps/၆၀၆kbps)"</item>
- <item msgid="3367904477834831032">"လိုလားသည့် ချိတ်ဆက်မှု (၃၃၀kbps/၃၀၃kbps)"</item>
+ <item msgid="7668834469173465015">"အသံအရည်အသွေးကို ပိုကောင်းအောင် ပြုလုပ်ပေးသည်"</item>
+ <item msgid="4327143584633311908">"အသံနှင့် ချိတ်ဆက်မှု အရည်သွေးကို မျှတအောင် ချိန်ဆပေးသည်"</item>
+ <item msgid="6155648878105378550">"ချိတ်ဆက်မှု အရည်အသွေးကို ပိုကောင်းအောင် ပြုလုပ်ပေးသည်"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ပိတ်ပါ"</item>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 941b240..e2850ae 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"ဆဲလ်လူလာဒေတာ အမြဲတမ်းဖွင့်ထားသည်"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"ပကတိ အသံနှုန်း သတ်မှတ်ချက် ပိတ်ရန်"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"ဘလူးတုသ်အသံ ကိုးဒက်ခ်"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"လိုလားသည့် ဘလူးတုသ် A2DP ကိုးဒက်ခ်ကို ရွေးချယ်ပါ"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"ဘလူးတုသ်အသံကိုးဒက်ခ်ကို ရွေးပါ"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"ဘလူးတုသ်အသံနမူနာနှုန်း"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"လိုလားသည့် ဘလူးတုသ် A2DP ကိုးဒက်ခ် အသံနမူနာနှုန်းကို ရွေးချယ်ပါ"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"ဘလူးတုသ်အသံ ကိုးဒက်ခ်ကို ရွေးပါ−\nနမူနာနှုန်း"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"နမူနာတစ်ခုစီတွင် ပါဝင်သော ဘလူးတုသ်အသံပမာဏ Bits"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"နမူနာတစ်ခုချင်းစီအတွက် လိုလားသည့် ဘလူးတုသ် A2DP ကိုးဒက်ခ် Bits ကို ရွေးပါ"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"ဘလူးတုသ်အသံ ကိုးဒက်ခ်ကို ရွေးပါ−\nနမူနာတစ်ခုချင်းအတွက် Bits"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"ဘလူးတုသ်အသံချန်နယ်မုဒ်"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"လိုလားသည့် ဘလူးတုသ် A2DP ကိုးဒက်ခ် ချန်နယ်မုဒ်ကို ရွေးချယ်ပါ"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"LDAC ဖွင့်ရန် ဘလူးတုသ်အသံ အရည်အသွေး"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"LDAC ဖွင့်ရန် လိုလားသည့် ဘလူးတုသ်အသံ အရည်အသွေးကို ရွေးချယ်ပါ"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"ဘလူးတုသ်အသံကိုးဒက်ခ်ကို ရွေးပါ−\nချန်နယ်မုဒ်"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ဘလူးတုသ်အသံ LDAC ကိုးဒက်ခ်− နားထောင်ရန် အရည်အသွေး"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ဘလူးတုသ်အသံ LDAC ကိုးဒက်ခ်ကို ရွေးပါ−\nနားထောင်ရန် အရည်အသွေး"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"တိုက်ရိုက်လွှင့်နေသည်− <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ကြိုးမဲ့ အခင်းအကျင်း အသိအမှတ်ပြုလက်မှတ်အတွက် ရွေးချယ်စရာများပြရန်"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi မှတ်တမ်းတင်ခြင်း နှုန်းအားမြင့်ကာ၊ Wi‑Fi ရွေးရာတွင် SSID RSSI ဖြင့်ပြပါ"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"ဖွင့်ထားလျှင်၊ Wi‑Fi မှ ဆယ်လူလာသို့ အချက်လက် ချိတ်ဆက်မှုအား လွှဲပြောင်းရာ၌ ပိုမိုထိရောက်ပါသည်၊ WIFI အားနည်းနေချိန်တွင်"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"အကူအညီနှင့် အကြံပြုချက်"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"မီနူး"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"ဂရင်းနစ်စံတော်ချိန်"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"ဒီမိုမုဒ်၌နဂိုဆက်တင်ထားရန်စကားဝှက်ထည့်ပါ"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"ရှေ့သို့"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"စကားဝှက် လိုအပ်သည်"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml
index bf0b414..bca999c 100644
--- a/packages/SettingsLib/res/values-nb/arrays.xml
+++ b/packages/SettingsLib/res/values-nb/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Bruk alltid HDCP-kontroll"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Standard"</item>
+ <item msgid="7065842274271279580">"Bruk systemvalg (standard)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Standard"</item>
+ <item msgid="5062108632402595000">"Bruk systemvalg (standard)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Standard"</item>
+ <item msgid="3093023430402746802">"Bruk systemvalg (standard)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Standard"</item>
+ <item msgid="3214516120190965356">"Bruk systemvalg (standard)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Standard"</item>
+ <item msgid="2684127272582591429">"Bruk systemvalg (standard)"</item>
<item msgid="5618929009984956469">"16 bits/sample"</item>
<item msgid="3412640499234627248">"24 bits/sample"</item>
<item msgid="121583001492929387">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Standard"</item>
+ <item msgid="1081159789834584363">"Bruk systemvalg (standard)"</item>
<item msgid="4726688794884191540">"16 bits/sample"</item>
<item msgid="305344756485516870">"24 bits/sample"</item>
<item msgid="244568657919675099">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Standard"</item>
+ <item msgid="5226878858503393706">"Bruk systemvalg (standard)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Standard"</item>
+ <item msgid="4118561796005528173">"Bruk systemvalg (standard)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Prioriter lydkvalitet (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Standard (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Prioriter tilkobling (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"Optimaliser for lydkvalitet (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Balansert lyd- og tilkoblingskvalitet (660 kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"Optimaliser for tilkoblingskvalitet (330 kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Prioriter lydkvalitet (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Prioriter tilkobling (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"Optimaliser for lydkvalitet"</item>
+ <item msgid="4327143584633311908">"Balansert lyd- og tilkoblingskvalitet"</item>
+ <item msgid="6155648878105378550">"Optimaliser for tilkoblingskvalitet"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Av"</item>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 53e0127..172d59c 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobildata er alltid aktiv"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Slå av funksjonen for absolutt volum"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Kodek for Bluetooth-lyd"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Velg foretrukket A2DP-kodek for Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Velg kodek for Bluetooth-lyd"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Samplefrekvens for Bluetooth-lyd"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Velg foretrukket samplefrekvens fra A2DP-kodek for Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Velg kodek for Bluetooth-lyd:\nSamplefrekvens"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits per sample for Bluetooth-lyd"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Velg foretrukket bits per sample fra A2DP-kodek for Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Velg lydkodek for Bluetooth:\nBits Per Sample"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Kanalmodus for Bluetooth-lyd"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Velg foretrukket kanalmodus fra A2DP-kodek for Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"LDAC-avspillingskvalitet for Bluetooth-lyd"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Velg foretrukket LDAC-avspillingskvalitet fra A2DP-kodek for Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Velg lydkodek for Bluetooth:\nKanalmodus"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC-kodek for Bluetooth-lyd: Avspillingskvalitet"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Velg LDAC-kodek for Bluetooth-lyd:\nAvspillingskvalitet"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strømming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Vis alternativer for sertifisering av trådløs skjerm"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Øk Wi-Fi-loggenivå – vis per SSID RSSI i Wi-Fi-velgeren"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Hvis dette slås på, overfører Wi-Fi-nettverket datatilkoblingen til mobil mer aggressivt når Wi-Fi-signalet er lavt"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Hjelp og tilbakemelding"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Meny"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Skriv inn passordet for å tilbakestille til fabrikkstandard i demomodus"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Neste"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Passord er obligatorisk"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ne/arrays.xml b/packages/SettingsLib/res/values-ne/arrays.xml
index d9cd5d2..ddde4e2 100644
--- a/packages/SettingsLib/res/values-ne/arrays.xml
+++ b/packages/SettingsLib/res/values-ne/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"सधैँ HDCP जाँच प्रयोग गर्नुहोस्"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"पूर्वनिर्धारित मान"</item>
+ <item msgid="7065842274271279580">"प्रणालीको चयन प्रयोग गर्नुहोस् (पूर्वनिर्धारित)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"पूर्वनिर्धारित मान"</item>
+ <item msgid="5062108632402595000">"प्रणालीको चयन प्रयोग गर्नुहोस् (पूर्वनिर्धारित)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"पूर्वनिर्धारित मान"</item>
+ <item msgid="3093023430402746802">"प्रणालीको चयन प्रयोग गर्नुहोस् (पूर्वनिर्धारित)"</item>
<item msgid="8895532488906185219">"४४.१ kHz"</item>
<item msgid="2909915718994807056">"४८.० kHz"</item>
<item msgid="3347287377354164611">"८८.२ kHz"</item>
<item msgid="1234212100239985373">"९६.० kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"पूर्वनिर्धारित मान"</item>
+ <item msgid="3214516120190965356">"प्रणालीको चयन प्रयोग गर्नुहोस् (पूर्वनिर्धारित)"</item>
<item msgid="4482862757811638365">"४४.१ kHz"</item>
<item msgid="354495328188724404">"४८.० kHz"</item>
<item msgid="7329816882213695083">"८८.२ kHz"</item>
<item msgid="6967397666254430476">"९६.० kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"पूर्वनिर्धारित मान"</item>
+ <item msgid="2684127272582591429">"प्रणालीको चयन प्रयोग गर्नुहोस् (पूर्वनिर्धारित)"</item>
<item msgid="5618929009984956469">"१६ बिट/नमूना"</item>
<item msgid="3412640499234627248">"२४ बिट/नमूना"</item>
<item msgid="121583001492929387">"३२ बिट/नमूना"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"पूर्वनिर्धारित मान"</item>
+ <item msgid="1081159789834584363">"प्रणालीको चयन प्रयोग गर्नुहोस् (पूर्वनिर्धारित)"</item>
<item msgid="4726688794884191540">"१६ बिट/नमूना"</item>
<item msgid="305344756485516870">"२४ बिट/नमूना"</item>
<item msgid="244568657919675099">"३२ बिट/नमूना"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"पूर्वनिर्धारित मान"</item>
+ <item msgid="5226878858503393706">"प्रणालीको चयन प्रयोग गर्नुहोस् (पूर्वनिर्धारित)"</item>
<item msgid="4106832974775067314">"मोनो"</item>
<item msgid="5571632958424639155">"स्टेरियो"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"पूर्वनिर्धारित मान"</item>
+ <item msgid="4118561796005528173">"प्रणालीको चयन प्रयोग गर्नुहोस् (पूर्वनिर्धारित)"</item>
<item msgid="8900559293912978337">"मोनो"</item>
<item msgid="8883739882299884241">"स्टेरियो"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"रुचाइएको आवाजको गुणस्तर (९९०kbps/९०९kbps)"</item>
- <item msgid="138837449700903545">"मानक (६६०kbps/६०६kbps)"</item>
- <item msgid="4777177307869441982">"रुचाइएको जडान (३३०kbps/३०३kbps)"</item>
+ <item msgid="3411577996960199959">"अडियोको गुणस्तर सुधार्न अनुकूलन गर्नुहोस् (९९०kbps/९०९kbps)"</item>
+ <item msgid="2921767058740704969">"सन्तुलित अडियो र जडान गुणस्तर (६६०kbps/६०६kbps)"</item>
+ <item msgid="3682554248829489641">"जडानको गुणस्तर सुधार्न अनुकूलन गर्नुहोस् (३३०kbps/३०३kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"रुचाइएको आवाजको गुणस्तर (९९०kbps/९०९kbps)"</item>
- <item msgid="9091111147684472529">"मानक (६६०kbps/६०६kbps)"</item>
- <item msgid="3367904477834831032">"रुचाइएको जडान (३३०kbps/३०३kbps)"</item>
+ <item msgid="7668834469173465015">"अडियोको गुणस्तर सुधार्न अनुकूलन गर्नुहोस्"</item>
+ <item msgid="4327143584633311908">"सन्तुलित अडियो र जडान गुणस्तर"</item>
+ <item msgid="6155648878105378550">"जडानको गुणस्तर सुधार्न अनुकूलन गर्नुहोस्"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"निष्क्रिय गर्नुहोस्"</item>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 311af42..376fe9c 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"सेलुलर डेटा सधैं सक्रिय"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"निरपेक्ष आवाज असक्षम गर्नुहोस्"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"ब्लुटुथ अडियोको कोडेक"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"रुचाइको ब्लुटुथ A2DP कोडेक चयन गर्नुहोस्"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"ब्लुटुथ अडियोको कोडेक चयन गर्नुहोस्"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"ब्लुटुथ अडियोको नमूना दर"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"रुचाइको ब्लुटुथ A2DP कोडेक नमूना दर चयन गर्नुहोस्"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"ब्लुटुथ अडियोको कोडेक चयन गर्नुहोस्:\nनमूना दर"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"प्रति नमूना ब्लुटुथ अडियोका बिटहरू"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"रुचाइको प्रति नमूना ब्लुटुथ A2DP कोडेकको बिट चयन गर्नुहोस्"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"ब्लुटुथ अडियोको कोडेक चयन गर्नुहोस्:\n प्रति नमूना बिट"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"ब्लुटुथ अडियो च्यानलको मोड"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"रुचाइको ब्लुटुथ A2DP कोडेक च्यानलको मोड चयन गर्नुहोस्"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"ब्लुटुथ अडियो LDAC प्लेब्याकको गुणस्तर"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"रुचाइको ब्लुटुथ A2DP कोडेक LDAC प्लेब्याकको गुणस्तर चयन गर्नुहोस्"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"ब्लुटुथ अडियोको कोडेक चयन गर्नुहोस्:\nच्यानलको मोड"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ब्लुटुथ अडियो LDAC कोडेक: प्लेब्याक गुणस्तर"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ब्लुटुथ अडियो LDAC कोडेक चयन गर्नुहोस्:\nप्लेब्याक गुणस्तर"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"स्ट्रिमिङ: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ताररहित प्रदर्शन प्रमाणीकरणका लागि विकल्पहरू देखाउनुहोस्"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi लग स्तर बढाउनुहोस्, Wi-Fi चयनकर्तामा प्रति SSID RSSI देखाइन्छ"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Wi-Fi संकेत कम हुँदा, सक्षम जब गरिन्छ, Wi-Fi सेलुलर लागि डेटा जडान सुम्पनामा बढी आक्रामक हुनेछ"</string>
@@ -352,4 +353,10 @@
<string name="help_feedback_label" msgid="6815040660801785649">"मद्दत र प्रतिक्रिया"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"मेनु"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <!-- no translation found for retail_demo_reset_message (118771671364131297) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_next (8356731459226304963) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_title (696589204029930100) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-nl/arrays.xml b/packages/SettingsLib/res/values-nl/arrays.xml
index 6eb2b67..e1822c6 100644
--- a/packages/SettingsLib/res/values-nl/arrays.xml
+++ b/packages/SettingsLib/res/values-nl/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"HDCP-controle altijd gebruiken"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Standaard"</item>
+ <item msgid="7065842274271279580">"Systeemselectie gebruiken (standaard)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Standaard"</item>
+ <item msgid="5062108632402595000">"Systeemselectie gebruiken (standaard)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Standaard"</item>
+ <item msgid="3093023430402746802">"Systeemselectie gebruiken (standaard)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Standaard"</item>
+ <item msgid="3214516120190965356">"Systeemselectie gebruiken (standaard)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Standaard"</item>
+ <item msgid="2684127272582591429">"Systeemselectie gebruiken (standaard)"</item>
<item msgid="5618929009984956469">"16 bits per sample"</item>
<item msgid="3412640499234627248">"24 bits per sample"</item>
<item msgid="121583001492929387">"32 bits per sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Standaard"</item>
+ <item msgid="1081159789834584363">"Systeemselectie gebruiken (standaard)"</item>
<item msgid="4726688794884191540">"16 bits per sample"</item>
<item msgid="305344756485516870">"24 bits per sample"</item>
<item msgid="244568657919675099">"32 bits per sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Standaard"</item>
+ <item msgid="5226878858503393706">"Systeemselectie gebruiken (standaard)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Standaard"</item>
+ <item msgid="4118561796005528173">"Systeemselectie gebruiken (standaard)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Voorkeur geluidskwaliteit (990/909 kbps)"</item>
- <item msgid="138837449700903545">"Standaard (660/606 kbps)"</item>
- <item msgid="4777177307869441982">"Voorkeursverbinding (330/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Optimaliseren voor audiokwaliteit (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Gebalanceerde audio- en verbindingskwaliteit (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Optimaliseren voor verbindingskwaliteit (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Voorkeur geluidskwaliteit (990/909 kbps)"</item>
- <item msgid="9091111147684472529">"Standaard (660/606 kbps)"</item>
- <item msgid="3367904477834831032">"Voorkeursverbinding (330/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Optimaliseren voor audiokwaliteit"</item>
+ <item msgid="4327143584633311908">"Gebalanceerde audio- en verbindingskwaliteit"</item>
+ <item msgid="6155648878105378550">"Optimaliseren voor verbindingskwaliteit"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Uit"</item>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 698ff09..30439710 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobiele data altijd actief"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Absoluut volume uitschakelen"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth-audiocodec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Voorkeur voor Bluetooth A2DP-codec selecteren"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth-audiocodec selecteren"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bemonsteringsfrequentie (sample rate) van Bluetooth-audio"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Voorkeur voor bemonsteringsfrequentie (sample rate) voor Bluetooth A2DP-codec selecteren"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth-audiocodec selecteren:\nbemonsteringsfrequentie"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits per sample voor Bluetooth-audio"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Voorkeur voor bits per sample voor Bluetooth A2DP-codec selecteren"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth-audiocodec selecteren:\nbit per sample"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Kanaalmodus voor Bluetooth-audio"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Voorkeur voor kanaalmodus voor Bluetooth A2DP-codec selecteren"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"LDAC-afspeelkwaliteit voor Bluetooth-audio"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Voorkeur voor LDAC-afspeelkwaliteit voor Bluetooth A2DP-codec selecteren"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth-audiocodec selecteren:\nkanaalmodus"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC-codec voor Bluetooth-audio: afspeelkwaliteit"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"LDAC-codec voor Bluetooth-audio selecteren:\nafspeelkwaliteit"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Opties weergeven voor certificering van draadloze weergave"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Logniveau voor wifi verhogen, weergeven per SSID RSSI in wifi-kiezer"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Indien ingeschakeld, is wifi agressiever bij het overgeven van de gegevensverbinding aan mobiel wanneer het wifi-signaal zwak is"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Help en feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Geef wachtwoord op om terug te zetten op fabrieksinstellingen in demomodus"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Volgende"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Wachtwoord vereist"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pa/arrays.xml b/packages/SettingsLib/res/values-pa/arrays.xml
index 670a69b..ed44102 100644
--- a/packages/SettingsLib/res/values-pa/arrays.xml
+++ b/packages/SettingsLib/res/values-pa/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"ਹਮੇਸਾਂ HDCP ਜਾਂਚ ਵਰਤੋ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ਪੂਰਵ-ਨਿਰਧਾਰਤ"</item>
+ <item msgid="7065842274271279580">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ਪੂਰਵ-ਨਿਰਧਾਰਤ"</item>
+ <item msgid="5062108632402595000">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ਪੂਰਵ-ਨਿਰਧਾਰਤ"</item>
+ <item msgid="3093023430402746802">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ਪੂਰਵ-ਨਿਰਧਾਰਤ"</item>
+ <item msgid="3214516120190965356">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ਪੂਰਵ-ਨਿਰਧਾਰਤ"</item>
+ <item msgid="2684127272582591429">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
<item msgid="5618929009984956469">"16 ਬਿਟਾਂ/ਨਮੂਨਾ"</item>
<item msgid="3412640499234627248">"24 ਬਿਟਾਂ/ਨਮੂਨਾ"</item>
<item msgid="121583001492929387">"32 ਬਿਟਾਂ/ਨਮੂਨਾ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ਪੂਰਵ-ਨਿਰਧਾਰਤ"</item>
+ <item msgid="1081159789834584363">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
<item msgid="4726688794884191540">"16 ਬਿਟਾਂ/ਨਮੂਨਾ"</item>
<item msgid="305344756485516870">"24 ਬਿਟਾਂ/ਨਮੂਨਾ"</item>
<item msgid="244568657919675099">"32 ਬਿਟਾਂ/ਨਮੂਨਾ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ਪੂਰਵ-ਨਿਰਧਾਰਤ"</item>
+ <item msgid="5226878858503393706">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
<item msgid="4106832974775067314">"ਮੋਨੋ"</item>
<item msgid="5571632958424639155">"ਸਟੀਰੀਓ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ਪੂਰਵ-ਨਿਰਧਾਰਤ"</item>
+ <item msgid="4118561796005528173">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
<item msgid="8900559293912978337">"ਮੋਨੋ"</item>
<item msgid="8883739882299884241">"ਸਟੀਰੀਓ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"ਤਰਜੀਹੀ ਧੁਨੀ ਗੁਣਵੱਤਾ (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"ਮਿਆਰੀ ਗੁਣਵੱਤਾ (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"ਤਰਜੀਹੀ ਕਨੈਕਸ਼ਨ (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"ਔਡੀਓ ਗੁਣਵੱਤਾ ਨੂੰ ਵਧਾਓ (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"ਬੈਲੈਂਸਡ ਔਡੀਓ ਅਤੇ ਕਨੈਕਸ਼ਨ ਗੁਣਵੱਤਾ (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"ਕਨੈਕਸ਼ਨ ਗੁਣਵੱਤਾ ਨੂੰ ਵਧਾਓ (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"ਤਰਜੀਹੀ ਧੁਨੀ ਗੁਣਵੱਤਾ (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"ਮਿਆਰੀ ਗੁਣਵੱਤਾ (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"ਤਰਜੀਹੀ ਕਨੈਕਸ਼ਨ (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"ਔਡੀਓ ਗੁਣਵੱਤਾ ਨੂੰ ਵਧਾਓ"</item>
+ <item msgid="4327143584633311908">"ਬੈਲੈਂਸਡ ਔਡੀਓ ਅਤੇ ਕਨੈਕਸ਼ਨ ਗੁਣਵੱਤਾ"</item>
+ <item msgid="6155648878105378550">"ਕਨੈਕਸ਼ਨ ਗੁਣਵੱਤਾ ਨੂੰ ਵਧਾਓ"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ਬੰਦ"</item>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 9f717e0..f7a95cd 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"ਸੈਲਿਊਲਰ ਡੇਟਾ ਹਮੇਸ਼ਾ ਕਿਰਿਆਸ਼ੀਲ"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"ਪੂਰਨ ਵੌਲਿਊਮ ਨੂੰ ਅਯੋਗ ਬਣਾਓ"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"ਬਲੂਟੁੱਥ ਔਡੀਓ ਕੋਡੇਕ"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"ਤਰਜੀਹੀ ਬਲੂਟੁੱਥ A2DP ਕੋਡੇਕ ਚੁਣੋ"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"ਬਲੂਟੁੱਥ ਔਡੀਓ ਕੋਡੇਕ ਚੁਣੋ"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"ਬਲੂਟੁੱਥ ਔਡੀਓ ਨਮੂਨਾ ਦਰ"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"ਤਰਜੀਹੀ ਬਲੂਟੁੱਥ A2DP ਕੋਡੇਕ ਨਮੂਨਾ ਦਰ ਚੁਣੋ"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"ਬਲੂਟੁੱਥ ਔਡੀਓ ਕੋਡੇਕ ਚੁਣੋ:\nਨਮੂਨਾ ਗਤੀ"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"ਪ੍ਰਤੀ ਨਮੂਨਾ ਬਲੂਟੁੱਥ ਔਡੀਓ ਬਿਟਾਂ"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"ਪ੍ਰਤੀ ਨਮੂਨਾ ਤਰਜੀਹੀ ਬਲੂਟੁੱਥ A2DP ਕੋਡੇਕ ਬਿਟਾਂ ਚੁਣੋ"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"ਬਲੂਟੁੱਥ ਔਡੀਓ ਕੋਡੇਕ ਚੁਣੋ:\nਬਿਟਾਂ ਪ੍ਰਤੀ ਨਮੂਨਾ"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"ਬਲੂਟੁੱਥ ਔਡੀਓ ਚੈਨਲ ਮੋਡ"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"ਤਰਜੀਹੀ ਬਲੂਟੁੱਥ A2DP ਕੋਡੇਕ ਚੈਨਲ ਮੋਡ ਚੁਣੋ"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"ਬਲੂਟੁੱਥ ਔਡੀਓ LDAC ਪਲੇਬੈਕ ਗੁਣਵੱਤਾ"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"ਤਰਜੀਹੀ ਬਲੂਟੁੱਥ A2DP ਕੋਡੇਕ LDAC ਪਲੇਬੈਕ ਗੁਣਵੱਤਾ ਚੁਣੋ"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"ਬਲੂਟੁੱਥ ਔਡੀਓ ਕੋਡੇਕ ਚੁਣੋ:\nਚੈਨਲ ਮੋਡ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ਬਲੂਟੁੱਥ ਔਡੀਓ LDAC ਕੋਡੇਕ: ਪਲੇਬੈਕ ਗੁਣਵੱਤਾ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ਬਲੂਟੁੱਥ ਔਡੀਓ LDAC ਕੋਡੇਕ ਚੁਣੋ:\nਪਲੇਬੈਕ ਗੁਣਵੱਤਾ"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ਸਟ੍ਰੀਮਿੰਗ: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ਵਾਇਰਲੈਸ ਡਿਸਪਲੇ ਪ੍ਰਮਾਣੀਕਰਨ ਲਈ ਚੋਣਾਂ ਦਿਖਾਓ"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ਲੌਗਿੰਗ ਪੱਧਰ ਵਧਾਓ, Wi‑Fi Picker ਵਿੱਚ ਪ੍ਰਤੀ SSID RSSI ਦਿਖਾਓ"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"ਜਦੋਂ ਸਮਰਥਿਤ ਹੋਵੇ, ਤਾਂ Wi‑Fi ਸੈਲਿਊਲਰ ਨੂੰ ਡੈਟਾ ਕਨੈਕਸ਼ਨ ਹੈਂਡ ਓਵਰ ਕਰਨ ਵਿੱਚ ਵੱਧ ਅਗ੍ਰੈਸਿਵ ਹੋ ਜਾਏਗਾ, ਜਦੋਂ Wi‑Fi ਸਿਗਨਲ ਘੱਟ ਹੋਵੇ"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"ਮਦਦ ਅਤੇ ਪ੍ਰਤੀਕਰਮ"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"ਮੀਨੂ"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"ਡੈਮੋ ਮੋਡ \'ਚ ਫੈਕਟਰੀ ਰੀਸੈੱਟ ਲਈ ਪਾਸਵਰਡ ਦਿਓ"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"ਅੱਗੇ"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pl/arrays.xml b/packages/SettingsLib/res/values-pl/arrays.xml
index 15a7b51..c813cad 100644
--- a/packages/SettingsLib/res/values-pl/arrays.xml
+++ b/packages/SettingsLib/res/values-pl/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Użyj sprawdzania HDCP tylko w przypadku treści chronionych DRM"</item>
<item msgid="45075631231212732">"Zawsze używaj sprawdzania HDCP"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Wartość domyślna"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Wartość domyślna"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Wartość domyślna"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Wartość domyślna"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Wartość domyślna"</item>
- <item msgid="5618929009984956469">"16 bitów/próbkę"</item>
- <item msgid="3412640499234627248">"24 bity/próbkę"</item>
- <item msgid="121583001492929387">"32 bity/próbkę"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Wartość domyślna"</item>
- <item msgid="4726688794884191540">"16 bitów/próbkę"</item>
- <item msgid="305344756485516870">"24 bity/próbkę"</item>
- <item msgid="244568657919675099">"32 bity/próbkę"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Wartość domyślna"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Wartość domyślna"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Preferowana jakość dźwięku (990 kb/s / 909 kb/s)"</item>
- <item msgid="138837449700903545">"Standardowa (660 kb/s / 606 kb/s)"</item>
- <item msgid="4777177307869441982">"Preferowane połączenie (330 kb/s / 303 kb/s)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Preferowana jakość dźwięku (990 kb/s / 909 kb/s)"</item>
- <item msgid="9091111147684472529">"Standardowa (660 kb/s / 606 kb/s)"</item>
- <item msgid="3367904477834831032">"Preferowane połączenie (330 kb/s / 303 kb/s)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Wył."</item>
<item msgid="1593289376502312923">"64 KB"</item>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 9c841fa..1c5f3d2 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Dane komórkowe zawsze aktywne"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Wyłącz głośność bezwzględną"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Kodek dźwięku Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Wybierz preferowany kodek Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Dźwięk Bluetooth – współczynnik próbkowania"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Wybierz preferowany współczynnik próbkowania w kodeku Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Dźwięk Bluetooth – liczba bitów na próbkę"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Wybierz preferowaną liczbę bitów na próbkę w kodeku Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Dźwięk Bluetooth – tryb kanału"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Wybierz preferowany tryb kanału w kodeku Bluetooth A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Dźwięk Bluetooth – jakość dźwięku LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Wybierz preferowaną jakość dźwięku LDAC w kodeku Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaż opcje certyfikacji wyświetlacza bezprzewodowego"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Zwiększ poziom rejestrowania Wi‑Fi, pokazuj według RSSI SSID w selektorze Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Po włączeniu połączenie danych będzie bardziej agresywnie przełączać się z Wi-Fi na sieć komórkową przy słabym sygnale Wi-Fi"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Pomoc i opinie"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Wpisz hasło, by przywrócić ustawienia fabryczne w trybie demonstracyjnym"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Dalej"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Wymagane hasło"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rBR/arrays.xml b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
index 4d1f3bb..e238905 100644
--- a/packages/SettingsLib/res/values-pt-rBR/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Sempre usar a verificação HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Padrão"</item>
+ <item msgid="7065842274271279580">"Usar seleção do sistema (padrão)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Padrão"</item>
+ <item msgid="5062108632402595000">"Usar seleção do sistema (padrão)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Padrão"</item>
+ <item msgid="3093023430402746802">"Usar seleção do sistema (padrão)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Padrão"</item>
+ <item msgid="3214516120190965356">"Usar seleção do sistema (padrão)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Padrão"</item>
+ <item msgid="2684127272582591429">"Usar seleção do sistema (padrão)"</item>
<item msgid="5618929009984956469">"16 bits/amostra"</item>
<item msgid="3412640499234627248">"24 bits/amostra"</item>
<item msgid="121583001492929387">"32 bits/amostra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Padrão"</item>
+ <item msgid="1081159789834584363">"Usar seleção do sistema (padrão)"</item>
<item msgid="4726688794884191540">"16 bits/amostra"</item>
<item msgid="305344756485516870">"24 bits/amostra"</item>
<item msgid="244568657919675099">"32 bits/amostra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Padrão"</item>
+ <item msgid="5226878858503393706">"Usar seleção do sistema (padrão)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Padrão"</item>
+ <item msgid="4118561796005528173">"Usar seleção do sistema (padrão)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Qualidade de som preferencial (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Padrão (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Conexão preferencial (330 kbps/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Otimizar para qualidade de áudio (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Qualidade de áudio e de conexão balanceada (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Otimizar para qualidade da conexão (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Qualidade de som preferencial (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Padrão (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Conexão preferencial (330 kbps/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Otimizar para qualidade de áudio"</item>
+ <item msgid="4327143584633311908">"Qualidade de áudio e de conexão balanceada"</item>
+ <item msgid="6155648878105378550">"Otimizar para qualidade de conexão"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Desativado"</item>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 1c34317..dcbac24 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Dados da rede celular sempre ativos"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Desativar volume absoluto"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Selecione o codec Bluetooth A2DP preferencial"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Selecionar codec de áudio Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Taxa de amostra do áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Selecione a taxa de amostra do codec Bluetooth A2DP preferencial"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Selecionar codec de áudio Bluetooth:\ntaxa de amostragem"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits por amostra do áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Selecione os bits por amostra do codec Bluetooth A2DP preferencial"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Selecionar codec de áudio Bluetooth:\nbits por amostra"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Modo de canal de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Selecione o modo de canal do codec Bluetooth A2DP preferencial"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Qualidade de reprodução LDAC de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Selecione a qualidade de reprodução LDAC do codec Bluetooth A2DP preferencial"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Selecionar codec de áudio Bluetooth:\nmodo de canal"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec de áudio Bluetooth LDAC: qualidade de reprodução"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecionar codec de áudio Bluetooth LDAC:\nqualidade de reprodução"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opções de certificação de Display sem fio"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nível de registro do Wi-Fi; mostrar conforme o RSSI de SSID na Seleção de Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Quando ativada, o Wi-Fi será mais agressivo em transferir a conexão de dados para celular, quando o sinal de Wi-Fi estiver fraco"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Ajuda e feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Digite a senha para redef. p/ configuração original em modo demo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Próxima"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Senha necessária"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/arrays.xml b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
index 7332f8a..048c727 100644
--- a/packages/SettingsLib/res/values-pt-rPT/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Utilizar sempre a verificação HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Predefinição"</item>
+ <item msgid="7065842274271279580">"Utilizar seleção do sistema (predef.)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Predefinição"</item>
+ <item msgid="5062108632402595000">"Utilizar seleção do sistema (predef.)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Predefinição"</item>
+ <item msgid="3093023430402746802">"Utilizar seleção do sistema (predef.)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Predefinição"</item>
+ <item msgid="3214516120190965356">"Utilizar seleção do sistema (predef.)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Predefinição"</item>
+ <item msgid="2684127272582591429">"Utilizar seleção do sistema (predef.)"</item>
<item msgid="5618929009984956469">"16 bits/amostra"</item>
<item msgid="3412640499234627248">"24 bits/amostra"</item>
<item msgid="121583001492929387">"32 bits/amostra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Predefinição"</item>
+ <item msgid="1081159789834584363">"Utilizar seleção do sistema (predef.)"</item>
<item msgid="4726688794884191540">"16 bits/amostra"</item>
<item msgid="305344756485516870">"24 bits/amostra"</item>
<item msgid="244568657919675099">"32 bits/amostra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Predefinição"</item>
+ <item msgid="5226878858503393706">"Utilizar seleção do sistema (predef.)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Predefinição"</item>
+ <item msgid="4118561796005528173">"Utilizar seleção do sistema (predef.)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Qual. som preferida (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Padrão (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Ligação preferida (330 kbps/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Otimizar para qualidade de áudio (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Qualidade de áudio e de ligação equilibrada (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Otimizar para qualidade de ligação (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Qual. som preferida (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Padrão (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Ligação preferida (330 kbps/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Otimizar para qualidade de áudio"</item>
+ <item msgid="4327143584633311908">"Qualidade de áudio e de ligação equilibrada"</item>
+ <item msgid="6155648878105378550">"Otimizar para qualidade de ligação"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Desativado"</item>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index c519b61..3163ce4 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Dados móveis sempre ativados"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Desativar volume absoluto"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Selecionar Codec A2DP Bluetooth preferido"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Selecionar codec de áudio Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Taxa de amostragem de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Selecionar Taxa de amostragem de codec A2DP Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Selecionar codec de áudio Bluetooth:\nTaxa de amostragem"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits por amostra de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Selecionar Bits por amostra de codec A2DP Bluetooth preferido"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Selecionar codec de áudio Bluetooth:\nBits por amostra"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Modo de canal áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Selecionar Modo de canal de codec A2DP Bluetooth preferido"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Qualidade de reprodução LDAC de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Selecionar Qualidade de reprodução LDAC de codec A2DP Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Selecionar codec de áudio de Bluetooth:\nModo de canal"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC de áudio Bluetooth: qualidade de reprodução"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecionar codec LDAC de áudio Bluetooth:\nQualidade de reprodução"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmissão em fluxo contínuo: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opções da certificação de display sem fios"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nível de reg. de Wi-Fi, mostrar por RSSI de SSID no Selec. de Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Se estiver ativado, o Wi-Fi será mais agressivo ao transmitir a lig. de dados p/ a rede móvel quando o sinal Wi-Fi estiver fraco"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Ajuda e comentários"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Intr. palavra-passe p/ repos. fábrica no modo demo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Próximo"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Palavra-passe obrigatória"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt/arrays.xml b/packages/SettingsLib/res/values-pt/arrays.xml
index 4d1f3bb..e238905 100644
--- a/packages/SettingsLib/res/values-pt/arrays.xml
+++ b/packages/SettingsLib/res/values-pt/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Sempre usar a verificação HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Padrão"</item>
+ <item msgid="7065842274271279580">"Usar seleção do sistema (padrão)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Padrão"</item>
+ <item msgid="5062108632402595000">"Usar seleção do sistema (padrão)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Padrão"</item>
+ <item msgid="3093023430402746802">"Usar seleção do sistema (padrão)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Padrão"</item>
+ <item msgid="3214516120190965356">"Usar seleção do sistema (padrão)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Padrão"</item>
+ <item msgid="2684127272582591429">"Usar seleção do sistema (padrão)"</item>
<item msgid="5618929009984956469">"16 bits/amostra"</item>
<item msgid="3412640499234627248">"24 bits/amostra"</item>
<item msgid="121583001492929387">"32 bits/amostra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Padrão"</item>
+ <item msgid="1081159789834584363">"Usar seleção do sistema (padrão)"</item>
<item msgid="4726688794884191540">"16 bits/amostra"</item>
<item msgid="305344756485516870">"24 bits/amostra"</item>
<item msgid="244568657919675099">"32 bits/amostra"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Padrão"</item>
+ <item msgid="5226878858503393706">"Usar seleção do sistema (padrão)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Padrão"</item>
+ <item msgid="4118561796005528173">"Usar seleção do sistema (padrão)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Estéreo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Qualidade de som preferencial (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Padrão (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Conexão preferencial (330 kbps/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Otimizar para qualidade de áudio (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Qualidade de áudio e de conexão balanceada (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Otimizar para qualidade da conexão (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Qualidade de som preferencial (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Padrão (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Conexão preferencial (330 kbps/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Otimizar para qualidade de áudio"</item>
+ <item msgid="4327143584633311908">"Qualidade de áudio e de conexão balanceada"</item>
+ <item msgid="6155648878105378550">"Otimizar para qualidade de conexão"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Desativado"</item>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 1c34317..dcbac24 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Dados da rede celular sempre ativos"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Desativar volume absoluto"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Selecione o codec Bluetooth A2DP preferencial"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Selecionar codec de áudio Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Taxa de amostra do áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Selecione a taxa de amostra do codec Bluetooth A2DP preferencial"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Selecionar codec de áudio Bluetooth:\ntaxa de amostragem"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits por amostra do áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Selecione os bits por amostra do codec Bluetooth A2DP preferencial"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Selecionar codec de áudio Bluetooth:\nbits por amostra"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Modo de canal de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Selecione o modo de canal do codec Bluetooth A2DP preferencial"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Qualidade de reprodução LDAC de áudio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Selecione a qualidade de reprodução LDAC do codec Bluetooth A2DP preferencial"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Selecionar codec de áudio Bluetooth:\nmodo de canal"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec de áudio Bluetooth LDAC: qualidade de reprodução"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecionar codec de áudio Bluetooth LDAC:\nqualidade de reprodução"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opções de certificação de Display sem fio"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nível de registro do Wi-Fi; mostrar conforme o RSSI de SSID na Seleção de Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Quando ativada, o Wi-Fi será mais agressivo em transferir a conexão de dados para celular, quando o sinal de Wi-Fi estiver fraco"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Ajuda e feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Digite a senha para redef. p/ configuração original em modo demo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Próxima"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Senha necessária"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ro/arrays.xml b/packages/SettingsLib/res/values-ro/arrays.xml
index a6a0757..e801213 100644
--- a/packages/SettingsLib/res/values-ro/arrays.xml
+++ b/packages/SettingsLib/res/values-ro/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Utilizează întotdeauna verificarea HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Prestabilit"</item>
+ <item msgid="7065842274271279580">"Folosiți selectarea sist. (prestabilit)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Prestabilit"</item>
+ <item msgid="5062108632402595000">"Folosiți selectarea sist. (prestabilit)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Prestabilit"</item>
+ <item msgid="3093023430402746802">"Folosiți selectarea sist. (prestabilit)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Prestabilit"</item>
+ <item msgid="3214516120190965356">"Folosiți selectarea sist. (prestabilit)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Prestabilit"</item>
+ <item msgid="2684127272582591429">"Folosiți selectarea sist. (prestabilit)"</item>
<item msgid="5618929009984956469">"16 biți/eșantion"</item>
<item msgid="3412640499234627248">"24 biți/eșantion"</item>
<item msgid="121583001492929387">"32 biți/eșantion"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Prestabilit"</item>
+ <item msgid="1081159789834584363">"Folosiți selectarea sist. (prestabilit)"</item>
<item msgid="4726688794884191540">"16 biți/eșantion"</item>
<item msgid="305344756485516870">"24 biți/eșantion"</item>
<item msgid="244568657919675099">"32 biți/eșantion"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Prestabilit"</item>
+ <item msgid="5226878858503393706">"Folosiți selectarea sist. (prestabilit)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Prestabilit"</item>
+ <item msgid="4118561796005528173">"Folosiți selectarea sist. (prestabilit)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Cal. pref. sunet (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Standard (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Conexiune preferată (330 kbps/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Optimizați pentru calitatea audio (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Calitatea audio și a conexiunii echilibrată (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Optimizați pentru calitatea conexiunii (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Cal. pref. sunet (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Standard (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Conexiune preferată (330 kbps/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Optimizați pentru calitatea audio"</item>
+ <item msgid="4327143584633311908">"Calitatea audio și a conexiunii echilibrată"</item>
+ <item msgid="6155648878105378550">"Optimizați pentru calitatea conexiunii"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Dezactivată"</item>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 512ba3a..bae40ef 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Conexiunea de date mobile este întotdeauna activată"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Dezactivați volumul absolut"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Selectați codecul Bluetooth A2DP preferat"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Selectați codecul audio Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Rată de eșantionare audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Selectați rata de eșantionare codec Bluetooth A2DP preferată"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Selectați codecul audio Bluetooth:\nrată de eșantionare"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Biți audio Bluetooth per eșantion"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Selectați biții codecului Bluetooth A2DP preferați per eșantion"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Selectați codecul audio Bluetooth:\nbiți per eșantion"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Modul canal audio Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Selectați modul canal codec pentru Bluetooth A2DP preferat"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Calitatea redării Bluetooth audio LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Selectați calitatea redării LDAC a codecului pentru Bluetooth A2DP preferată"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Selectați codecul audio Bluetooth:\nmodul canal"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codecul LDAC audio pentru Bluetooth: calitatea redării"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selectați codecul LDAC audio pentru Bluetooth:\ncalitatea redării"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmitere în flux: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afișați opțiunile pentru certificarea Ecran wireless"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Măriți niv. de înr. prin Wi‑Fi, afișați în fcț. de SSID RSSI în Selectorul Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Când este activată, funcția Wi-Fi va fi mai agresivă la predarea conexiunii de date către mobil când semnalul Wi-Fi este slab"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Ajutor și feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Meniu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Intr. par. pt. a rev. set. fab. în demo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Înainte"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Trebuie să introduceți o parolă"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ru/arrays.xml b/packages/SettingsLib/res/values-ru/arrays.xml
index 1080b01..6cc82a2 100644
--- a/packages/SettingsLib/res/values-ru/arrays.xml
+++ b/packages/SettingsLib/res/values-ru/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Всегда использовать проверку HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"По умолчанию"</item>
+ <item msgid="7065842274271279580">"Выбор системы (по умолчанию)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"По умолчанию"</item>
+ <item msgid="5062108632402595000">"Выбор системы (по умолчанию)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"По умолчанию"</item>
+ <item msgid="3093023430402746802">"Выбор системы (по умолчанию)"</item>
<item msgid="8895532488906185219">"44,1 кГц"</item>
<item msgid="2909915718994807056">"48 кГц"</item>
<item msgid="3347287377354164611">"88,2 кГц"</item>
<item msgid="1234212100239985373">"96 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"По умолчанию"</item>
+ <item msgid="3214516120190965356">"Выбор системы (по умолчанию)"</item>
<item msgid="4482862757811638365">"44,1 кГц"</item>
<item msgid="354495328188724404">"48 кГц"</item>
<item msgid="7329816882213695083">"88,2 кГц"</item>
<item msgid="6967397666254430476">"96 кГц"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"По умолчанию"</item>
+ <item msgid="2684127272582591429">"Выбор системы (по умолчанию)"</item>
<item msgid="5618929009984956469">"16 бит/отсчет"</item>
<item msgid="3412640499234627248">"24 бит/отсчет"</item>
<item msgid="121583001492929387">"32 бит/отсчет"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"По умолчанию"</item>
+ <item msgid="1081159789834584363">"Выбор системы (по умолчанию)"</item>
<item msgid="4726688794884191540">"16 бит/отсчет"</item>
<item msgid="305344756485516870">"24 бит/отсчет"</item>
<item msgid="244568657919675099">"32 бит/отсчет"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"По умолчанию"</item>
+ <item msgid="5226878858503393706">"Выбор системы (по умолчанию)"</item>
<item msgid="4106832974775067314">"Моно"</item>
<item msgid="5571632958424639155">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"По умолчанию"</item>
+ <item msgid="4118561796005528173">"Выбор системы (по умолчанию)"</item>
<item msgid="8900559293912978337">"Моно"</item>
<item msgid="8883739882299884241">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Предпочтит. качество звука (990 кбит/с/909 кбит/с)"</item>
- <item msgid="138837449700903545">"Стандартное (660 кбит/с/606 кбит/с)"</item>
- <item msgid="4777177307869441982">"Предпочтит. соединение (330 кбит/с/303 кбит/с)"</item>
+ <item msgid="3411577996960199959">"Оптимизация под качество аудио (990/909 кбит/с)"</item>
+ <item msgid="2921767058740704969">"Баланс качества аудио и скорости подключения (660/606 кбит/с)"</item>
+ <item msgid="3682554248829489641">"Оптимизация под скорость подключения (330/303 кбит/с)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Предпочтит. качество звука (990 кбит/с/909 кбит/с)"</item>
- <item msgid="9091111147684472529">"Стандартное (660 кбит/с/606 кбит/с)"</item>
- <item msgid="3367904477834831032">"Предпочтит. соединение (330 кбит/с/303 кбит/с)"</item>
+ <item msgid="7668834469173465015">"Оптимизация под качество аудио"</item>
+ <item msgid="4327143584633311908">"Баланс качества аудио и скорости подключения"</item>
+ <item msgid="6155648878105378550">"Оптимизация под скорость подключения"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Выкл."</item>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index bad2158..df69f91 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Не отключать передачу данных"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Отключить абсолютный уровень громкости"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Аудиокодек для передачи через Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Выберите кодек A2DP для передачи через Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Аудиокодек для передачи через Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Частота дискретизации при передаче через Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Выберите частоту дискретизации для кодека A2DP (через Bluetooth)"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Аудиокодек для передачи через Bluetooth:\nчастота дискретизации"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Глубина кодирования звука при передаче через Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Выберите глубину кодирования звука для кодека A2DP (через Bluetooth)"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Аудиокодек для передачи через Bluetooth:\nбитов на образец"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Режим аудиоканала Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Выберите режим аудиоканала для кодека A2DP (через Bluetooth)"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Качество воспроизведения с кодеком A2DP (через Bluetooth)"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Выберите качество воспроизведения с кодеком A2DP (через Bluetooth)"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Аудиокодек для передачи через Bluetooth:\nрежим канала"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Качество воспроизведения с аудиокодеком LDAC (через Bluetooth)"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Аудиокодек LDAC:\nкачество воспроизведения"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Потоковая передача: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Показывать параметры сертификации беспроводных мониторов"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"При выборе Wi‑Fi указывать в журнале RSSI для каждого SSID"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Принудительно переключаться на мобильную сеть, если сигнал Wi-Fi слабый"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Справка/отзыв"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Меню"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Чтобы сбросить настройки в деморежиме, введите пароль."</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Далее"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Требуется пароль"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-si/arrays.xml b/packages/SettingsLib/res/values-si/arrays.xml
index afc125f..c9a5d12 100644
--- a/packages/SettingsLib/res/values-si/arrays.xml
+++ b/packages/SettingsLib/res/values-si/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"DRM අන්තර්ගත සඳහා පමණක් HDCP පරික්ෂාව භාවිතා කරන්න"</item>
<item msgid="45075631231212732">"සැමවිටම HDCP පිරික්සුම භාවිතා කරන්න"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"පෙරනිමි"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"පෙරනිමි"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"පෙරනිමි"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"පෙරනිමි"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"පෙරනිමි"</item>
- <item msgid="5618929009984956469">"බිටු 16/නියැදිය"</item>
- <item msgid="3412640499234627248">"බිටු 24/නියැදිය"</item>
- <item msgid="121583001492929387">"බිටු 32/නියැදිය"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"පෙරනිමි"</item>
- <item msgid="4726688794884191540">"බිටු 16/නියැදිය"</item>
- <item msgid="305344756485516870">"බිටු 24/නියැදිය"</item>
- <item msgid="244568657919675099">"බිටු 32/නියැදිය"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"පෙරනිමි"</item>
- <item msgid="4106832974775067314">"ඒකල"</item>
- <item msgid="5571632958424639155">"ස්ටීරියෝ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"පෙරනිමි"</item>
- <item msgid="8900559293912978337">"ඒකල"</item>
- <item msgid="8883739882299884241">"ස්ටීරියෝ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"වඩා කැමති හඬ ගුණත්වය (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"සම්මත (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"වඩා කැමති සබැඳුම (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"වඩා කැමති හඬ ගුණත්වය (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"සම්මත (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"වඩා කැමති සබැඳුම (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ක්රියාවිරහිතය"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index fb6545b..bfe1e9b 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"සෙලියුලර් දත්ත සැමවිට ක්රියාකාරීය"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"නිරපේක්ෂ හඩ පරිමාව අබල කරන්න"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"බ්ලූටූත් ශ්රව්ය Codec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"වඩා කැමති බ්ලූටූත් A2DP Codec තෝරන්න"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"බ්ලූටූත් ශ්රව්ය නියැදි අනුපාතය"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"වඩා කැමති බ්ලූටූත් A2DP Codec නියැදි අනුපාතය තෝරන්න"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"නියැදියකට බ්ලූටූත් ශ්රව්ය බිටු"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"නියැදියකට වඩා කැමති බ්ලූටූත් A2DP Codec බිටු තෝරන්න"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"බ්ලූටූත් ශ්රව්ය නාලිකා ප්රකාරය"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"වඩා කැමති බ්ලූටූත් A2DP Codec නාලිකා ප්රකාරය තෝරන්න"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"බ්ලූටූත් ශ්රව්ය LDAC පසුධාවන ගුණත්වය"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"වඩා කැමති බ්ලූටූත් A2DP Codec පසුධාවන ගුණත්වය තෝරන්න"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"නොරැහැන් සංදර්ශක සහතිකය සඳහා විකල්ප පෙන්වන්න"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ලොග් මට්ටම වැඩි කරන්න, Wi‑Fi තෝරනයෙහි SSID RSSI අනුව පෙන්වන්න"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"සබල විට Wi‑Fi සිග්නලය අඩු විට Wi‑Fi දත්ත සම්බන්ධතාවය සෙලියුලර් වෙත භාර දීමට වඩා ආක්රමණික වේ"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"උදව් සහ ප්රතිපෝෂණ"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"මෙනුව"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"ආදර්ශන ප්රකාර කර්මාන්තශාලා යළි සැකසීමට මුරපදය ඇ. ක."</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"ඊළඟ"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"මුරපදය අවශ්යයි"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sk/arrays.xml b/packages/SettingsLib/res/values-sk/arrays.xml
index 91b0351..178388e 100644
--- a/packages/SettingsLib/res/values-sk/arrays.xml
+++ b/packages/SettingsLib/res/values-sk/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Vždy používať kontrolu HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Predvolené"</item>
+ <item msgid="7065842274271279580">"Použiť voľbu systému (predvolené)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Predvolené"</item>
+ <item msgid="5062108632402595000">"Použiť voľbu systému (predvolené)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Predvolené"</item>
+ <item msgid="3093023430402746802">"Použiť voľbu systému (predvolené)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Predvolené"</item>
+ <item msgid="3214516120190965356">"Použiť voľbu systému (predvolené)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Predvolené"</item>
+ <item msgid="2684127272582591429">"Použiť voľbu systému (predvolené)"</item>
<item msgid="5618929009984956469">"16 bitov na vzorku"</item>
<item msgid="3412640499234627248">"24 bitov na vzorku"</item>
<item msgid="121583001492929387">"32 bitov na vzorku"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Predvolené"</item>
+ <item msgid="1081159789834584363">"Použiť voľbu systému (predvolené)"</item>
<item msgid="4726688794884191540">"16 bitov na vzorku"</item>
<item msgid="305344756485516870">"24 bitov na vzorku"</item>
<item msgid="244568657919675099">"32 bitov na vzorku"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Predvolené"</item>
+ <item msgid="5226878858503393706">"Použiť voľbu systému (predvolené)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Predvolené"</item>
+ <item msgid="4118561796005528173">"Použiť voľbu systému (predvolené)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Preferovaná kvalita zvuku (990/909 kb/s)"</item>
- <item msgid="138837449700903545">"Štandardné (660/606 kb/s)"</item>
- <item msgid="4777177307869441982">"Preferované pripojenie (330/303 kb/s)"</item>
+ <item msgid="3411577996960199959">"Optimalizovať na kvalitu zvuku (990/909 kb/s)"</item>
+ <item msgid="2921767058740704969">"Vyrovnaná kvalita zvuku a pripojenia (660/606 kb/s)"</item>
+ <item msgid="3682554248829489641">"Optimalizovať na kvalitu pripojenia (330/303 kb/s)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Preferovaná kvalita zvuku (990/909 kb/s)"</item>
- <item msgid="9091111147684472529">"Štandardné (660/606 kb/s)"</item>
- <item msgid="3367904477834831032">"Preferované pripojenie (330/303 kb/s)"</item>
+ <item msgid="7668834469173465015">"Optimalizovať na kvalitu zvuku"</item>
+ <item msgid="4327143584633311908">"Vyrovnaná kvalita zvuku a pripojenia"</item>
+ <item msgid="6155648878105378550">"Optimalizovať na kvalitu pripojenia"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Vypnuté"</item>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 65de397..ce1315a 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobilné dáta vždy aktívne"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Zakázať absolútnu hlasitosť"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Audio – kodek"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Vyberte preferovaný kodek Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Vybrať kodek Bluetooth Audio"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth Audio – vzorkovacia frekvencia"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Zvoľte preferovanú vzorkovaciu frekvenciu kodeku Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Vybrať kodek Bluetooth Audio:\nVzorkovacia frekvencia"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth Audio – počet bitov na vzorku"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Zvoľte preferovaný počet bitov na vzorky kodeku Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Vybrať kodek Bluetooth Audio:\nPočet bitov na vzorku"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth Audio – režim kanála"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Zvoľte preferovaný režim kanála kodeku Bluetooth A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Kvalita prehrávania LDAC Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Zvoľte preferovanú kvalitu prehrávania LDAC kodeku Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Vybrať kodek Bluetooth Audio:\nRežim kanála"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek LDAC Bluetooth Audio: Kvalita prehrávania"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Vybrať kodek LDAC Bluetooth Audio:\nKvalita prehrávania"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamovanie: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Zobraziť možnosti certifikácie bezdrôtového zobrazenia"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Zvýšiť úroveň denníkov Wi-Fi, zobrazovať podľa SSID RSSI pri výbere siete Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Keď túto možnosť zapnete, Wi-Fi bude agresívnejšie odovzdávať dát. pripoj. na mob. sieť vtedy, keď bude slabý signál Wi-Fi"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Pomocník a spätná väzba"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Ponuka"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Zadajte heslo na obnovenie továrenských nastavení v režime ukážky"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Ďalej"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Vyžaduje sa heslo"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sl/arrays.xml b/packages/SettingsLib/res/values-sl/arrays.xml
index 6b6e0c8..d717308 100644
--- a/packages/SettingsLib/res/values-sl/arrays.xml
+++ b/packages/SettingsLib/res/values-sl/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Preverjanje HDCP uporabi samo za vsebino DRM"</item>
<item msgid="45075631231212732">"Vedno uporabi preverjanje HDCP"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Privzeto"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Privzeto"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Privzeto"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Privzeto"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Privzeto"</item>
- <item msgid="5618929009984956469">"16 bitov/vzorec"</item>
- <item msgid="3412640499234627248">"24 bitov/vzorec"</item>
- <item msgid="121583001492929387">"32 bitov/vzorec"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Privzeto"</item>
- <item msgid="4726688794884191540">"16 bitov/vzorec"</item>
- <item msgid="305344756485516870">"24 bitov/vzorec"</item>
- <item msgid="244568657919675099">"32 bitov/vzorec"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Privzeto"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Privzeto"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Prednostna kakovost zvoka (990/909 kb/s)"</item>
- <item msgid="138837449700903545">"Standardno (660/606 kb/s)"</item>
- <item msgid="4777177307869441982">"Prednostna povezava (330/303 kb/s)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Prednostna kakovost zvoka (990/909 kb/s)"</item>
- <item msgid="9091111147684472529">"Standardno (660/606 kb/s)"</item>
- <item msgid="3367904477834831032">"Prednostna povezava (330/303 kb/s)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Izklopljeno"</item>
<item msgid="1593289376502312923">"64 K"</item>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index ef21240..eb80a30 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Prenos podatkov v mobilnih omrežjih je vedno aktiven"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Onemogočanje absolutnega praga glasnosti"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Zvočni kodek za Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Izberite prednostni kodek A2DP za Bluetooth"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Hitrost vzorčenja zvoka prek Bluetootha"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Izberite prednostno hitrost vzorčenja za kodek A2DP za Bluetooth"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Število bitov na vzorec za zvok prek Bluetootha"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Izberite prednostno število bitov na vzorec za kodek A2DP za Bluetooth"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Način zvočnega kanala prek Bluetootha"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Izberite prednostni način kanala za kodek A2DP za Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Kakovost predvajanja LDAC za zvok prek Bluetootha"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Izberite prednostno kakovost predvajanja LDAC za kodek A2DP za Bluetooth"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaži možnosti za potrdilo brezžičnega zaslona"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povečaj raven zapis. dnev. za Wi-Fi; v izbir. Wi‑Fi-ja pokaži glede na SSID RSSI"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Če je ta možnost omogočena, Wi-Fi odločneje preda podatkovno povezavo mobilnemu omrežju, ko je signal Wi-Fi šibek"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Pomoč in povratne informacije"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Meni"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Geslo za tovar. nast. v predstav. načinu"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Naprej"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Vnesite geslo"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sq/arrays.xml b/packages/SettingsLib/res/values-sq/arrays.xml
index e52b9fa..29f27a4 100644
--- a/packages/SettingsLib/res/values-sq/arrays.xml
+++ b/packages/SettingsLib/res/values-sq/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Përdor gjithmonë kontrollin e HDCP-së"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"I parazgjedhur"</item>
+ <item msgid="7065842274271279580">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"I parazgjedhur"</item>
+ <item msgid="5062108632402595000">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"I parazgjedhur"</item>
+ <item msgid="3093023430402746802">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"I parazgjedhur"</item>
+ <item msgid="3214516120190965356">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"I parazgjedhur"</item>
+ <item msgid="2684127272582591429">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
<item msgid="5618929009984956469">"16 bite/shembull"</item>
<item msgid="3412640499234627248">"24 bite/shembull"</item>
<item msgid="121583001492929387">"32 bite/shembull"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"I parazgjedhur"</item>
+ <item msgid="1081159789834584363">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
<item msgid="4726688794884191540">"16 bite/shembull"</item>
<item msgid="305344756485516870">"24 bite/shembull"</item>
<item msgid="244568657919675099">"32 bite/shembull"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"I parazgjedhur"</item>
+ <item msgid="5226878858503393706">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"I parazgjedhur"</item>
+ <item msgid="4118561796005528173">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Cilësia e preferuar e zërit (990 kbps/909 kbps)"</item>
- <item msgid="138837449700903545">"Standarde (660 kbps/606 kbps)"</item>
- <item msgid="4777177307869441982">"Lidhja e preferuar (330 kbps/303 kbps)"</item>
+ <item msgid="3411577996960199959">"Optimizoje për cilësi audioje (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"Cilësi e balancuar e audios dhe e lidhjes (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"Optimizoje për cilësi lidhjeje (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Cilësia e preferuar e zërit (990 kbps/909 kbps)"</item>
- <item msgid="9091111147684472529">"Standarde (660 kbps/606 kbps)"</item>
- <item msgid="3367904477834831032">"Lidhja e preferuar (330 kbps/303 kbps)"</item>
+ <item msgid="7668834469173465015">"Optimizoje për cilësi audioje"</item>
+ <item msgid="4327143584633311908">"Cilësi e balancuar e audios dhe e lidhjes"</item>
+ <item msgid="6155648878105378550">"Optimizoje për cilësi lidhjeje"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Joaktiv"</item>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 56c36c0..7163852 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Të dhënat celulare gjithmonë aktive"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Çaktivizo volumin absolut"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Kodeku Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Zgjidh kodekun e preferuar Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Zgjidh kodekun e audios së Bluetooth-it"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Shpejtësia e shembullit të Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Zgjidh shpejtësinë e preferuar të shembullit të kodekut Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Zgjidh kodekun e audios së Bluetooth-it:\nShpejtësia e shembullit"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bite për shembull Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Zgjidh bite për shembull të preferuar të kodekut Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Zgjidh kodekun e audios së Bluetooth-it:\nBite për shembull"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Regjimi i kanalit Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Zgjidh regjimi e preferuar të kanalit të kodekut Bluetooth A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Cilësia e luajtjes së Bluetooth Audio LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Zgjidh cilësinë e preferuar të luajtjes të kodekut Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Zgjidh kodekun e audios së Bluetooth-it:\nModaliteti i kanalit"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodeku LDAC i audios së Bluetooth-it: Cilësia e luajtjes"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Zgjidh kodekun LDAC të audios së Bluetooth-it:\nCilësia e luajtjes"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmetimi: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Shfaq opsionet për certifikimin e ekranit valor"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Rrit nivelin regjistrues të Wi‑Fi duke shfaqur SSID RSSI-në te Zgjedhësi i Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Kur ky funksion aktivizohet, Wi‑Fi bëhet më agresiv në kalimin e lidhjes së të dhënave te rrjeti celular, në rastet kur sinjali Wi‑Fi është i dobët"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Ndihma dhe komentet"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menyja"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Fut fjalëkalimin për të kryer rivendosje në gjendje fabrike në modalitetin e demonstrimit"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Përpara"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Kërkohet fjalëkalimi"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sr/arrays.xml b/packages/SettingsLib/res/values-sr/arrays.xml
index 8448c10..6956bb8 100644
--- a/packages/SettingsLib/res/values-sr/arrays.xml
+++ b/packages/SettingsLib/res/values-sr/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Увек користи HDCP проверу"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Подразумевано"</item>
+ <item msgid="7065842274271279580">"Користи избор система (подразумевано)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Подразумевано"</item>
+ <item msgid="5062108632402595000">"Користи избор система (подразумевано)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Подразумевано"</item>
+ <item msgid="3093023430402746802">"Користи избор система (подразумевано)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Подразумевано"</item>
+ <item msgid="3214516120190965356">"Користи избор система (подразумевано)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Подразумевано"</item>
+ <item msgid="2684127272582591429">"Користи избор система (подразумевано)"</item>
<item msgid="5618929009984956469">"16 битова по узорку"</item>
<item msgid="3412640499234627248">"24 бита по узорку"</item>
<item msgid="121583001492929387">"32 бита по узорку"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Подразумевано"</item>
+ <item msgid="1081159789834584363">"Користи избор система (подразумевано)"</item>
<item msgid="4726688794884191540">"16 битова по узорку"</item>
<item msgid="305344756485516870">"24 бита по узорку"</item>
<item msgid="244568657919675099">"32 бита по узорку"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Подразумевано"</item>
+ <item msgid="5226878858503393706">"Користи избор система (подразумевано)"</item>
<item msgid="4106832974775067314">"Моно"</item>
<item msgid="5571632958424639155">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Подразумевано"</item>
+ <item msgid="4118561796005528173">"Користи избор система (подразумевано)"</item>
<item msgid="8900559293912978337">"Моно"</item>
<item msgid="8883739882299884241">"Стерео"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Жељени квалитет звука (990 kb/s/909 kb/s)"</item>
- <item msgid="138837449700903545">"Стандардно (660 kb/s/606 kb/s)"</item>
- <item msgid="4777177307869441982">"Жељена веза (330 kb/s/303 kb/s)"</item>
+ <item msgid="3411577996960199959">"Оптимизуј за квалитет звука (990 kb/s/909 kb/s)"</item>
+ <item msgid="2921767058740704969">"Уједначен квалитет звука и везе (660 kb/s/606 kb/s)"</item>
+ <item msgid="3682554248829489641">"Оптимизуј за квалитет везе (330 kb/s/303 kb/s)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Жељени квалитет звука (990 kb/s/909 kb/s)"</item>
- <item msgid="9091111147684472529">"Стандардно (660 kb/s/606 kb/s)"</item>
- <item msgid="3367904477834831032">"Жељена веза (330 kb/s/303 kb/s)"</item>
+ <item msgid="7668834469173465015">"Оптимизује се за квалитет звука"</item>
+ <item msgid="4327143584633311908">"Уједначен квалитет звука и везе"</item>
+ <item msgid="6155648878105378550">"Оптимизује се за квалитет везе"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Искључено"</item>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index fd33eab..7ea3bf3 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Подаци за мобилне уређаје су увек активни"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Онемогући главно подешавање јачине звука"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth аудио кодек"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Изаберите жељени Bluetooth A2DP кодек"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Изаберите Bluetooth аудио кодек"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Брзина узорковања за Bluetooth аудио"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Изаберите жељену брзину узорковања за Bluetooth A2DP кодек"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Изаберите Bluetooth аудио кодек:\nбрзина узорковања"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Битова по узорку за Bluetooth аудио"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Изаберите жељени број битова по узорку за Bluetooth A2DP кодек"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Изаберите Bluetooth аудио кодек:\nбитова по узорку"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Режим канала за Bluetooth аудио"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Изаберите жељени режим канала за Bluetooth A2DP кодек"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Квалитет LDAC снимка за Bluetooth аудио"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Изаберите жељени квалитет LDAC снимка за Bluetooth A2DP кодек"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Изаберите Bluetooth аудио кодек:\nрежим канала"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth аудио кодек LDAC: квалитет репродукције"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Изаберите Bluetooth аудио кодек LDAC:\nквалитет репродукције"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Стримовање: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Приказ опција за сертификацију бежичног екрана"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Повећава ниво евидентирања за Wi‑Fi. Приказ по SSID RSSI-у у бирачу Wi‑Fi мреже"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Када се омогући, Wi‑Fi ће бити агресивнији при пребацивању мреже за пренос података на Мобилну, када је Wi‑Fi сигнал слаб"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Помоћ и повратне информације"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Мени"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Унесите лозинку да бисте обавили ресетовање на фабричка подешавања у режиму демонстрације"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Даље"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Потребна је лозинка"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sv/arrays.xml b/packages/SettingsLib/res/values-sv/arrays.xml
index e3e0103..fe395ac 100644
--- a/packages/SettingsLib/res/values-sv/arrays.xml
+++ b/packages/SettingsLib/res/values-sv/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Använd bara HDCP-kontroll för DRM-innehåll"</item>
<item msgid="45075631231212732">"Använd alltid HDCP-kontroll"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Standard"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Standard"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Standard"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Standard"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Standard"</item>
- <item msgid="5618929009984956469">"16 bitar/sampling"</item>
- <item msgid="3412640499234627248">"24 bitar/sampling"</item>
- <item msgid="121583001492929387">"32 bitar/sampling"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Standard"</item>
- <item msgid="4726688794884191540">"16 bitar/sampling"</item>
- <item msgid="305344756485516870">"24 bitar/sampling"</item>
- <item msgid="244568657919675099">"32 bitar/sampling"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Standard"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Standard"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Föredragen ljudkvalitet (990 kbit/s eller 909 kbit/s)"</item>
- <item msgid="138837449700903545">"Standard (660 kbit/s eller 606 kbit/s)"</item>
- <item msgid="4777177307869441982">"Föredragen anslutning (330 kbit/s eller 303 kbit/s)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Föredragen ljudkvalitet (990 kbit/s eller 909 kbit/s)"</item>
- <item msgid="9091111147684472529">"Standard (660 kbit/s eller 606 kbit/s)"</item>
- <item msgid="3367904477834831032">"Föredragen anslutning (330 kbit/s eller 303 kbit/s)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Av"</item>
<item msgid="1593289376502312923">"64 kB"</item>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 3b45fde..9e4ad29 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobildata alltid aktiverad"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Inaktivera Absolute volume"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Ljudkodek för Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Välj föredragen A2DP-kodek för Bluetooth"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Samplingsfrekvens för Bluetooth-ljud"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Välj föredragen samplingsfrekvens för A2DP-kodek för Bluetooth"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Antar bitar per sampling för Bluetooth-ljud"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Välj föredraget antal bitar per sampling för A2DP-kodek för Bluetooth"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Kanalläge för Bluetooth-ljud"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Välj föredraget kanalläge för A2DP-kodek för Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Uppspelningskvalitet för Bluetooth-ljud via LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Välj föredragen uppspelningskvalitet för A2DP-kodek för Bluetooth via LDAC"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Visa certifieringsalternativ för Wi-Fi-skärmdelning"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Öka loggningsnivån för Wi-Fi, visa per SSID RSSI i Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"När funktionen har aktiverats kommer dataanslutningen lämnas över från Wi-Fi till mobilen på ett aggressivare sätt när Wi-Fi-signalen är svag"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Hjälp och feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Meny"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Ange lösenord och utför fabriksåterställning i demoläge"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Nästa"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Lösenord krävs"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sw/arrays.xml b/packages/SettingsLib/res/values-sw/arrays.xml
index b4aeb1f..66d65c9 100644
--- a/packages/SettingsLib/res/values-sw/arrays.xml
+++ b/packages/SettingsLib/res/values-sw/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Kila wakati tumia ukakuaji wa HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Chaguo-msingi"</item>
+ <item msgid="7065842274271279580">"Tumia Uteuzi wa Mfumo (Chaguo-msingi)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Chaguo-msingi"</item>
+ <item msgid="5062108632402595000">"Tumia Uteuzi wa Mfumo (Chaguo-msingi)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Chaguo-msingi"</item>
+ <item msgid="3093023430402746802">"Tumia Uteuzi wa Mfumo (Chaguo-msingi)"</item>
<item msgid="8895532488906185219">"kHz 44.1"</item>
<item msgid="2909915718994807056">"kHz 48.0"</item>
<item msgid="3347287377354164611">"kHz 88.2"</item>
<item msgid="1234212100239985373">"kHz 96.0"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Chaguo-msingi"</item>
+ <item msgid="3214516120190965356">"Tumia Uteuzi wa Mfumo (Chaguo-msingi)"</item>
<item msgid="4482862757811638365">"kHz 44.1"</item>
<item msgid="354495328188724404">"kHz 48.0"</item>
<item msgid="7329816882213695083">"kHz 88.2"</item>
<item msgid="6967397666254430476">"kHz 96.0"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Chaguo-msingi"</item>
+ <item msgid="2684127272582591429">"Tumia Uteuzi wa Mfumo (Chaguo-msingi)"</item>
<item msgid="5618929009984956469">"Biti 16 kwa kila sampuli"</item>
<item msgid="3412640499234627248">"Biti 24 kwa kila sampuli"</item>
<item msgid="121583001492929387">"Biti 32 kwa kila sampuli"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Chaguo-msingi"</item>
+ <item msgid="1081159789834584363">"Tumia Uteuzi wa Mfumo (Chaguo-msingi)"</item>
<item msgid="4726688794884191540">"Biti 16 kwa kila sampuli"</item>
<item msgid="305344756485516870">"Biti 24 kwa kila sampuli"</item>
<item msgid="244568657919675099">"Biti 32 kwa kila sampuli"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Chaguo-msingi"</item>
+ <item msgid="5226878858503393706">"Tumia Uteuzi wa Mfumo (Chaguo-msingi)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Chaguo-msingi"</item>
+ <item msgid="4118561796005528173">"Tumia Uteuzi wa Mfumo (Chaguo-msingi)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Ubora wa sauti unaopendelewa (kbps990/kbps909)"</item>
- <item msgid="138837449700903545">"Ubora wa sauti wa kawaida (kbps660/kbps606)"</item>
- <item msgid="4777177307869441982">"Muunganisho unaopendelewa (kbps330/kbps303)"</item>
+ <item msgid="3411577996960199959">"Imarisha kwa ajili ya Ubora wa Sauti (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"Ubora wa Muunganisho na Sauti Umesawazishwa (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"Imarisha kwa ajili ya Ubora wa Muunganisho (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Ubora wa sauti unaopendelewa (kbps990/kbps909)"</item>
- <item msgid="9091111147684472529">"Ubora wa sauti wa kawaida (kbps660/kbps606)"</item>
- <item msgid="3367904477834831032">"Muunganisho unaopendelewa (kbps330/kbps303)"</item>
+ <item msgid="7668834469173465015">"Imarisha kwa ajili ya Ubora wa Sauti"</item>
+ <item msgid="4327143584633311908">"Ubora wa Muunganisho na Sauti Umesawazishwa"</item>
+ <item msgid="6155648878105378550">"Imarisha kwa ajili ya Ubora wa Muunganisho"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Imezimwa"</item>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index bd1b71a..9296e81 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Data ya kifaa cha mkononi inatumika kila wakati"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Zima sauti kamili"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Kodeki ya Sauti ya Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Chagua Kodeki Unayopendelea ya Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Chagua Kodeki ya Sauti ya Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Kiwango cha Sampuli ya Sauti ya Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Chagua Kiwango Unachopendelea cha Sampuli ya Kodeki ya Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Chagua Kodeki ya Sauti ya Bluetooth:\nKiwango cha Sampuli"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Biti za Sauti ya Bluetooth kwa Kila Sampuli"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Chagua Biti za Kodeki ya Bluetooth A2DP Unazopendelea kwa Kila Sampuli"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Chagua Kodeki ya Sauti ya Bluetooth:\nBiti kwa Kila Sampuli"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Hali ya Mkondo wa Sauti ya Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Chagua Hali ya Mkondo wa Kodeki ya Bluetooth A2DP Unayopendelea"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Ubora wa Kucheza LDAC ya Sauti ya Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Chagua Ubora Unaopendelea wa Kucheza LDAC ya Kodeki ya Bluetooth A2DP"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Chagua Kodeki ya Sauti ya Bluetooth:\nHali ya Kituo"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodeki ya LDAC ya Sauti ya Bluetooth: Ubora wa Kucheza"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Chagua Kodeki ya LDAC ya Sauti ya Bluetooth:\nUbora wa Kucheza"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Kutiririsha: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Onyesha chaguo za cheti cha kuonyesha pasiwaya"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Ongeza hatua ya uwekaji kumbukumbu ya Wi-Fi, onyesha kwa kila SSID RSSI kwenye Kichukuzi cha Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Ikiwashwa, Wifi itakabidhi kwa hima muunganisho wa data kwa mtandao wa Simu za Mkononi, mawimbi ya Wifi yanapokuwa hafifu"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Usaidizi na maoni"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menyu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Weka nenosiri ili urejeshe mipangilio ya kiwandani ikiwa katika hali ya onyesho."</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Inayofuata"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Nenosiri linahitajika"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ta/arrays.xml b/packages/SettingsLib/res/values-ta/arrays.xml
index 9b3b1d4..efbe16d 100644
--- a/packages/SettingsLib/res/values-ta/arrays.xml
+++ b/packages/SettingsLib/res/values-ta/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"DRM உள்ளடக்கத்திற்கு மட்டும் HDCP சோதனையைப் பயன்படுத்து"</item>
<item msgid="45075631231212732">"HDCP சரிபார்ப்பை எப்போதும் பயன்படுத்து"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"இயல்பு"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"இயல்பு"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"இயல்பு"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"இயல்பு"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"இயல்பு"</item>
- <item msgid="5618929009984956469">"16 பிட்கள்/சாம்பிள்"</item>
- <item msgid="3412640499234627248">"24 பிட்கள்/சாம்பிள்"</item>
- <item msgid="121583001492929387">"32 பிட்கள்/சாம்பிள்"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"இயல்பு"</item>
- <item msgid="4726688794884191540">"16 பிட்கள்/சாம்பிள்"</item>
- <item msgid="305344756485516870">"24 பிட்கள்/சாம்பிள்"</item>
- <item msgid="244568657919675099">"32 பிட்கள்/சாம்பிள்"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"இயல்பு"</item>
- <item msgid="4106832974775067314">"மோனோ"</item>
- <item msgid="5571632958424639155">"ஸ்டீரியோ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"இயல்பு"</item>
- <item msgid="8900559293912978337">"மோனோ"</item>
- <item msgid="8883739882299884241">"ஸ்டீரியோ"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"ஒலித் தரம் (பரிந்துரை) (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"நிலையானது (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"இணைப்பு (பரிந்துரை) (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"ஒலித் தரம் (பரிந்துரை) (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"நிலையானது (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"இணைப்பு (பரிந்துரை) (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"முடக்கு"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index c628d5e..790eb67 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"செல்லுலார் தரவு எப்போதும் இயக்கத்தில்"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"அப்சல்யூட் ஒலியளவு அம்சத்தை முடக்கு"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"புளூடூத் ஆடியோ கோடெக்"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"பரிந்துரைக்கப்படும் புளூடூத் A2DP கோடெக்கைத் தேர்ந்தெடுக்கவும்"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"புளூடூத் ஆடியோ சாம்பிள் ரேட்"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"பரிந்துரைக்கப்படும் புளூடூத் A2DP கோடெக் சாம்பிள் ரேட்டைத் தேர்ந்தெடுக்கவும்"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"புளூடூத் ஆடியோ பிட்கள்/சாம்பிள்"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"ஒரு சாம்பிளுக்குப் பரிந்துரைக்கப்படும் புளூடூத் A2DP கோடெக் பிட்களைத் தேர்ந்தெடுக்கவும்"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"புளூடூத் ஆடியோ சேனல் பயன்முறை"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"பரிந்துரைக்கப்படும் புளூடூத் A2DP கோடெக் சேனல் பயன்முறையைத் தேர்ந்தெடுக்கவும்"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"புளூடூத் ஆடியோ LDAC வீடியோவின் தரம்"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"பரிந்துரைக்கப்படும் புளூடூத் A2DP கோடெக் LDAC வீடியோவின் தரத்தைத் தேர்ந்தெடுக்கவும்"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"வயர்லெஸ் காட்சி சான்றுக்கான விருப்பங்களைக் காட்டு"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wifi நுழைவு அளவை அதிகரித்து, வைஃபை தேர்வியில் ஒவ்வொன்றிற்கும் SSID RSSI ஐ காட்டுக"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"இயக்கப்பட்டதும், வைஃபை சிக்னல் குறையும் போது, வைஃபை முழுமையாக ஒத்துழைக்காமல் இருப்பதால் செல்லுலாரின் தரவு இணைப்புக்கு மாறும்"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"உதவி & கருத்து"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"மெனு"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"டெமோ பயன்முறையில் ஆரம்பநிலை மீட்டமைவைச் செயல்படுத்த, கடவுச்சொல்லை உள்ளிடவும்"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"அடுத்து"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"கடவுச்சொல் தேவை"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml
index beb705a..44a16b1 100644
--- a/packages/SettingsLib/res/values-te/arrays.xml
+++ b/packages/SettingsLib/res/values-te/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"ఎప్పటికీ HDCP తనిఖీని ఉపయోగించు"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"డిఫాల్ట్"</item>
+ <item msgid="7065842274271279580">"సిస్టమ్ ఎంపికను ఉపయోగించండి (డిఫాల్ట్)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"డిఫాల్ట్"</item>
+ <item msgid="5062108632402595000">"సిస్టమ్ ఎంపికను ఉపయోగించండి (డిఫాల్ట్)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"డిఫాల్ట్"</item>
+ <item msgid="3093023430402746802">"సిస్టమ్ ఎంపికను ఉపయోగించండి (డిఫాల్ట్)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"డిఫాల్ట్"</item>
+ <item msgid="3214516120190965356">"సిస్టమ్ ఎంపికను ఉపయోగించండి (డిఫాల్ట్)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"డిఫాల్ట్"</item>
+ <item msgid="2684127272582591429">"సిస్టమ్ ఎంపికను ఉపయోగించండి (డిఫాల్ట్)"</item>
<item msgid="5618929009984956469">"16 బిట్లు/నమూనా"</item>
<item msgid="3412640499234627248">"24 బిట్లు/నమూనా"</item>
<item msgid="121583001492929387">"32 బిట్లు/నమూనా"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"డిఫాల్ట్"</item>
+ <item msgid="1081159789834584363">"సిస్టమ్ ఎంపికను ఉపయోగించండి (డిఫాల్ట్)"</item>
<item msgid="4726688794884191540">"16 బిట్లు/నమూనా"</item>
<item msgid="305344756485516870">"24 బిట్లు/నమూనా"</item>
<item msgid="244568657919675099">"32 బిట్లు/నమూనా"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"డిఫాల్ట్"</item>
+ <item msgid="5226878858503393706">"సిస్టమ్ ఎంపికను ఉపయోగించండి (డిఫాల్ట్)"</item>
<item msgid="4106832974775067314">"మోనో"</item>
<item msgid="5571632958424639155">"స్టీరియో"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"డిఫాల్ట్"</item>
+ <item msgid="4118561796005528173">"సిస్టమ్ ఎంపికను ఉపయోగించండి (డిఫాల్ట్)"</item>
<item msgid="8900559293912978337">"మోనో"</item>
<item msgid="8883739882299884241">"స్టీరియో"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"ప్రాధాన్య శబ్ద నాణ్యత (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"ప్రామాణికం (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"ప్రాధాన్య కనెక్షన్ (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"ఆడియో నాణ్యత కోసం అనుకూలీకరించండి (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"సమతుల్య ఆడియో మరియు కనెక్షన్ నాణ్యత (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"కనెక్షన్ నాణ్యత కోసం అనుకూలీకరించండి (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"ప్రాధాన్య శబ్ద నాణ్యత (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"ప్రామాణికం (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"ప్రాధాన్య కనెక్షన్ (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"ఆడియో నాణ్యత కోసం అనుకూలీకరించండి"</item>
+ <item msgid="4327143584633311908">"సమతుల్య ఆడియో మరియు కనెక్షన్ నాణ్యత"</item>
+ <item msgid="6155648878105378550">"కనెక్షన్ నాణ్యత కోసం అనుకూలీకరించండి"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ఆఫ్"</item>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 0d21371..8379dd8 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"ఎల్లప్పుడూ సెల్యులార్ డేటాను సక్రియంగా ఉంచు"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"సంపూర్ణ వాల్యూమ్ను నిలిపివేయి"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"బ్లూటూత్ ఆడియో కోడెక్"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"ప్రాధాన్య బ్లూటూత్ A2DP కోడెక్ను ఎంచుకోండి"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"బ్లూటూత్ ఆడియో కోడెక్ని ఎంచుకోండి"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"బ్లూటూత్ ఆడియో నమూనా రేట్"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"ప్రాధాన్య బ్లూటూత్ A2DP కోడెక్ నమూనా రేట్ను ఎంచుకోండి"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"బ్లూటూత్ ఆడియో కోడెక్ని ఎంచుకోండి:\nనమూనా రేటు"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"ఒక్కో నమూనాకు బ్లూటూత్ ఆడియో బిట్లు"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"ఒక్కో నమూనాకు ప్రాధాన్య బ్లూటూత్ A2DP కోడెక్ బిట్లను ఎంచుకోండి"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"బ్లూటూత్ ఆడియో కోడెక్ని ఎంచుకోండి:\nఒక్కో నమూనాలో బిట్లు"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"బ్లూటూత్ ఆడియో ఛానెల్ మోడ్"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"ప్రాధాన్య బ్లూటూత్ A2DP కోడెక్ ఛానెల్ మోడ్ను ఎంచుకోండి"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"బ్లూటూత్ ఆడియో LDAC ప్లేబ్యాక్ నాణ్యత"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"ప్రాధాన్య బ్లూటూత్ A2DP కోడెక్ LDAC ప్లేబ్యాక్ నాణ్యతను ఎంచుకోండి"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"బ్లూటూత్ ఆడియో కోడెక్ని ఎంచుకోండి:\nఛానెల్ మోడ్"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"బ్లూటూత్ ఆడియో LDAC కోడెక్: ప్లేబ్యాక్ నాణ్యత"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"బ్లూటూత్ ఆడియో LDAC కోడెక్ని ఎంచుకోండి:\nప్లేబ్యాక్ నాణ్యత"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ప్రసారం చేస్తోంది: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"వైర్లెస్ ప్రదర్శన ప్రమాణపత్రం కోసం ఎంపికలను చూపు"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ఎంపికలో SSID RSSI ప్రకారం చూపబడే Wi‑Fi లాగింగ్ స్థాయిని పెంచండి"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"ప్రారంభించబడినప్పుడు, Wi‑Fi సిగ్నల్ బలహీనంగా ఉంటే డేటా కనెక్షన్ను సెల్యులార్కి మార్చేలా Wi‑Fiపై మరింత తీవ్ర ఒత్తిడి కలుగుతుంది"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"సహాయం & అభిప్రాయం"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"మెను"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"డెమో మోడ్లో ఫ్యాక్టరీ రీసెట్ను నిర్వహించడానికి పాస్వర్డ్ను నమోదు చేయండి"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"తదుపరి"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"పాస్వర్డ్ అవసరం"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-th/arrays.xml b/packages/SettingsLib/res/values-th/arrays.xml
index 1032337..519a439 100644
--- a/packages/SettingsLib/res/values-th/arrays.xml
+++ b/packages/SettingsLib/res/values-th/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"ใช้การตรวจสอบ HDCP เสมอ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ค่าเริ่มต้น"</item>
+ <item msgid="7065842274271279580">"ใช้การเลือกระบบ (ค่าเริ่มต้น)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ค่าเริ่มต้น"</item>
+ <item msgid="5062108632402595000">"ใช้การเลือกระบบ (ค่าเริ่มต้น)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ค่าเริ่มต้น"</item>
+ <item msgid="3093023430402746802">"ใช้การเลือกระบบ (ค่าเริ่มต้น)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ค่าเริ่มต้น"</item>
+ <item msgid="3214516120190965356">"ใช้การเลือกระบบ (ค่าเริ่มต้น)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ค่าเริ่มต้น"</item>
+ <item msgid="2684127272582591429">"ใช้การเลือกระบบ (ค่าเริ่มต้น)"</item>
<item msgid="5618929009984956469">"16 บิต/ตัวอย่าง"</item>
<item msgid="3412640499234627248">"24 บิต/ตัวอย่าง"</item>
<item msgid="121583001492929387">"32 บิต/ตัวอย่าง"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ค่าเริ่มต้น"</item>
+ <item msgid="1081159789834584363">"ใช้การเลือกระบบ (ค่าเริ่มต้น)"</item>
<item msgid="4726688794884191540">"16 บิต/ตัวอย่าง"</item>
<item msgid="305344756485516870">"24 บิต/ตัวอย่าง"</item>
<item msgid="244568657919675099">"32 บิต/ตัวอย่าง"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ค่าเริ่มต้น"</item>
+ <item msgid="5226878858503393706">"ใช้การเลือกระบบ (ค่าเริ่มต้น)"</item>
<item msgid="4106832974775067314">"โมโน"</item>
<item msgid="5571632958424639155">"สเตอริโอ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ค่าเริ่มต้น"</item>
+ <item msgid="4118561796005528173">"ใช้การเลือกระบบ (ค่าเริ่มต้น)"</item>
<item msgid="8900559293912978337">"โมโน"</item>
<item msgid="8883739882299884241">"สเตอริโอ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"คุณภาพเสียงที่ต้องการ (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"มาตรฐาน (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"การเชื่อมต่อที่ต้องการ (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"เพิ่มประสิทธิภาพสำหรับคุณภาพเสียง (990 kbps/909 kbps)"</item>
+ <item msgid="2921767058740704969">"คุณภาพเสียงและการเชื่อมต่อที่สมดุล (660 kbps/606 kbps)"</item>
+ <item msgid="3682554248829489641">"เพิ่มประสิทธิภาพสำหรับคุณภาพการเชื่อมต่อ (330 kbps/303 kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"คุณภาพเสียงที่ต้องการ (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"มาตรฐาน (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"การเชื่อมต่อที่ต้องการ (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"เพิ่มประสิทธิภาพสำหรับคุณภาพเสียง"</item>
+ <item msgid="4327143584633311908">"คุณภาพเสียงและการเชื่อมต่อที่สมดุล"</item>
+ <item msgid="6155648878105378550">"เพิ่มประสิทธิภาพสำหรับคุณภาพการเชื่อมต่อ"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"ปิด"</item>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 60d91b5..8f9bd86 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"เปิดใช้ข้อมูลมือถือเสมอ"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"ปิดใช้การควบคุมระดับเสียงของอุปกรณ์อื่น"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"ตัวแปลงรหัสเสียงบลูทูธ"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"เลือกตัวแปลงรหัสบลูทูธ A2DP ที่ต้องการ"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"เลือกตัวแปลงรหัสเสียงบลูทูธ"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"อัตราตัวอย่างเสียงบลูทูธ"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"เลือกอัตราตัวอย่างของตัวแปลงรหัสบลูทูธ A2DP ที่ต้องการ"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"เลือกตัวแปลงรหัสเสียงบลูทูธ:\nอัตราการสุ่มตัวอย่าง"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"บิตต่อตัวอย่างของเสียงบลูทูธ"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"เลือกบิตต่อตัวอย่างของตัวแปลงรหัสบลูทูธ A2DP ที่ต้องการ"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"เลือกตัวแปลงรหัสเสียงบลูทูธ:\nบิตต่อตัวอย่าง"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"โหมดช่องสัญญาณเสียงบลูทูธ"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"เลือกโหมดช่องสัญญาณเสียงของตัวแปลงรหัสบลูทูธ A2DP ที่ต้องการ"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"คุณภาพการเล่นเสียงบลูทูธ LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"เลือกคุณภาพการเล่น LDAC ของตัวแปลงรหัสบลูทูธ A2DP ที่ต้องการ"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"เลือกตัวแปลงรหัสเสียงบลูทูธ:\nโหมดช่องสัญญาณ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC: คุณภาพการเล่น"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"เลือกตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC:\nคุณภาพการเล่น"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"สตรีมมิง: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"แสดงตัวเลือกสำหรับการรับรองการแสดงผล แบบไร้สาย"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"เพิ่มระดับการบันทึก Wi‑Fi แสดงต่อ SSID RSSI ในตัวเลือก Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"เมื่อเปิดใช้แล้ว Wi-Fi จะส่งผ่านการเชื่อมต่อข้อมูลไปยังเครือข่ายมือถือในทันทีที่พบสัญญาณ Wi-Fi อ่อน"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"ความช่วยเหลือและความคิดเห็น"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"เมนู"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"ป้อนรหัสผ่านเพื่อรีเซ็ตค่าในโหมดสาธิต"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"ถัดไป"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"ต้องป้อนรหัสผ่าน"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-tl/arrays.xml b/packages/SettingsLib/res/values-tl/arrays.xml
index c0e9843..c67be8a 100644
--- a/packages/SettingsLib/res/values-tl/arrays.xml
+++ b/packages/SettingsLib/res/values-tl/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Gamitin lang ang pagsusuring HDCP para sa nilalamang DRM"</item>
<item msgid="45075631231212732">"Palaging gumamit ng pagsusuring HDCP"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Default"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Default"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Default"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Default"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Default"</item>
- <item msgid="5618929009984956469">"16 bits/sample"</item>
- <item msgid="3412640499234627248">"24 bits/sample"</item>
- <item msgid="121583001492929387">"32 bits/sample"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Default"</item>
- <item msgid="4726688794884191540">"16 bits/sample"</item>
- <item msgid="305344756485516870">"24 bits/sample"</item>
- <item msgid="244568657919675099">"32 bits/sample"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Default"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Default"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Gustong kalidad tunog (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Karaniwan (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Gustong koneksyon (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Gustong kalidad tunog (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Karaniwan (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Gustong koneksyon (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"I-off"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 58781b2..682ee0ed 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Palaging aktibo ang cellular data"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"I-disable ang absolute volume"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Audio Codec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Piliin ang Gustong Bluetooth A2DP Codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Sample na Rate ng Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Piliin ang Gustong Sample na Rate ng Bluetooth A2DP Codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bits Per Sample ng Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Piliin ang Gustong Bits Per Sample ng Bluetooth A2DP Codec"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Channel Mode ng Bluetooth Audio"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Piliin ang Gustong Channel Mode ng Bluetooth A2DP Codec"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Playback Quality ng Bluetooth Audio LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Piliin ang Gustong Playback Quality ng Bluetooth A2DP Codec LDAC"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Ipakita ang mga opsyon para sa certification ng wireless display"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Pataasin ang antas ng Wi‑Fi logging, ipakita sa bawat SSID RSSI sa Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Kapag naka-enable, mas magiging agresibo ang Wi‑Fi sa paglipat ng koneksyon ng data sa Cellular, kapag mahina ang signal ng Wi‑Fi"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Tulong at feedback"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Ilagay ang password upang mag-factory reset sa demo mode"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Susunod"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Kinakailangan ang password"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-tr/arrays.xml b/packages/SettingsLib/res/values-tr/arrays.xml
index ee668c7..00cf236 100644
--- a/packages/SettingsLib/res/values-tr/arrays.xml
+++ b/packages/SettingsLib/res/values-tr/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"HDCP denetimini yalnızca DRM içeriği için kullan"</item>
<item msgid="45075631231212732">"HDCP denetimini her zaman kullan"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Varsayılan"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Varsayılan"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Varsayılan"</item>
- <item msgid="8895532488906185219">"44,1 kHz"</item>
- <item msgid="2909915718994807056">"48,0 kHz"</item>
- <item msgid="3347287377354164611">"88,2 kHz"</item>
- <item msgid="1234212100239985373">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Varsayılan"</item>
- <item msgid="4482862757811638365">"44,1 kHz"</item>
- <item msgid="354495328188724404">"48,0 kHz"</item>
- <item msgid="7329816882213695083">"88,2 kHz"</item>
- <item msgid="6967397666254430476">"96,0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Varsayılan"</item>
- <item msgid="5618929009984956469">"16 bit/örnek"</item>
- <item msgid="3412640499234627248">"24 bit/örnek"</item>
- <item msgid="121583001492929387">"32 bit/örnek"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Varsayılan"</item>
- <item msgid="4726688794884191540">"16 bit/örnek"</item>
- <item msgid="305344756485516870">"24 bit/örnek"</item>
- <item msgid="244568657919675099">"32 bit/örnek"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Varsayılan"</item>
- <item msgid="4106832974775067314">"Mono"</item>
- <item msgid="5571632958424639155">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Varsayılan"</item>
- <item msgid="8900559293912978337">"Mono"</item>
- <item msgid="8883739882299884241">"Stereo"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Ses kalitesi öncelikli (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Standart (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Bağlantı öncelikli (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Ses kalitesi öncelikli (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Standart (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Bağlantı öncelikli (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Kapalı"</item>
<item msgid="1593289376502312923">"64 KB"</item>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index b5c081a..3101b2c 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Hücresel veri her zaman etkin"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Mutlak sesi iptal et"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth Ses Codec\'i"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Tercih Edilen Bluetooth A2DP Codec\'ini Seçin"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth Ses Örnek Hızı"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Tercih Edilen Bluetooth A2DP Codec\'i Örnek Hızını Seçin"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth Ses Örnek Başına Bit Sayısı"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Tercih Edilen Bluetooth A2DP Codec\'i Örnek Başına Bit Sayısını Seçin"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth Ses Kanalı Modu"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Tercih Edilen Bluetooth A2DP Codec\'i Kanal Modunu Seçin"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth Ses LDAC Oynatma Kalitesi"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Tercih Edilen Bluetooth A2DP Codec\'i LDAC Oynatma Kalitesini Seçin"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Kablosuz ekran sertifikası seçeneklerini göster"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Kablosuz günlük kaydı seviyesini artır. Kablosuz Seçici\'de her bir SSID RSSI için göster."</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Etkinleştirildiğinde, Kablosuz ağ sinyali zayıfken veri bağlantısının Hücresel ağa geçirilmesinde daha agresif olunur"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Yardım ve geri bildirim"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menü"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Demo modunda sıfırlamak için şifreyi girin"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Sonraki"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Şifre gerekli"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-uk/arrays.xml b/packages/SettingsLib/res/values-uk/arrays.xml
index 91a6d6f..dd661c4 100644
--- a/packages/SettingsLib/res/values-uk/arrays.xml
+++ b/packages/SettingsLib/res/values-uk/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"Використовувати перевірку HDCP лише для вмісту, захищеного DRM"</item>
<item msgid="45075631231212732">"Завжди використовувати перевірку HDCP"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"За умовчанням"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"За умовчанням"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"За умовчанням"</item>
- <item msgid="8895532488906185219">"44,1 кГц"</item>
- <item msgid="2909915718994807056">"48 кГц"</item>
- <item msgid="3347287377354164611">"88,2 кГц"</item>
- <item msgid="1234212100239985373">"96 кГц"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"За умовчанням"</item>
- <item msgid="4482862757811638365">"44,1 кГц"</item>
- <item msgid="354495328188724404">"48 кГц"</item>
- <item msgid="7329816882213695083">"88,2 кГц"</item>
- <item msgid="6967397666254430476">"96 кГц"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"За умовчанням"</item>
- <item msgid="5618929009984956469">"16 бітів на зразок"</item>
- <item msgid="3412640499234627248">"24 біти на зразок"</item>
- <item msgid="121583001492929387">"32 біти на зразок"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"За умовчанням"</item>
- <item msgid="4726688794884191540">"16 бітів на зразок"</item>
- <item msgid="305344756485516870">"24 біти на зразок"</item>
- <item msgid="244568657919675099">"32 біти на зразок"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"За умовчанням"</item>
- <item msgid="4106832974775067314">"Моно"</item>
- <item msgid="5571632958424639155">"Стерео"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"За умовчанням"</item>
- <item msgid="8900559293912978337">"Моно"</item>
- <item msgid="8883739882299884241">"Стерео"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Реком. якість звуку (990 та 909 Кбіт/с)"</item>
- <item msgid="138837449700903545">"Стандартна якість (660 і 606 Кбіт/с)"</item>
- <item msgid="4777177307869441982">"Рекоменд. з’єднання (330 і 303 Кбіт/с)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Реком. якість звуку (990 та 909 Кбіт/с)"</item>
- <item msgid="9091111147684472529">"Стандартна якість (660 і 606 Кбіт/с)"</item>
- <item msgid="3367904477834831032">"Рекоменд. з’єднання (330 і 303 Кбіт/с)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Вимкнено"</item>
<item msgid="1593289376502312923">"64 Кб"</item>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index bafd8b6..5e241f5 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Не вимикати передавання даних"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Вимкнути абсолютну гучність"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Кодек для аудіо Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Виберіть кодек Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Частота вибірки для аудіо Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Виберіть частоту вибірки для кодека Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Кількість бітів на зразок для аудіо Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Виберіть кількість бітів на зразок для кодека Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Режим каналу для аудіо Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Виберіть режим каналу для кодека Bluetooth A2DP"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Якість відтворення аудіо Bluetooth LDAC"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Виберіть якість відтворення для кодека Bluetooth A2DP"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Показати параметри сертифікації бездротового екрана"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Показувати в журналі RSSI для кожного SSID під час вибору Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Примусово перемикатися на мобільну мережу, коли сигнал Wi-Fi слабкий"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Довідка й відгуки"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Меню"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Введіть пароль, щоб скинути налаштування в демо-режимі"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Далі"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Потрібен пароль"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ur/arrays.xml b/packages/SettingsLib/res/values-ur/arrays.xml
index d7dd3d2..d55bd2b 100644
--- a/packages/SettingsLib/res/values-ur/arrays.xml
+++ b/packages/SettingsLib/res/values-ur/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"ہمیشہ HDCP چیکنگ استعمال کریں"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"ڈیفالٹ"</item>
+ <item msgid="7065842274271279580">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"ڈیفالٹ"</item>
+ <item msgid="5062108632402595000">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"ڈیفالٹ"</item>
+ <item msgid="3093023430402746802">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"ڈیفالٹ"</item>
+ <item msgid="3214516120190965356">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"ڈیفالٹ"</item>
+ <item msgid="2684127272582591429">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
<item msgid="5618929009984956469">"16 بٹس/نمونہ"</item>
<item msgid="3412640499234627248">"24 بٹس/نمونہ"</item>
<item msgid="121583001492929387">"32 بٹس/نمونہ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"ڈیفالٹ"</item>
+ <item msgid="1081159789834584363">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
<item msgid="4726688794884191540">"16 بٹس/نمونہ"</item>
<item msgid="305344756485516870">"24 بٹس/نمونہ"</item>
<item msgid="244568657919675099">"32 بٹس/نمونہ"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"ڈیفالٹ"</item>
+ <item msgid="5226878858503393706">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
<item msgid="4106832974775067314">"مونو"</item>
<item msgid="5571632958424639155">"اسٹیریو"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"ڈیفالٹ"</item>
+ <item msgid="4118561796005528173">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
<item msgid="8900559293912978337">"مونو"</item>
<item msgid="8883739882299884241">"اسٹیریو"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"آواز کا معیار ترجیحی (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"معیاری (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"کنکشن ترجیحی (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"آڈیو کے معیار کے لیے بہتر بنائیں (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"متوازن آڈیو اور کنکشن کا معیار (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"کنکشن کے معیار کے لیے بہتر بنائیں (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"آواز کا معیار ترجیحی (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"معیاری (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"کنکشن ترجیحی (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"آڈیو کے معیار کے لیے بہتر بنائیں"</item>
+ <item msgid="4327143584633311908">"متوازن آڈیو اور کنکشن کا معیار"</item>
+ <item msgid="6155648878105378550">"کنکشن کے معیار کے لیے بہتر بنائیں"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"آف"</item>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index ce27df8..d4c97cd 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"سیلولر ڈیٹا کو ہمیشہ فعال رکھیں"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"مطلق والیوم کو غیر فعال کریں"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"بلوٹوتھ آڈیو کوڈیک"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"ترجیحی بلوٹوتھ A2DP کوڈیک منتخب کریں"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"بلوٹوتھ آڈیو کوڈیک منتخب کریں"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"بلوٹوتھ آڈیو کے نمونے کی شرح"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"ترجیحی بلوٹوتھ A2DP کوڈیک نمونے کی شرح منتخب کریں"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"بلوٹوتھ آڈیو کوڈیک منتخب کریں:\nنمونے کی شرح"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"بلوٹوتھ آڈیو بٹس فی نمونہ"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"ترجیحی بلو ٹوتھ A2DP کوڈیک بٹس فی نمونہ منتخب کریں"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"بلوٹوتھ آڈیو کوڈیک منتخب کریں:\nبٹس فی نمونہ"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"بلوٹوتھ آڈیو چینل موڈ"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"ترجیحی بلوٹوتھ A2DP کوڈیک چینل موڈ منتخب کریں"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"بلوٹوتھ آڈیو LDAC پلے بیک کا معیار"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"ترجیحی بلوٹوتھ A2DP کوڈیک LDAC پلے بیک کا معیار منتخب کریں"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"بلوٹوتھ آڈیو کوڈیک منتخب کریں:\nچینل موڈ"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"بلوٹوتھ آڈیو LDAC کوڈیک: پلے بیک کا معیار"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"بلوٹوتھ آڈیو LDAC کوڈیک منتخب کریں:\nپلے بیک کا معیار"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"سلسلہ بندی: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"وائرلیس ڈسپلے سرٹیفیکیشن کیلئے اختیارات دکھائیں"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi لاگنگ لیول میں اضافہ کریں، Wi‑Fi منتخب کنندہ میں فی SSID RSSI دکھائیں"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"فعال ہونے پر، جب Wi‑Fi سگنل کمزور ہوگا تو Wi‑Fi سیلولر پر ڈیٹا کنکشن بھیجنے کیلئے مزید جارحانہ کاروائی کرے گا۔"</string>
@@ -352,4 +353,10 @@
<string name="help_feedback_label" msgid="6815040660801785649">"مدد اور تاثرات"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"مینو"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <!-- no translation found for retail_demo_reset_message (118771671364131297) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_next (8356731459226304963) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_title (696589204029930100) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-uz/arrays.xml b/packages/SettingsLib/res/values-uz/arrays.xml
index 11b3de7..f222d1f 100644
--- a/packages/SettingsLib/res/values-uz/arrays.xml
+++ b/packages/SettingsLib/res/values-uz/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Har doim HDCP tekshiruvidan foydalanilsin"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Standart"</item>
+ <item msgid="7065842274271279580">"Tizim tanlovi (birlamchi)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Standart"</item>
+ <item msgid="5062108632402595000">"Tizim tanlovi (birlamchi)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Standart"</item>
+ <item msgid="3093023430402746802">"Tizim tanlovi (birlamchi)"</item>
<item msgid="8895532488906185219">"44.1 kGs"</item>
<item msgid="2909915718994807056">"48.0 kGs"</item>
<item msgid="3347287377354164611">"88.2 kGs"</item>
<item msgid="1234212100239985373">"96.0 kGs"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Standart"</item>
+ <item msgid="3214516120190965356">"Tizim tanlovi (birlamchi)"</item>
<item msgid="4482862757811638365">"44.1 kGs"</item>
<item msgid="354495328188724404">"48.0 kGs"</item>
<item msgid="7329816882213695083">"88.2 kGs"</item>
<item msgid="6967397666254430476">"96.0 kGs"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Standart"</item>
+ <item msgid="2684127272582591429">"Tizim tanlovi (birlamchi)"</item>
<item msgid="5618929009984956469">"16 bit/namuna"</item>
<item msgid="3412640499234627248">"24 bit/namuna"</item>
<item msgid="121583001492929387">"32 bit/namuna"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Standart"</item>
+ <item msgid="1081159789834584363">"Tizim tanlovi (birlamchi)"</item>
<item msgid="4726688794884191540">"16 bit/namuna"</item>
<item msgid="305344756485516870">"24 bit/namuna"</item>
<item msgid="244568657919675099">"32 bit/namuna"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Standart"</item>
+ <item msgid="5226878858503393706">"Tizim tanlovi (birlamchi)"</item>
<item msgid="4106832974775067314">"Mono"</item>
<item msgid="5571632958424639155">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Standart"</item>
+ <item msgid="4118561796005528173">"Tizim tanlovi (birlamchi)"</item>
<item msgid="8900559293912978337">"Mono"</item>
<item msgid="8883739882299884241">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"O‘zingizga ma’qul ovoz sifati (990/909 kbit/s)"</item>
- <item msgid="138837449700903545">"Standart (660/606 kbit/s)"</item>
- <item msgid="4777177307869441982">"O‘zingizga ma’qul ulanish (330/303 kbit/s)"</item>
+ <item msgid="3411577996960199959">"Audio sifati uchun moslashtirish (990/909 kbit/s)"</item>
+ <item msgid="2921767058740704969">"Audio sifati balansi va ulanish tezligi (660/606 kbit/s)"</item>
+ <item msgid="3682554248829489641">"Ulanish sifati uchun moslashtirish (330/303 kbit/s)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"O‘zingizga ma’qul ovoz sifati (990/909 kbit/s)"</item>
- <item msgid="9091111147684472529">"Standart (660/606 kbit/s)"</item>
- <item msgid="3367904477834831032">"O‘zingizga ma’qul ulanish (330/303 kbit/s)"</item>
+ <item msgid="7668834469173465015">"Audio sifati uchun moslashtirish"</item>
+ <item msgid="4327143584633311908">"Audio sifati balansi va ulanish tezligi"</item>
+ <item msgid="6155648878105378550">"Ulanish sifati uchun moslashtirish"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"O‘chiq"</item>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 6efc6c4..b5460c2 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Mobil internet o‘chirilmasin"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Ovoz balangligining mutlaq darajasini o‘chirib qo‘yish"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Bluetooth audio kodeki"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"O‘zingizga ma’qul Bluetooth A2DP kodekini tanlang"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Bluetooth orqali uzatish uchun audiokodek"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Bluetooth audio namunasi chastotasi"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"O‘zingizga ma’qul Bluetooth A2DP kodek namunasi chastotasini tanlang"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Bluetooth orqali uzatish uchun audiokodek:\nnamuna chastotasi"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Bluetooth audio namunasidagi bitlar soni"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"O‘zingizga ma’qul Bluetooth A2DP kodek namunasidagi bitlar sonini tanlang"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Bluetooth orqali uzatish uchun audiokodek:\nnamunadagi bitlar soni"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Bluetooth audio kanali rejimi"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"O‘zingizga ma’qul Bluetooth A2DP kodek kanali rejimini tanlang"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Bluetooth audio LDAC ijrosi sifati"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"O‘zingizga ma’qul Bluetooth A2DP kodek LDAC ijrosi sifatini tanlang"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Bluetooth orqali uzatish uchun audiokodek:\nkanal rejimi"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC audiokodeki bilan ijro etish sifati (Bluetooth orqali)"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"LDAC audiokodeki:\nijro sifati"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Translatsiya: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Simsiz monitorlarni sertifikatlash parametrini ko‘rsatish"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi ulanishini tanlashda har bir SSID uchun jurnalda ko‘rsatilsin"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Agar ushbu funksiya yoqilsa, Wi-Fi signali past bo‘lganda internetga ulanish majburiy ravishda mobil internetga o‘tkaziladi."</string>
@@ -240,7 +241,7 @@
<string name="enable_opengl_traces_title" msgid="6790444011053219871">"OpenGL trassasini yoqish"</string>
<string name="usb_audio_disable_routing" msgid="8114498436003102671">"Audio uzatishni o‘ch. qo‘yish (USB)"</string>
<string name="usb_audio_disable_routing_summary" msgid="980282760277312264">"Tashqi USB qurilmaga avto-yo‘naltirishni o‘ch. qo‘yish"</string>
- <string name="debug_layout" msgid="5981361776594526155">"Elementlar chegarasini ko‘rsatish"</string>
+ <string name="debug_layout" msgid="5981361776594526155">"Elementlar hoshiyasi"</string>
<string name="debug_layout_summary" msgid="2001775315258637682">"Klip, maydon va h.k. chegaralarini ko‘rsatish"</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"O‘ngdan chapga qarab yozish"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Barcha tillarda o‘ngdan chapga qarab yozish"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Yordam va fikr-mulohaza"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menyu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Demo rejimda zavod holatiga qaytarish uchun parolni kiriting"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Keyingisi"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Parolni kiritish zarur"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-vi/arrays.xml b/packages/SettingsLib/res/values-vi/arrays.xml
index 3f6f729..7b2368f 100644
--- a/packages/SettingsLib/res/values-vi/arrays.xml
+++ b/packages/SettingsLib/res/values-vi/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Luôn sử dụng kiểm tra HDCP"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Mặc định"</item>
+ <item msgid="7065842274271279580">"Sử dụng lựa chọn hệ thống (Mặc định)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Mặc định"</item>
+ <item msgid="5062108632402595000">"Sử dụng lựa chọn hệ thống (Mặc định)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Mặc định"</item>
+ <item msgid="3093023430402746802">"Sử dụng lựa chọn hệ thống (Mặc định)"</item>
<item msgid="8895532488906185219">"44,1 kHz"</item>
<item msgid="2909915718994807056">"48,0 kHz"</item>
<item msgid="3347287377354164611">"88,2 kHz"</item>
<item msgid="1234212100239985373">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Mặc định"</item>
+ <item msgid="3214516120190965356">"Sử dụng lựa chọn hệ thống (Mặc định)"</item>
<item msgid="4482862757811638365">"44,1 kHz"</item>
<item msgid="354495328188724404">"48,0 kHz"</item>
<item msgid="7329816882213695083">"88,2 kHz"</item>
<item msgid="6967397666254430476">"96,0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Mặc định"</item>
+ <item msgid="2684127272582591429">"Sử dụng lựa chọn hệ thống (Mặc định)"</item>
<item msgid="5618929009984956469">"16 bit/mẫu"</item>
<item msgid="3412640499234627248">"24 bit/mẫu"</item>
<item msgid="121583001492929387">"32 bit/mẫu"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Mặc định"</item>
+ <item msgid="1081159789834584363">"Sử dụng lựa chọn hệ thống (Mặc định)"</item>
<item msgid="4726688794884191540">"16 bit/mẫu"</item>
<item msgid="305344756485516870">"24 bit/mẫu"</item>
<item msgid="244568657919675099">"32 bit/mẫu"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Mặc định"</item>
+ <item msgid="5226878858503393706">"Sử dụng lựa chọn hệ thống (Mặc định)"</item>
<item msgid="4106832974775067314">"Đơn âm"</item>
<item msgid="5571632958424639155">"Âm thanh nổi"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Mặc định"</item>
+ <item msgid="4118561796005528173">"Sử dụng lựa chọn hệ thống (Mặc định)"</item>
<item msgid="8900559293912978337">"Đơn âm"</item>
<item msgid="8883739882299884241">"Âm thanh nổi"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Ưu tiên chất lượng (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Tiêu chuẩn (660kb/giây/606kb/giây)"</item>
- <item msgid="4777177307869441982">"Kết nối được ưu tiên (330kb/giây/303kb/giây)"</item>
+ <item msgid="3411577996960199959">"Tối ưu hóa chất lượng âm thanh (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"Chất lượng kết nối và âm thanh cân bằng (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"Tối ưu hóa chất lượng kết nối (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Ưu tiên chất lượng (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Tiêu chuẩn (660kb/giây/606kb/giây)"</item>
- <item msgid="3367904477834831032">"Kết nối được ưu tiên (330kb/giây/303kb/giây)"</item>
+ <item msgid="7668834469173465015">"Tối ưu hóa chất lượng âm thanh"</item>
+ <item msgid="4327143584633311908">"Chất lượng kết nối và âm thanh cân bằng"</item>
+ <item msgid="6155648878105378550">"Tối ưu hóa chất lượng kết nối"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Tắt"</item>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 5822f18..ff9a557 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Dữ liệu di động luôn hoạt động"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Vô hiệu hóa âm lượng tuyệt đối"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"Codec âm thanh Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Chọn Codec A2DP Bluetooth ưu tiên"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Chọn Codec âm thanh Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Tốc độ lấy mẫu âm thanh Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Chọn tốc độ lấy mẫu Codec A2DP Bluetooth ưu tiên"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Chọn Codec âm thanh Bluetooth:\nTốc độ lấy mẫu"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Số bit âm thanh Bluetooth mỗi mẫu"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Chọn số bit Codec A2DP Bluetooth ưu tiên mỗi mẫu"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Chọn Codec âm thanh Bluetooth:\nSố bit trên mẫu"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Chế độ kênh âm thanh Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Chọn chế độ kênh Codec A2DP Bluetooth ưu tiên"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Chất lượng phát lại LDAC Âm thanh Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Chọn chất lượng phát lại LDAC Codec A2DP Bluetooth ưu tiên"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Chọn Codec âm thanh Bluetooth:\nChế độ kênh"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC âm thanh Bluetooth: Chất lượng phát lại"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Chọn Codec LDAC âm thanh Bluetooth:\nChất lượng phát lại"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Truyền trực tuyến: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Hiển thị tùy chọn chứng nhận hiển thị không dây"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Tăng mức ghi nhật ký Wi‑Fi, hiển thị mỗi SSID RSSI trong bộ chọn Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Khi được bật, Wi‑Fi sẽ tích cực hơn trong việc chuyển vùng kết nối dữ liệu sang mạng di động khi tín hiệu Wi‑Fi yếu"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Trợ giúp và phản hồi"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Menu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Nhập mật khẩu để tiến hành khôi phục cài đặt gốc ở chế độ trình diễn"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Tiếp theo"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Yêu cầu mật khẩu"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
index 0ef095d..5ec63b3 100644
--- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"仅使用 HDCP 检查 DRM 内容"</item>
<item msgid="45075631231212732">"始终使用 HDCP 检查"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"默认"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"默认"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"默认"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"默认"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"默认"</item>
- <item msgid="5618929009984956469">"16 位/样本"</item>
- <item msgid="3412640499234627248">"24 位/样本"</item>
- <item msgid="121583001492929387">"32 位/样本"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"默认"</item>
- <item msgid="4726688794884191540">"16 位/样本"</item>
- <item msgid="305344756485516870">"24 位/样本"</item>
- <item msgid="244568657919675099">"32 位/样本"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"默认"</item>
- <item msgid="4106832974775067314">"单声道"</item>
- <item msgid="5571632958424639155">"立体声"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"默认"</item>
- <item msgid="8900559293912978337">"单声道"</item>
- <item msgid="8883739882299884241">"立体声"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"音质优先 (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"标准 (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"连接优先 (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"音质优先 (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"标准 (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"连接优先 (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"关闭"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 35a1dfc..4c47ace 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"始终开启移动数据网络"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"停用绝对音量功能"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"蓝牙音频编解码器"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"选择首选的蓝牙 A2DP 编解码器"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"蓝牙音频采样率"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"选择首选的蓝牙 A2DP 编解码器采样率"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"蓝牙音频每样本位数"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"选择首选的蓝牙 A2DP 编解码器每样本位数"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"蓝牙音频声道模式"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"选择首选的蓝牙 A2DP 编解码器声道模式"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"蓝牙音频 LDAC 播放质量"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"选择首选的蓝牙 A2DP 编解码器 LDAC 播放质量"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"显示无线显示认证选项"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"提升WLAN日志记录级别(在WLAN选择器中显示每个SSID的RSSI)"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"开启此设置后,系统会在WLAN信号较弱时,主动将网络模式从WLAN网络切换到移动数据网络"</string>
@@ -352,4 +360,10 @@
<string name="help_feedback_label" msgid="6815040660801785649">"帮助和反馈"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"菜单"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <!-- no translation found for retail_demo_reset_message (118771671364131297) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_next (8356731459226304963) -->
+ <skip />
+ <!-- no translation found for retail_demo_reset_title (696589204029930100) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rHK/arrays.xml b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
index 872e7c9..953c8cc 100644
--- a/packages/SettingsLib/res/values-zh-rHK/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"僅使用 HDCP 檢查 DRM 內容"</item>
<item msgid="45075631231212732">"永遠使用 HDCP 檢查"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"預設"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"預設"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"預設"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"預設"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"預設"</item>
- <item msgid="5618929009984956469">"每個樣本 16 位元"</item>
- <item msgid="3412640499234627248">"每個樣本 24 位元"</item>
- <item msgid="121583001492929387">"每個樣本 32 位元"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"預設"</item>
- <item msgid="4726688794884191540">"每個樣本 16 位元"</item>
- <item msgid="305344756485516870">"每個樣本 24 位元"</item>
- <item msgid="244568657919675099">"每個樣本 32 位元"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"預設"</item>
- <item msgid="4106832974775067314">"單聲道"</item>
- <item msgid="5571632958424639155">"立體聲"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"預設"</item>
- <item msgid="8900559293912978337">"單聲道"</item>
- <item msgid="8883739882299884241">"立體聲"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"音質優先 (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"標準 (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"連線優先 (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"音質優先 (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"標準 (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"連線優先 (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"關閉"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 3c5c328..3d8b673 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"經常啟用流動數據"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"停用絕對音量功能"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"藍牙音訊編解碼器"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"選取偏好的藍牙 A2DP 編解碼器"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"藍牙音訊取樣率"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"選取偏好的藍牙 A2DP 編解碼器取樣率"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"藍牙音訊每個樣本位元數"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"選取偏好的藍牙 A2DP 編解碼器每個樣本位元數"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"藍牙音訊聲道模式"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"選取偏好的藍牙 A2DP 編解碼器聲道模式"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"藍牙音訊 LDAC 播放音質"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"選取偏好的藍牙 A2DP 編解碼器 LDAC 播放音質"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"顯示無線螢幕分享認證的選項"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"讓 Wi‑Fi 記錄功能升級,在 Wi‑Fi 選擇器中依每個 SSID RSSI 顯示 Wi‑Fi 詳細紀錄"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"啟用時,Wi-Fi 連線會在訊號不穩的情況下更積極轉換成流動數據連線"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"說明和意見反映"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"選單"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"輸入密碼即可在示範模式下重設原廠設定"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"下一步"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"請輸入密碼"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/arrays.xml b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
index ad99128..d426b30 100644
--- a/packages/SettingsLib/res/values-zh-rTW/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
@@ -58,66 +58,22 @@
<item msgid="3878793616631049349">"僅使用 HDCP 檢查 DRM 內容"</item>
<item msgid="45075631231212732">"一律使用 HDCP 檢查"</item>
</string-array>
- <string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"預設"</item>
- <item msgid="7539690996561263909">"SBC"</item>
- <item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
- <item msgid="2301339338870319651">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"預設"</item>
- <item msgid="6898329690939802290">"SBC"</item>
- <item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
- <item msgid="508106435710925399">"LDAC"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"預設"</item>
- <item msgid="8895532488906185219">"44.1 kHz"</item>
- <item msgid="2909915718994807056">"48.0 kHz"</item>
- <item msgid="3347287377354164611">"88.2 kHz"</item>
- <item msgid="1234212100239985373">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"預設"</item>
- <item msgid="4482862757811638365">"44.1 kHz"</item>
- <item msgid="354495328188724404">"48.0 kHz"</item>
- <item msgid="7329816882213695083">"88.2 kHz"</item>
- <item msgid="6967397666254430476">"96.0 kHz"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"預設"</item>
- <item msgid="5618929009984956469">"16 位元/樣本"</item>
- <item msgid="3412640499234627248">"24 位元/樣本"</item>
- <item msgid="121583001492929387">"32 位元/樣本"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"預設"</item>
- <item msgid="4726688794884191540">"16 位元/樣本"</item>
- <item msgid="305344756485516870">"24 位元/樣本"</item>
- <item msgid="244568657919675099">"32 位元/樣本"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"預設"</item>
- <item msgid="4106832974775067314">"單聲道"</item>
- <item msgid="5571632958424639155">"立體聲"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"預設"</item>
- <item msgid="8900559293912978337">"單聲道"</item>
- <item msgid="8883739882299884241">"立體聲"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"音質優先 (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"標準 (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"連線優先 (330kbps/303kbps)"</item>
- </string-array>
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"音質優先 (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"標準 (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"連線優先 (330kbps/303kbps)"</item>
- </string-array>
+ <!-- no translation found for bluetooth_a2dp_codec_titles:0 (7065842274271279580) -->
+ <!-- no translation found for bluetooth_a2dp_codec_titles:3 (500463122137421129) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:0 (5062108632402595000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_summaries:3 (3093550793512117000) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_titles:0 (3093023430402746802) -->
+ <!-- no translation found for bluetooth_a2dp_codec_sample_rate_summaries:0 (3214516120190965356) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_titles:0 (2684127272582591429) -->
+ <!-- no translation found for bluetooth_a2dp_codec_bits_per_sample_summaries:0 (1081159789834584363) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_titles:0 (5226878858503393706) -->
+ <!-- no translation found for bluetooth_a2dp_codec_channel_mode_summaries:0 (4118561796005528173) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:0 (3411577996960199959) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:1 (2921767058740704969) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_titles:2 (3682554248829489641) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:0 (7668834469173465015) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:1 (4327143584633311908) -->
+ <!-- no translation found for bluetooth_a2dp_codec_ldac_playback_quality_summaries:2 (6155648878105378550) -->
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"關閉"</item>
<item msgid="1593289376502312923">"64K"</item>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 861834b..ad3b83a 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -171,15 +171,23 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"行動數據連線一律保持啟用狀態"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"停用絕對音量功能"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"藍牙音訊轉碼器"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"選取偏好的藍牙 A2DP 轉碼器"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_type_dialog_title (4558347981670553665) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"藍牙音訊取樣率"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"選取偏好的藍牙 A2DP 轉碼器取樣率"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_sample_rate_dialog_title (5628790207448471613) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"藍牙音訊每單位樣本位元數"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"選取偏好的藍牙 A2DP 轉碼器每單位樣本位元數"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_bits_per_sample_dialog_title (4546131401358681321) -->
+ <skip />
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"藍牙音訊聲道模式"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"選取偏好的藍牙 A2DP 轉碼器聲道模式"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"藍牙音訊 LDAC 播放品質"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"選取偏好的藍牙 A2DP 轉碼器 LDAC 播放品質"</string>
+ <!-- no translation found for bluetooth_select_a2dp_codec_channel_mode_dialog_title (9133545781346216071) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality (3619694372407843405) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title (3181967377574368400) -->
+ <skip />
+ <!-- no translation found for bluetooth_select_a2dp_codec_streaming_label (5347862512596240506) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"顯示無線螢幕分享認證的選項"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"讓 Wi‑Fi 記錄功能升級,在 Wi‑Fi 選擇器中依每個 SSID RSSI 顯示 Wi‑Fi 詳細紀錄"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"啟用時,Wi-Fi 連線在訊號不穩的情況下會更積極轉換成行動數據連線"</string>
@@ -352,4 +360,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"說明與意見回饋"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"選單"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"如要在示範模式中恢復原廠設定,請輸入密碼"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"下一步"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"請輸入密碼"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zu/arrays.xml b/packages/SettingsLib/res/values-zu/arrays.xml
index 50c1274..50597f3 100644
--- a/packages/SettingsLib/res/values-zu/arrays.xml
+++ b/packages/SettingsLib/res/values-zu/arrays.xml
@@ -59,64 +59,64 @@
<item msgid="45075631231212732">"Sebenzisa njalo ukuhlola kwe-HDPC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="1852387125374225729">"Okuzenzakalelayo"</item>
+ <item msgid="7065842274271279580">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="4260844283202960798">"aptX"</item>
- <item msgid="7279983368484312990">"aptX-HD"</item>
+ <item msgid="500463122137421129">"aptX HD"</item>
<item msgid="2301339338870319651">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="9072025520360316957">"Okuzenzakalelayo"</item>
+ <item msgid="5062108632402595000">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="1190434429082395888">"aptX"</item>
- <item msgid="649699003004233053">"aptX-HD"</item>
+ <item msgid="3093550793512117000">"aptX HD"</item>
<item msgid="508106435710925399">"LDAC"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="7102940318360468759">"Okuzenzakalelayo"</item>
+ <item msgid="3093023430402746802">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="7224433008148687313">"Okuzenzakalelayo"</item>
+ <item msgid="3214516120190965356">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="6694044160540313386">"Okuzenzakalelayo"</item>
+ <item msgid="2684127272582591429">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
<item msgid="5618929009984956469">"16 bits/isampula"</item>
<item msgid="3412640499234627248">"24 bits/isampula"</item>
<item msgid="121583001492929387">"32 bits/isampula"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="5091076677792306320">"Okuzenzakalelayo"</item>
+ <item msgid="1081159789834584363">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
<item msgid="4726688794884191540">"16 bits/isampula"</item>
<item msgid="305344756485516870">"24 bits/isampula"</item>
<item msgid="244568657919675099">"32 bits/isampula"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="13423709606339855">"Okuzenzakalelayo"</item>
+ <item msgid="5226878858503393706">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
<item msgid="4106832974775067314">"Okukodwa"</item>
<item msgid="5571632958424639155">"I-Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="8128478683963250130">"Okuzenzakalelayo"</item>
+ <item msgid="4118561796005528173">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
<item msgid="8900559293912978337">"Okukodwa"</item>
<item msgid="8883739882299884241">"I-Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="2944889121850394020">"Ikhwalithi yomsindo ekhethwayo (990kbps/909kbps)"</item>
- <item msgid="138837449700903545">"Okujwayelekile (660kbps/606kbps)"</item>
- <item msgid="4777177307869441982">"Uxhumo olukhethwayo (330kbps/303kbps)"</item>
+ <item msgid="3411577996960199959">"Thuthukisela ikhwalithi yomsindo (990kbps/909kbps)"</item>
+ <item msgid="2921767058740704969">"Umsindo obhalansile nekhwalithi yoxhumo (660kbps/606kbps)"</item>
+ <item msgid="3682554248829489641">"Thuthukisela ikhwalithi yoxhumo (330kbps/303kbps)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="172302906231378902">"Ikhwalithi yomsindo ekhethwayo (990kbps/909kbps)"</item>
- <item msgid="9091111147684472529">"Okujwayelekile (660kbps/606kbps)"</item>
- <item msgid="3367904477834831032">"Uxhumo olukhethwayo (330kbps/303kbps)"</item>
+ <item msgid="7668834469173465015">"Thuthukisela ikhwalithi yomsindo"</item>
+ <item msgid="4327143584633311908">"Umsindo obhalansile nekhwalithi yoxhumo"</item>
+ <item msgid="6155648878105378550">"Thuthukisela ikhwalithi yoxhumo"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="8665206199209698501">"Valiwe"</item>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 289f4f6..9f7e72e 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -171,15 +171,16 @@
<string name="mobile_data_always_on" msgid="7745605759775320362">"Idatha yeselula ihlala isebenza"</string>
<string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"Khubaza ivolumu ngokuphelele"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="90597356942154882">"I-Bluetooth Audio Codec"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="6470824182074383881">"Khetha i-Bluetooth A2DP Codec ethandwayo"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="4558347981670553665">"Khetha i-Bluetooth Audio Codec"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="4788245703824623062">"Isilinganiso sesampula yomsindo we-Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="4263851572248033749">"Khetha isilinganiso sesampuli ye-Bluetooth A2DP Codec ethandwayo"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5628790207448471613">"Khetha i-Bluetooth Audio Codec:\nIsilinganiso sesampula"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="2099645202720164141">"Ama-Bits omsindo we-Bluetooth ngesampula ngayinye"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="2096170505745650345">"Khetha i-Bluetooth A2DP Codec Bits ethandwayo ngesampula ngayinye"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4546131401358681321">"Khetha i-Bluetooth Audio Codec:\nBits ngesampuli"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="884855779449390540">"Imodi yesiteshi somsindo we-Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="4073812880900816325">"Khetha imodi yesiteshi se-Bluetooth A2DP Codec ethandwayo"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="4846872213548295632">"Ikhwalithi yokudlala ye-LDAC yomsindo we-Bluetooth"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3940973633342423717">"Khetha ikhwalithi yokudlala ye-Bluetooth A2DP Codec LDAC ethandwayo"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="9133545781346216071">"Khetha i-Bluetooth Audio Codec:\nImodi yesiteshi"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"I-Bluetooth Audio LDAC Codec: Ikhwalithi yokudlala"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Khetha i-Bluetooth Audio LDAC Codec:\nIkhwalithi yokudlala"</string>
+ <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Ukusakaza: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Bonisa izinketho zokunikeza isitifiketi ukubukeka okungenantambo"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"khuphula izinga lokungena le-Wi-Fi, bonisa nge-SSID RSSI engayodwana kusikhethi se-Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="6328455667642570371">"Uma inikwe amandla, i-Wi-Fi izoba namandla kakhulu ekunikezeleni ukuxhumeka kwedatha kuselula, uma isiginali ye-Wi-Fi iphansi"</string>
@@ -352,4 +353,7 @@
<string name="help_feedback_label" msgid="6815040660801785649">"Usizo nempendulo"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"Imenyu"</string>
<string name="time_zone_gmt" msgid="2587097992671450782">"I-GMT"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"Faka iphasiwedi ukuze wenze ukusetha kwefekthri kumodi yedemo"</string>
+ <string name="retail_demo_reset_next" msgid="8356731459226304963">"Okulandelayo"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"Iphasiwedi iyadingeka"</string>
</resources>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index cfb990e..7bbca5e 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -103,10 +103,11 @@
<!-- Bluetooth settings -->
- <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40] -->
+ <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50] -->
<string-array name="bluetooth_a2dp_codec_titles">
<item>Use System Selection (Default)</item>
<item>SBC</item>
+ <item>AAC</item>
<item>aptX</item>
<item>aptX HD</item>
<item>LDAC</item>
@@ -119,18 +120,20 @@
<item>1</item>
<item>2</item>
<item>3</item>
+ <item>4</item>
</string-array>
- <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40]-->
+ <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_summaries" >
<item>Use System Selection (Default)</item>
<item>SBC</item>
+ <item>AAC</item>
<item>aptX</item>
<item>aptX HD</item>
<item>LDAC</item>
</string-array>
- <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40] -->
+ <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50] -->
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item>Use System Selection (Default)</item>
<item>44.1 kHz</item>
@@ -148,7 +151,7 @@
<item>8</item>
</string-array>
- <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40]-->
+ <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries" >
<item>Use System Selection (Default)</item>
<item>44.1 kHz</item>
@@ -157,7 +160,7 @@
<item>96.0 kHz</item>
</string-array>
- <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40] -->
+ <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50] -->
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
<item>Use System Selection (Default)</item>
<item>16 bits/sample</item>
@@ -173,7 +176,7 @@
<item>4</item>
</string-array>
- <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40]-->
+ <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries" >
<item>Use System Selection (Default)</item>
<item>16 bits/sample</item>
@@ -181,7 +184,7 @@
<item>32 bits/sample</item>
</string-array>
- <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40] -->
+ <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50] -->
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
<item>Use System Selection (Default)</item>
<item>Mono</item>
@@ -195,7 +198,7 @@
<item>2</item>
</string-array>
- <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40]-->
+ <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries" >
<item>Use System Selection (Default)</item>
<item>Mono</item>
@@ -204,9 +207,9 @@
<!-- Titles for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70] -->
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item>Optimize for Audio Quality (990kbps/909kbps)</item>
+ <item>Optimized for Audio Quality (990kbps/909kbps)</item>
<item>Balanced Audio And Connection Quality (660kbps/606kbps)</item>
- <item>Optimize for Connection Quality (330kbps/303kbps)</item>
+ <item>Optimized for Connection Quality (330kbps/303kbps)</item>
</string-array>
<!-- Values for Bluetooth Audio Codec LDAC Playback Quaility selection preference. -->
@@ -218,9 +221,9 @@
<!-- Summaries for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70]-->
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries" >
- <item>Optimize for Audio Quality</item>
+ <item>Optimized for Audio Quality</item>
<item>Balanced Audio And Connection Quality</item>
- <item>Optimize for Connection Quality</item>
+ <item>Optimized for Connection Quality</item>
</string-array>
<!-- Titles for logd limit size selection preference. [CHAR LIMIT=14] -->
diff --git a/packages/SettingsLib/res/values/attrs.xml b/packages/SettingsLib/res/values/attrs.xml
index f064e4e..1f35d3e 100644
--- a/packages/SettingsLib/res/values/attrs.xml
+++ b/packages/SettingsLib/res/values/attrs.xml
@@ -36,7 +36,12 @@
<declare-styleable name="WifiEncryptionState">
<attr name="state_encrypted" format="boolean" />
</declare-styleable>
+ <declare-styleable name="WifiSavedState">
+ <attr name="state_saved" format="boolean" />
+ </declare-styleable>
+
<attr name="wifi_signal" format="reference" />
+ <attr name="wifi_friction" format="reference" />
<declare-styleable name="UsageView">
<attr name="android:colorAccent" />
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a1b2bdf..0129632 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -893,4 +893,11 @@
<!-- Label for Greenwich mean time, used in a string like GMT+05:00. [CHAR LIMIT=NONE] -->
<string name="time_zone_gmt">GMT</string>
+
+ <!-- Label for carrier demo mode factory reset confirmation dialog. [CHAR LIMIT=NONE] -->
+ <string name="retail_demo_reset_message">Enter password to perform factory reset in demo mode</string>
+ <!-- Label for positive button on carrier demo mode factory reset confirmation dialog [CHAR LIMIT=40] -->
+ <string name="retail_demo_reset_next">Next</string>
+ <!-- Title for carrier demo mode factory reset confirmation dialog. [CHAR LIMIT=40] -->
+ <string name="retail_demo_reset_title">Password required</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
index b037a3da..fc697ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
@@ -198,7 +198,7 @@
intent.putExtra(feedbackIntentExtraKey, packageNameKey);
intent.putExtra(feedbackIntentNameKey, packageNameValue);
}
- intent.putExtra(EXTRA_THEME, 1 /* Light, dark action bar */);
+ intent.putExtra(EXTRA_THEME, 0 /* Light theme */);
TypedArray array = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary});
intent.putExtra(EXTRA_PRIMARY_COLOR, array.getColor(0, 0));
array.recycle();
diff --git a/packages/SettingsLib/src/com/android/settingslib/TronUtils.java b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
new file mode 100644
index 0000000..1d9d03a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib;
+
+import android.content.Context;
+import android.net.ScoredNetwork;
+
+import com.android.internal.logging.MetricsLogger;
+
+/** Utilites for Tron Logging. */
+public final class TronUtils {
+
+ private TronUtils() {};
+
+ public static void logWifiSettingsBadge(Context context, int badgeEnum) {
+ logNetworkBadgeMetric(context, "settings_wifibadging", badgeEnum);
+ }
+
+ /**
+ * Logs an occurrence of the given network badge to a Histogram.
+ *
+ * @param context Context
+ * @param histogram the Tron histogram name to write to
+ * @param badgeEnum the {@link ScoredNetwork.Badging} badge value
+ * @throws IllegalArgumentException if the given badge enum is not supported
+ */
+ private static void logNetworkBadgeMetric(
+ Context context, String histogram, int badgeEnum)
+ throws IllegalArgumentException {
+ int bucket;
+ switch (badgeEnum) {
+ case ScoredNetwork.BADGING_NONE:
+ bucket = 0;
+ break;
+ case ScoredNetwork.BADGING_SD:
+ bucket = 1;
+ break;
+ case ScoredNetwork.BADGING_HD:
+ bucket = 2;
+ break;
+ case ScoredNetwork.BADGING_4K:
+ bucket = 3;
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported badge enum: " + badgeEnum);
+ }
+
+ MetricsLogger.histogram(context, histogram, bucket);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 6179244..ae6ada2a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -13,11 +13,16 @@
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.net.ConnectivityManager;
+import android.net.ScoredNetwork;
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;
@@ -29,6 +34,14 @@
private static String sServicesSystemSharedLibPackageName;
private static String sSharedSystemSharedLibPackageName;
+ 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,
+ com.android.internal.R.drawable.ic_signal_wifi_badged_3_bars,
+ com.android.internal.R.drawable.ic_signal_wifi_badged_4_bars
+ };
+
/**
* Return string resource that best describes combination of tethering
* options available on this device.
@@ -160,10 +173,7 @@
@ColorInt
public static int getColorAccent(Context context) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
- @ColorInt int colorAccent = ta.getColor(0, 0);
- ta.recycle();
- return colorAccent;
+ return getColorAttr(context, android.R.attr.colorAccent);
}
@ColorInt
@@ -182,6 +192,29 @@
return list.getDefaultColor();
}
+ @ColorInt
+ public static int getDisabled(Context context, int inputColor) {
+ return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
+ }
+
+ @ColorInt
+ public static int applyAlphaAttr(Context context, int attr, int inputColor) {
+ TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ float alpha = ta.getFloat(0, 0);
+ ta.recycle();
+ alpha *= Color.alpha(inputColor);
+ return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+ Color.blue(inputColor));
+ }
+
+ @ColorInt
+ public static int getColorAttr(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ @ColorInt int colorAccent = ta.getColor(0, 0);
+ ta.recycle();
+ return colorAccent;
+ }
+
/**
* Determine whether a package is a "system package", in which case certain things (like
* disabling notifications or disabling the package altogether) should be disallowed.
@@ -233,4 +266,48 @@
com.android.internal.R.string.config_deviceProvisioningPackage);
return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
}
+
+ /**
+ * Returns a badged Wifi icon drawable.
+ *
+ * <p>The first layer contains the Wifi pie and the second layer contains the badge. Callers
+ * should set the drawable to the appropriate size and tint color.
+ *
+ * @param context The caller's context (must have access to internal resources)
+ * @param level The number of bars to show (0-4)
+ * @param badge The badge enum {@see android.net.ScoredNetwork}
+ *
+ * @throws IllegalArgumentException if an invalid badge enum is given
+ *
+ * @deprecated TODO(sghuman): Finalize the form of this method and then move it to a new
+ * location.
+ */
+ public static LayerDrawable getBadgedWifiIcon(Context context, int level, int badge) {
+ return new LayerDrawable(
+ new Drawable[] {
+ context.getDrawable(WIFI_PIE_FOR_BADGING[level]),
+ context.getDrawable(getWifiBadgeResource(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:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_hd;
+ case ScoredNetwork.BADGING_4K:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_4k;
+ default:
+ throw new IllegalArgumentException(
+ "No badge resource found for badge value: " + badge);
+ }
+ }
}
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/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index b327be0..6e10aab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -105,6 +106,12 @@
private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
/**
+ * The key used to get the package name of the icon resource for the preference.
+ */
+ private static final String EXTRA_PREFERENCE_ICON_PACKAGE =
+ "com.android.settings.icon_package";
+
+ /**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the key that should be used for the preference.
*/
@@ -342,6 +349,7 @@
ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) {
if (applicationInfo.isSystemApp()) {
int icon = 0;
+ Pair<String, Integer> iconFromUri = null;
CharSequence title = null;
String summary = null;
String keyHint = null;
@@ -358,10 +366,10 @@
if (res != null && metaData != null) {
if (metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
- icon = getIconFromUri(context,
+ iconFromUri = getIconFromUri(context, activityInfo.packageName,
metaData.getString(META_DATA_PREFERENCE_ICON_URI), providerMap);
}
- if ((icon == 0) && metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
+ if (iconFromUri == null && metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
}
if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
@@ -401,12 +409,18 @@
if (TextUtils.isEmpty(title)) {
title = activityInfo.loadLabel(pm).toString();
}
- if (icon == 0) {
- icon = activityInfo.icon;
+
+ // Set the icon
+ if (iconFromUri != null) {
+ tile.icon = Icon.createWithResource(iconFromUri.first, iconFromUri.second);
+ } else {
+ if (icon == 0) {
+ icon = activityInfo.icon;
+ }
+ tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
}
- // Set icon, title and summary for the preference
- tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
+ // Set title and summary for the preference
tile.title = title;
tile.summary = summary;
// Replace the intent with this specific activity
@@ -422,16 +436,33 @@
}
/**
- * Gets the icon resource id from content provider.
+ * Gets the icon package name and resource id from content provider.
* @param Context context
+ * @param packageName package name of the target activity
* @param uriString URI for the content provider
* @param providerMap Maps URI authorities to providers
- * @return Resource id if returned by the content provider, otherwise 0
+ * @return package name and resource id of the icon specified
*/
- public static int getIconFromUri(Context context, String uriString,
- Map<String, IContentProvider> providerMap) {
+ public static Pair<String, Integer> getIconFromUri(Context context, String packageName,
+ String uriString, Map<String, IContentProvider> providerMap) {
Bundle bundle = getBundleFromUri(context, uriString, providerMap);
- return (bundle != null) ? bundle.getInt(META_DATA_PREFERENCE_ICON, 0) : 0;
+ if (bundle == null) {
+ return null;
+ }
+ String iconPackageName = bundle.getString(EXTRA_PREFERENCE_ICON_PACKAGE);
+ if (TextUtils.isEmpty(iconPackageName)) {
+ return null;
+ }
+ int resId = bundle.getInt(META_DATA_PREFERENCE_ICON, 0);
+ if (resId == 0) {
+ return null;
+ }
+ // Icon can either come from the target package or from the Settings app.
+ if (iconPackageName.equals(packageName)
+ || iconPackageName.equals(context.getPackageName())) {
+ return Pair.create(iconPackageName, bundle.getInt(META_DATA_PREFERENCE_ICON, 0));
+ }
+ return null;
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index e7450fa..06ea445 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -27,12 +27,15 @@
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
+import android.net.NetworkKey;
+import android.net.ScoredNetwork;
import android.net.wifi.IWifiManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkScoreCache;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -48,6 +51,7 @@
import com.android.settingslib.R;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -132,6 +136,9 @@
private Object mTag;
+ private int mRankingScore = Integer.MIN_VALUE;
+ private int mBadge = ScoredNetwork.BADGING_NONE;
+
// used to co-relate internal vs returned accesspoint.
int mId;
@@ -205,6 +212,8 @@
this.mScanResultCache.clear();
this.mScanResultCache.putAll(that.mScanResultCache);
this.mId = that.mId;
+ this.mBadge = that.mBadge;
+ this.mRankingScore = that.mRankingScore;
}
@Override
@@ -213,6 +222,11 @@
if (isActive() && !other.isActive()) return -1;
if (!isActive() && other.isActive()) return 1;
+ // Higher scores go before lower scores
+ if (mRankingScore != other.mRankingScore) {
+ return (mRankingScore > other.mRankingScore) ? -1 : 1;
+ }
+
// Reachable one goes before unreachable one.
if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1;
if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1;
@@ -268,9 +282,38 @@
if (security != SECURITY_NONE) {
builder.append(',').append(securityToString(security, pskType));
}
+ builder.append(",rankingScore=").append(mRankingScore);
+ builder.append(",badge=").append(mBadge);
+
return builder.append(')').toString();
}
+ /**
+ * Updates the AccessPoint rankingScore and badge, returning true if the data has changed.
+ *
+ * @param scoreCache The score cache to use to retrieve scores.
+ */
+ boolean updateScores(WifiNetworkScoreCache scoreCache) {
+ int oldBadge = mBadge;
+ int oldRankingScore = mRankingScore;
+ mBadge = ScoredNetwork.BADGING_NONE;
+ mRankingScore = Integer.MIN_VALUE;
+
+ for (ScanResult result : mScanResultCache.values()) {
+ ScoredNetwork score = scoreCache.getScoredNetwork(result);
+ if (score == null) {
+ continue;
+ }
+
+ if (score.hasRankingScore()) {
+ mRankingScore = Math.max(mRankingScore, score.calculateRankingScore(result.level));
+ }
+ mBadge = Math.max(mBadge, score.calculateBadge(result.level));
+ }
+
+ return (oldBadge != mBadge || oldRankingScore != mRankingScore);
+ }
+
private void evictOldScanResults() {
long nowMs = SystemClock.elapsedRealtime();
for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
@@ -555,6 +598,8 @@
visibility.append(" rssi=").append(mInfo.getRssi());
visibility.append(" ");
visibility.append(" score=").append(mInfo.score);
+ visibility.append(" rankingScore=").append(getRankingScore());
+ visibility.append(" badge=").append(getBadge());
visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
visibility.append(String.format("%.1f ", mInfo.txBadRate));
@@ -820,6 +865,14 @@
mRssi = rssi;
}
+ int getRankingScore() {
+ return mRankingScore;
+ }
+
+ int getBadge() {
+ return mBadge;
+ }
+
public static String getSummary(Context context, String ssid, DetailedState state,
boolean isEphemeral, String passpointProvider) {
if (state == DetailedState.CONNECTED && ssid == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index aae9cf6..703bc17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -18,8 +18,12 @@
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
+import android.net.ScoredNetwork;
import android.net.wifi.WifiConfiguration;
import android.os.Looper;
import android.os.UserHandle;
@@ -28,19 +32,29 @@
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseArray;
+import android.widget.ImageView;
import android.widget.TextView;
+
import com.android.settingslib.R;
+import com.android.settingslib.TronUtils;
+import com.android.settingslib.Utils;
public class AccessPointPreference extends Preference {
private static final int[] STATE_SECURED = {
R.attr.state_encrypted
};
+ private static final int[] STATE_SAVED = {
+ R.attr.state_encrypted,
+ R.attr.state_saved
+ };
private static final int[] STATE_NONE = {};
- private static int[] wifi_signal_attributes = { R.attr.wifi_signal };
+ private static final int[] wifi_signal_attributes = { R.attr.wifi_signal };
+ private static final int[] wifi_friction_attributes = { R.attr.wifi_friction };
private final StateListDrawable mWifiSld;
+ private final StateListDrawable mFrictionSld;
private final int mBadgePadding;
private final UserBadgeCache mBadgeCache;
private TextView mTitleView;
@@ -51,6 +65,9 @@
private int mLevel;
private CharSequence mContentDescription;
private int mDefaultIconResId;
+ private int mIconWidth;
+ private int mIconHeight;
+ private int mWifiBadge = ScoredNetwork.BADGING_NONE;
static final int[] WIFI_CONNECTION_STRENGTH = {
R.string.accessibility_wifi_one_bar,
@@ -63,6 +80,7 @@
public AccessPointPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mWifiSld = null;
+ mFrictionSld = null;
mBadgePadding = 0;
mBadgeCache = null;
}
@@ -70,6 +88,7 @@
public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache,
boolean forSavedNetworks) {
super(context);
+ setWidgetLayoutResource(R.layout.access_point_friction_widget);
mBadgeCache = cache;
mAccessPoint = accessPoint;
mForSavedNetworks = forSavedNetworks;
@@ -78,6 +97,17 @@
mWifiSld = (StateListDrawable) context.getTheme()
.obtainStyledAttributes(wifi_signal_attributes).getDrawable(0);
+ // Save icon width and height to use for creating a badged icon
+ setIconWidthAndHeight();
+
+ TypedArray frictionSld;
+ try {
+ frictionSld = context.getTheme().obtainStyledAttributes(wifi_friction_attributes);
+ } catch (Resources.NotFoundException e) {
+ // Fallback for platforms that do not need friction icon resources.
+ frictionSld = null;
+ }
+ mFrictionSld = frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null;
// Distance from the end of the title at which this AP's user badge should sit.
mBadgePadding = context.getResources()
@@ -88,6 +118,7 @@
public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache,
int iconResId, boolean forSavedNetworks) {
super(context);
+ setWidgetLayoutResource(R.layout.access_point_friction_widget);
mBadgeCache = cache;
mAccessPoint = accessPoint;
mForSavedNetworks = forSavedNetworks;
@@ -97,12 +128,30 @@
mWifiSld = (StateListDrawable) context.getTheme()
.obtainStyledAttributes(wifi_signal_attributes).getDrawable(0);
+ // Save icon width and height to use for creating a badged icon
+ setIconWidthAndHeight();
+
+ TypedArray frictionSld;
+ try {
+ frictionSld = context.getTheme().obtainStyledAttributes(wifi_friction_attributes);
+ } catch (Resources.NotFoundException e) {
+ // Fallback for platforms that do not need friction icon resources.
+ frictionSld = null;
+ }
+ mFrictionSld = frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null;
// Distance from the end of the title at which this AP's user badge should sit.
mBadgePadding = context.getResources()
.getDimensionPixelSize(R.dimen.wifi_preference_badge_padding);
}
+ private void setIconWidthAndHeight() {
+ // TODO(sghuman): Refactor this defined widths and heights into a dimension resource and
+ // reference directly.
+ mIconWidth = mWifiSld.getIntrinsicWidth();
+ mIconHeight = mWifiSld.getIntrinsicHeight();
+ }
+
public AccessPoint getAccessPoint() {
return mAccessPoint;
}
@@ -126,12 +175,28 @@
mTitleView.setCompoundDrawablePadding(mBadgePadding);
}
view.itemView.setContentDescription(mContentDescription);
+
+ if (!mForSavedNetworks) {
+ ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
+ bindFrictionImage(frictionImageView);
+ }
}
protected void updateIcon(int level, Context context) {
if (level == -1) {
safeSetDefaultIcon();
} else {
+ TronUtils.logWifiSettingsBadge(context, mWifiBadge);
+ if (mWifiBadge != ScoredNetwork.BADGING_NONE) {
+ // TODO(sghuman): Refactor this to reuse drawable to save memory and add to a
+ // special subclass of AccessPointPreference
+ LayerDrawable drawable = Utils.getBadgedWifiIcon(context, level, mWifiBadge);
+ drawable.setLayerSize(0, mIconWidth, mIconHeight);
+ drawable.setLayerSize(1, mIconWidth, mIconHeight);
+ drawable.setTint(Utils.getColorAccent(getContext()));
+ setIcon(drawable);
+ return;
+ }
if (getIcon() == null) {
// To avoid a drawing race condition, we first set the state (SECURE/NONE) and then
// set the icon (drawable) to that state's drawable.
@@ -152,6 +217,27 @@
}
}
+ /**
+ * Binds the friction icon drawable using a StateListDrawable.
+ *
+ * <p>Friction icons will be rebound when notifyChange() is called, and therefore
+ * do not need to be managed in refresh()</p>.
+ */
+ private void bindFrictionImage(ImageView frictionImageView) {
+ if (frictionImageView == null || mFrictionSld == null) {
+ return;
+ }
+ if (mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
+ if (mAccessPoint.isSaved()) {
+ mFrictionSld.setState(STATE_SAVED);
+ } else {
+ mFrictionSld.setState(STATE_SECURED);
+ }
+ }
+ Drawable drawable = mFrictionSld.getCurrent();
+ frictionImageView.setImageDrawable(drawable);
+ }
+
private void safeSetDefaultIcon() {
if (mDefaultIconResId != 0) {
setIcon(mDefaultIconResId);
@@ -182,11 +268,14 @@
final Context context = getContext();
int level = mAccessPoint.getLevel();
- if (level != mLevel) {
+ int wifiBadge = mAccessPoint.getBadge();
+ if (level != mLevel || wifiBadge != mWifiBadge) {
mLevel = level;
+ mWifiBadge = wifiBadge;
updateIcon(mLevel, context);
notifyChanged();
}
+
updateBadge(context);
setSummary(mForSavedNetworks ? mAccessPoint.getSavedNetworkSummary()
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/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 77a45b3..c617994 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -16,23 +16,33 @@
package com.android.settingslib.wifi;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkKey;
import android.net.NetworkRequest;
+import android.net.NetworkScoreManager;
+import android.net.ScoredNetwork;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.WifiNetworkScoreCache.CacheListener;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.provider.Settings;
+import android.support.annotation.WorkerThread;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -49,14 +59,18 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Tracks saved or available wifi networks and their state.
*/
public class WifiTracker {
+ // TODO(sghuman): Document remaining methods with @UiThread and @WorkerThread where possible.
+ // TODO(sghuman): Refactor to avoid calling certain methods on the UiThread.
+
private static final String TAG = "WifiTracker";
- private static final boolean DBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
/** verbose logging flag. this flag is set thru developer debugging options
* and used so as to assist with in-the-field WiFi connectivity debugging */
@@ -122,6 +136,12 @@
private NetworkInfo mLastNetworkInfo;
private WifiInfo mLastInfo;
+ private final NetworkScoreManager mNetworkScoreManager;
+ private final WifiNetworkScoreCache mScoreCache;
+ private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
+ private boolean mNetworkScoringUiEnabled;
+ private final ContentObserver mObserver;
+
@VisibleForTesting
Scanner mScanner;
@@ -144,14 +164,16 @@
boolean includeSaved, boolean includeScans, boolean includePasspoints) {
this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
context.getSystemService(WifiManager.class),
- context.getSystemService(ConnectivityManager.class), Looper.myLooper());
+ context.getSystemService(ConnectivityManager.class),
+ context.getSystemService(NetworkScoreManager.class), Looper.myLooper()
+ );
}
@VisibleForTesting
WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
boolean includeSaved, boolean includeScans, boolean includePasspoints,
WifiManager wifiManager, ConnectivityManager connectivityManager,
- Looper currentLooper) {
+ NetworkScoreManager networkScoreManager, Looper currentLooper) {
if (!includeSaved && !includeScans) {
throw new IllegalArgumentException("Must include either saved or scans");
}
@@ -186,6 +208,28 @@
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
+
+ mNetworkScoreManager = networkScoreManager;
+
+ mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
+ @Override
+ public void networkCacheUpdated(List<ScoredNetwork> networks) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Score cache was updated with networks: " + networks);
+ }
+ Message.obtain(mWorkHandler, WorkHandler.MSG_UPDATE_NETWORK_SCORES).sendToTarget();
+ }
+ });
+
+ mObserver = new ContentObserver(mWorkHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mNetworkScoringUiEnabled =
+ Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
+ }
+ };
}
/**
@@ -216,6 +260,8 @@
/**
* Resume scanning for wifi networks after it has been paused.
+ *
+ * <p>The score cache should be registered before this method is invoked.
*/
public void resumeScanning() {
if (mScanner == null) {
@@ -230,11 +276,25 @@
}
/**
- * Start tracking wifi networks.
- * Registers listeners and starts scanning for wifi networks. If this is not called
+ * Start tracking wifi networks and scores.
+ *
+ * <p>Registers listeners and starts scanning for wifi networks. If this is not called
* then forceUpdate() must be called to populate getAccessPoints().
*/
public void startTracking() {
+ mWorkHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ registerScoreCache();
+ }
+ });
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED),
+ false /* notifyForDescendants */,
+ mObserver);
+ mObserver.onChange(false /* selfChange */); // Set the initial value for mScoringUiEnabled
+
resumeScanning();
if (!mRegistered) {
mContext.registerReceiver(mReceiver, mFilter);
@@ -245,9 +305,28 @@
}
}
+ @WorkerThread
+ private void registerScoreCache() {
+ mNetworkScoreManager.registerNetworkScoreCache(
+ NetworkKey.TYPE_WIFI,
+ mScoreCache,
+ NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+ }
+
+ private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
+ if (keys.isEmpty()) return;
+
+ if (DBG) {
+ Log.d(TAG, "Requesting scores for Network Keys: " + keys);
+ }
+ mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
+ mRequestedScores.addAll(keys);
+ }
+
/**
- * Stop tracking wifi networks.
- * Unregisters all listeners and stops scanning for wifi networks. This should always
+ * Stop tracking wifi networks and scores.
+ *
+ * <p>Unregisters all listeners and stops scanning for wifi networks. This should always
* be called when done with a WifiTracker (if startTracking was called) to ensure
* proper cleanup.
*/
@@ -255,11 +334,27 @@
if (mRegistered) {
mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_NETWORK_INFO);
+ mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_NETWORK_SCORES);
mContext.unregisterReceiver(mReceiver);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mRegistered = false;
}
pauseScanning();
+
+ mWorkHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ unregisterAndClearScoreCache();
+ }
+ });
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ }
+
+ @WorkerThread
+ private void unregisterAndClearScoreCache() {
+ mRequestedScores.clear();
+ mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
+ mScoreCache.clearScores();
}
/**
@@ -415,6 +510,7 @@
}
}
+ final List<NetworkKey> scoresToRequest = new ArrayList<>();
if (results != null) {
for (ScanResult result : results) {
// Ignore hidden and ad-hoc networks.
@@ -423,6 +519,11 @@
continue;
}
+ NetworkKey key = NetworkKey.createFromScanResult(result);
+ if (!mRequestedScores.contains(key)) {
+ scoresToRequest.add(key);
+ }
+
boolean found = false;
for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
if (accessPoint.update(result)) {
@@ -457,27 +558,36 @@
}
}
+ if (mNetworkScoringUiEnabled) {
+ requestScoresForNetworkKeys(scoresToRequest);
+ for (AccessPoint ap : accessPoints) {
+ ap.updateScores(mScoreCache);
+ }
+ }
+
// Pre-sort accessPoints to speed preference insertion
Collections.sort(accessPoints);
// Log accesspoints that were deleted
- if (DBG) Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
- for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
- if (prevAccessPoint.getSsid() == null)
- continue;
- String prevSsid = prevAccessPoint.getSsidStr();
- boolean found = false;
- for (AccessPoint newAccessPoint : accessPoints) {
- if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid()
- .equals(prevSsid)) {
- found = true;
- break;
+ if (DBG) {
+ Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
+ for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
+ if (prevAccessPoint.getSsid() == null)
+ continue;
+ String prevSsid = prevAccessPoint.getSsidStr();
+ boolean found = false;
+ for (AccessPoint newAccessPoint : accessPoints) {
+ if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid()
+ .equals(prevSsid)) {
+ found = true;
+ break;
+ }
}
+ if (!found)
+ Log.d(TAG, "Did not find " + prevSsid + " in this scan");
}
- if (!found)
- if (DBG) Log.d(TAG, "Did not find " + prevSsid + " in this scan");
+ Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
}
- if (DBG) Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
mInternalAccessPoints.clear();
mInternalAccessPoints.addAll(accessPoints);
@@ -549,7 +659,42 @@
boolean reorder = false;
for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
- if (mInternalAccessPoints.get(i).update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
+ AccessPoint ap = mInternalAccessPoints.get(i);
+ if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
+ reorder = true;
+ }
+ if (mNetworkScoringUiEnabled && ap.updateScores(mScoreCache)) {
+ reorder = true;
+ }
+ }
+ if (reorder) {
+ Collections.sort(mInternalAccessPoints);
+ mMainHandler.scheduleAPCopyingAndCloseWriteLock();
+ }
+ }
+
+ /**
+ * Update all the internal access points rankingScores and badge.
+ *
+ * <p>Will trigger a resort and notify listeners of changes if applicable.
+ */
+ private void updateNetworkScores() {
+ if (!mNetworkScoringUiEnabled) {
+ return;
+ }
+
+ // Lock required to prevent accidental copying of AccessPoint states while the modification
+ // is in progress. see #copyAndNotifyListeners
+ long before = System.currentTimeMillis();
+ mInternalAccessPointsWriteLock.block();
+ if (DBG) {
+ Log.d(TAG, "Acquired AP lock on WorkerHandler for inserting NetworkScores. Wait time = " +
+ (System.currentTimeMillis() - before) + "ms.");
+ }
+
+ boolean reorder = false;
+ for (int i = 0; i < mInternalAccessPoints.size(); i++) {
+ if (mInternalAccessPoints.get(i).updateScores(mScoreCache)) {
reorder = true;
}
}
@@ -661,6 +806,7 @@
private static final int MSG_UPDATE_NETWORK_INFO = 1;
private static final int MSG_RESUME = 2;
private static final int MSG_UPDATE_WIFI_STATE = 3;
+ private static final int MSG_UPDATE_NETWORK_SCORES = 4;
public WorkHandler(Looper looper) {
super(looper);
@@ -695,6 +841,9 @@
mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0)
.sendToTarget();
break;
+ case MSG_UPDATE_NETWORK_SCORES:
+ updateNetworkScores();
+ break;
}
}
}
diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk
index 4208522..bd910dd 100644
--- a/packages/SettingsLib/tests/integ/Android.mk
+++ b/packages/SettingsLib/tests/integ/Android.mk
@@ -27,7 +27,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
espresso-core \
- mockito-target-minus-junit4
+ mockito-target-minus-junit4 \
+ legacy-android-test \
+ truth-prebuilt
include frameworks/base/packages/SettingsLib/common.mk
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index 00b2164..0d5ff2c 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
<uses-permission android:name="android.permission.SET_TIME_ZONE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application>
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/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index c650190..08736c7 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -15,29 +15,282 @@
*/
package com.android.settingslib.wifi;
-
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkKey;
+import android.net.NetworkScoreManager;
+import android.net.ScoredNetwork;
+import android.net.RssiCurve;
+import android.net.WifiKey;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.WifiSsid;
+import android.os.Bundle;
import android.os.SystemClock;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
-
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WifiTrackerTest {
+ private static final String TAG = "WifiTrackerTest";
+ private static final int LATCH_TIMEOUT = 2000;
+
+ private static final String SSID_1 = "ssid1";
+ private static final String BSSID_1 = "00:00:00:00:00:00";
+ private static final NetworkKey NETWORK_KEY_1 =
+ new NetworkKey(new WifiKey('"' + SSID_1 + '"', BSSID_1));
+ private static final int RSSI_1 = -30;
+ private static final byte SCORE_1 = 10;
+ private static final int BADGE_1 = ScoredNetwork.BADGING_SD;
+
+ private static final String SSID_2 = "ssid2";
+ private static final String BSSID_2 = "AA:AA:AA:AA:AA:AA";
+ private static final NetworkKey NETWORK_KEY_2 =
+ new NetworkKey(new WifiKey('"' + SSID_2 + '"', BSSID_2));
+ private static final int RSSI_2 = -30;
+ private static final byte SCORE_2 = 15;
+ private static final int BADGE_2 = ScoredNetwork.BADGING_HD;
+
+ @Captor ArgumentCaptor<WifiNetworkScoreCache> mScoreCacheCaptor;
+ @Mock private ConnectivityManager mockConnectivityManager;
+ @Mock private NetworkScoreManager mockNetworkScoreManager;
+ @Mock private RssiCurve mockCurve1;
+ @Mock private RssiCurve mockCurve2;
+ @Mock private RssiCurve mockBadgeCurve1;
+ @Mock private RssiCurve mockBadgeCurve2;
+ @Mock private WifiManager mockWifiManager;
+ @Mock private WifiTracker.WifiListener mockWifiListener;
+
+ private final List<NetworkKey> mRequestedKeys = new ArrayList<>();
+
+ private Context mContext;
+ private CountDownLatch mAccessPointsChangedLatch;
+ private CountDownLatch mRequestScoresLatch;
+ private Handler mScannerHandler;
+ private HandlerThread mMainThread;
+ private HandlerThread mWorkerThread;
+ private Looper mLooper;
+ private Looper mMainLooper;
+ private int mOriginalScoringUiSettingValue;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ MockitoAnnotations.initMocks(this);
+
+ mWorkerThread = new HandlerThread("TestHandlerWorkerThread");
+ mWorkerThread.start();
+ mLooper = mWorkerThread.getLooper();
+ mMainThread = new HandlerThread("TestHandlerThread");
+ mMainThread.start();
+ mMainLooper = mMainThread.getLooper();
+
+ // Make sure the scanner doesn't try to run on the testing thread.
+ HandlerThread scannerThread = new HandlerThread("ScannerWorkerThread");
+ scannerThread.start();
+ mScannerHandler = new Handler(scannerThread.getLooper());
+
+ when(mockWifiManager.isWifiEnabled()).thenReturn(true);
+ when(mockWifiManager.getScanResults())
+ .thenReturn(Arrays.asList(buildScanResult1(), buildScanResult2()));
+
+
+ when(mockCurve1.lookupScore(RSSI_1)).thenReturn(SCORE_1);
+ when(mockCurve2.lookupScore(RSSI_2)).thenReturn(SCORE_2);
+
+ when(mockBadgeCurve1.lookupScore(RSSI_1)).thenReturn((byte) BADGE_1);
+ when(mockBadgeCurve2.lookupScore(RSSI_2)).thenReturn((byte) BADGE_2);
+
+ doNothing()
+ .when(mockNetworkScoreManager)
+ .registerNetworkScoreCache(
+ anyInt(),
+ mScoreCacheCaptor.capture(),
+ Matchers.anyInt());
+
+ // 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(mockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any());
+
+ doAnswer(
+ new Answer<Void>() {
+ @Override
+ public Void answer (InvocationOnMock invocation) throws Throwable {
+ if (mAccessPointsChangedLatch != null) {
+ mAccessPointsChangedLatch.countDown();
+ }
+
+ return null;
+ }
+ }).when(mockWifiListener).onAccessPointsChanged();
+
+ // Turn on Scoring UI features
+ mOriginalScoringUiSettingValue = Settings.Global.getInt(
+ InstrumentationRegistry.getTargetContext().getContentResolver(),
+ Settings.Global.NETWORK_SCORING_UI_ENABLED,
+ 0 /* disabled */);
+ Settings.Global.putInt(
+ InstrumentationRegistry.getTargetContext().getContentResolver(),
+ Settings.Global.NETWORK_SCORING_UI_ENABLED,
+ 1 /* enabled */);
+ }
+
+ @After
+ public void cleanUp() {
+ Settings.Global.putInt(
+ InstrumentationRegistry.getTargetContext().getContentResolver(),
+ Settings.Global.NETWORK_SCORING_UI_ENABLED,
+ mOriginalScoringUiSettingValue);
+ }
+
+ private static ScanResult buildScanResult1() {
+ return new ScanResult(
+ WifiSsid.createFromAsciiEncoded(SSID_1),
+ BSSID_1,
+ 0, // hessid
+ 0, //anqpDomainId
+ null, // osuProviders
+ "", // capabilities
+ RSSI_1,
+ 0, // frequency
+ SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */);
+ }
+
+ private static ScanResult buildScanResult2() {
+ return new ScanResult(
+ WifiSsid.createFromAsciiEncoded(SSID_2),
+ BSSID_2,
+ 0, // hessid
+ 0, //anqpDomainId
+ null, // osuProviders
+ "", // capabilities
+ RSSI_2,
+ 0, // frequency
+ SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */);
+ }
+
+ private WifiTracker createTrackerAndInjectInitialScanResults() throws InterruptedException {
+ WifiTracker tracker = createMockedWifiTracker();
+ startTracking(tracker);
+
+ mAccessPointsChangedLatch = new CountDownLatch(1);
+ sendScanResultsAndProcess(tracker);
+ mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ return tracker;
+ }
+
+ private WifiTracker createMockedWifiTracker() {
+ WifiTracker tracker =
+ new WifiTracker(
+ mContext,
+ mockWifiListener,
+ mLooper,
+ true,
+ true,
+ true,
+ mockWifiManager,
+ mockConnectivityManager,
+ mockNetworkScoreManager, mMainLooper
+ );
+
+ return tracker;
+ }
+
+ private void startTracking(WifiTracker tracker) throws InterruptedException {
+ CountDownLatch latch = new CountDownLatch(1);
+ mScannerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ tracker.startTracking();
+ latch.countDown();
+ }
+ });
+ latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+
+ private void sendScanResultsAndProcess(WifiTracker tracker) throws InterruptedException {
+ mAccessPointsChangedLatch = new CountDownLatch(1);
+ Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ tracker.mReceiver.onReceive(mContext, i);
+
+ mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+
+ private void updateScores() {
+ Bundle attr1 = new Bundle();
+ attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve1);
+ ScoredNetwork sc1 =
+ new ScoredNetwork(
+ NETWORK_KEY_1,
+ mockCurve1,
+ false /* meteredHint */,
+ attr1);
+
+ Bundle attr2 = new Bundle();
+ attr2.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve2);
+ ScoredNetwork sc2 =
+ new ScoredNetwork(
+ NETWORK_KEY_2,
+ mockCurve2,
+ false /* meteredHint */,
+ attr2);
+
+ WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
+ scoreCache.updateScores(Arrays.asList(sc1, sc2));
+ }
+
@Test
public void testAccessPointListenerSetWhenLookingUpUsingScanResults() {
ScanResult scanResult = new ScanResult();
@@ -47,7 +300,7 @@
scanResult.capabilities = "";
WifiTracker tracker = new WifiTracker(
- InstrumentationRegistry.getTargetContext(), null, true, true);
+ InstrumentationRegistry.getTargetContext(), null, mLooper, true, true);
AccessPoint result = tracker.getCachedOrCreate(scanResult, new ArrayList<AccessPoint>());
assertTrue(result.mAccessPointListener != null);
@@ -63,9 +316,177 @@
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiTracker tracker = new WifiTracker(
- InstrumentationRegistry.getTargetContext(), null, true, true);
+ InstrumentationRegistry.getTargetContext(), null, mLooper, true, true);
AccessPoint result = tracker.getCachedOrCreate(configuration, new ArrayList<AccessPoint>());
assertTrue(result.mAccessPointListener != null);
}
+
+ @Test
+ public void startAndStopTrackingShouldRegisterAndUnregisterScoreCache()
+ throws InterruptedException {
+ WifiTracker tracker = createMockedWifiTracker();
+
+ // Test register
+ startTracking(tracker);
+ verify(mockNetworkScoreManager)
+ .registerNetworkScoreCache(
+ Matchers.anyInt(),
+ mScoreCacheCaptor.capture(),
+ Matchers.anyInt());
+
+ WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ doAnswer(
+ (invocation) -> {
+ latch.countDown();
+ return null;
+ }).when(mockNetworkScoreManager)
+ .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
+
+ // Test unregister
+ tracker.stopTracking();
+
+ latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+ verify(mockNetworkScoreManager)
+ .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
+ }
+
+ @Test
+ public void startTrackingShouldRequestScoresForCurrentAccessPoints() throws InterruptedException {
+ // Start the tracker and inject the initial scan results and then stop tracking
+ WifiTracker tracker = createTrackerAndInjectInitialScanResults();
+
+ tracker.stopTracking();
+ mRequestedKeys.clear();
+
+ mRequestScoresLatch = new CountDownLatch(2);
+ startTracking(tracker);
+ mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ assertTrue(mRequestedKeys.contains(NETWORK_KEY_1));
+ assertTrue(mRequestedKeys.contains(NETWORK_KEY_2));
+ }
+
+ @Test
+ public void scoreCacheUpdateScoresShouldTriggerOnAccessPointsChanged() throws InterruptedException {
+ WifiTracker tracker = createMockedWifiTracker();
+ startTracking(tracker);
+ sendScanResultsAndProcess(tracker);
+
+ updateScoresAndWaitForAccessPointsChangedCallback();
+ }
+
+ private void updateScoresAndWaitForAccessPointsChangedCallback() throws InterruptedException {
+ mAccessPointsChangedLatch = new CountDownLatch(2);
+ updateScores();
+ mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void scoreCacheUpdateScoresShouldChangeSortOrder() throws InterruptedException {
+ WifiTracker tracker = createTrackerAndInjectInitialScanResults();
+ List<AccessPoint> aps = tracker.getAccessPoints();
+ assertTrue(aps.size() == 2);
+ assertEquals(aps.get(0).getSsidStr(), SSID_1);
+ assertEquals(aps.get(1).getSsidStr(), SSID_2);
+
+ updateScoresAndWaitForAccessPointsChangedCallback();
+
+ aps = tracker.getAccessPoints();
+ assertTrue(aps.size() == 2);
+ assertEquals(aps.get(0).getSsidStr(), SSID_2);
+ assertEquals(aps.get(1).getSsidStr(), SSID_1);
+ }
+
+ @Test
+ public void scoreCacheUpdateScoresShouldNotChangeSortOrderWhenSortingDisabled()
+ throws InterruptedException {
+ Settings.Global.putInt(
+ InstrumentationRegistry.getTargetContext().getContentResolver(),
+ Settings.Global.NETWORK_SCORING_UI_ENABLED,
+ 0 /* disabled */);
+
+ WifiTracker tracker = createTrackerAndInjectInitialScanResults();
+ List<AccessPoint> aps = tracker.getAccessPoints();
+ assertTrue(aps.size() == 2);
+ assertEquals(aps.get(0).getSsidStr(), SSID_1);
+ assertEquals(aps.get(1).getSsidStr(), SSID_2);
+
+ updateScoresAndWaitForAccessPointsChangedCallback();
+
+ aps = tracker.getAccessPoints();
+ assertTrue(aps.size() == 2);
+ assertEquals(aps.get(0).getSsidStr(), SSID_1);
+ assertEquals(aps.get(1).getSsidStr(), SSID_2);
+ }
+
+ @Test
+ public void scoreCacheUpdateScoresShouldInsertBadgeIntoAccessPoint() throws InterruptedException {
+ WifiTracker tracker = createTrackerAndInjectInitialScanResults();
+ updateScoresAndWaitForAccessPointsChangedCallback();
+
+ List<AccessPoint> aps = tracker.getAccessPoints();
+
+ for (AccessPoint ap : aps) {
+ if (ap.getSsidStr().equals(SSID_1)) {
+ assertEquals(BADGE_1, ap.getBadge());
+ } else if (ap.getSsidStr().equals(SSID_2)) {
+ assertEquals(BADGE_2, ap.getBadge());
+ }
+ }
+ }
+
+ @Test
+ public void noBadgesShouldBeInsertedIntoAccessPointWhenScoringUiDisabled()
+ throws InterruptedException {
+ Settings.Global.putInt(
+ InstrumentationRegistry.getTargetContext().getContentResolver(),
+ Settings.Global.NETWORK_SCORING_UI_ENABLED,
+ 0 /* disabled */);
+
+ WifiTracker tracker = createTrackerAndInjectInitialScanResults();
+ updateScoresAndWaitForAccessPointsChangedCallback();
+
+ List<AccessPoint> aps = tracker.getAccessPoints();
+
+ for (AccessPoint ap : aps) {
+ if (ap.getSsidStr().equals(SSID_1)) {
+ assertEquals(ScoredNetwork.BADGING_NONE, ap.getBadge());
+ } else if (ap.getSsidStr().equals(SSID_2)) {
+ assertEquals(ScoredNetwork.BADGING_NONE, ap.getBadge());
+ }
+ }
+ }
+
+ @Test
+ public void scoresShouldBeRequestedForNewScanResultOnly() throws InterruptedException {
+ mRequestScoresLatch = new CountDownLatch(2);
+ WifiTracker tracker = createTrackerAndInjectInitialScanResults();
+ mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+ mRequestedKeys.clear();
+
+ String ssid = "ssid3";
+ String bssid = "00:00:00:00:00:00";
+ ScanResult newResult = new ScanResult(
+ WifiSsid.createFromAsciiEncoded(ssid),
+ bssid,
+ 0, // hessid
+ 0, //anqpDomainId
+ null, // osuProviders
+ "", // capabilities
+ RSSI_1,
+ 0, // frequency
+ SystemClock.elapsedRealtime() * 1000);
+ when(mockWifiManager.getScanResults())
+ .thenReturn(Arrays.asList(buildScanResult1(), buildScanResult2(), newResult));
+
+ mRequestScoresLatch = new CountDownLatch(1);
+ sendScanResultsAndProcess(tracker);
+ mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ assertEquals(1, mRequestedKeys.size());
+ assertTrue(mRequestedKeys.contains(new NetworkKey(new WifiKey('"' + ssid + '"', bssid))));
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index d8082c4..c95cac5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -255,6 +255,7 @@
Bundle bundle = new Bundle();
bundle.putInt("com.android.settings.icon", 161803);
+ bundle.putString("com.android.settings.icon_package", "abc");
bundle.putString("com.android.settings.summary", "dynamic-summary");
when(mIContentProvider.call(anyString(),
eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_ICON))), eq(URI_GET_ICON), any()))
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 058e38a..7a9ba20 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;
@@ -78,20 +80,25 @@
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
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>
@@ -175,6 +182,18 @@
private static final Bundle NULL_SETTING_BUNDLE = Bundle.forPair(
Settings.NameValueTable.VALUE, null);
+ // Changes to these global settings are synchronously persisted
+ private static final Set<String> CRITICAL_GLOBAL_SETTINGS = new ArraySet<>();
+ static {
+ CRITICAL_GLOBAL_SETTINGS.add(Settings.Global.DEVICE_PROVISIONED);
+ }
+
+ // Changes to these secure settings are synchronously persisted
+ private static final Set<String> CRITICAL_SECURE_SETTINGS = new ArraySet<>();
+ static {
+ CRITICAL_SECURE_SETTINGS.add(Settings.Secure.USER_SETUP_COMPLETE);
+ }
+
// Per user secure settings that moved to the for all users global settings.
static final Set<String> sSecureMovedToGlobalSettings = new ArraySet<>();
static {
@@ -271,6 +290,7 @@
@Override
public boolean onCreate() {
+ Settings.setInSystemServer();
synchronized (mLock) {
mUserManager = UserManager.get(getContext());
mPackageManager = AppGlobals.getPackageManager();
@@ -600,6 +620,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();
@@ -662,7 +698,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());
@@ -829,7 +865,8 @@
SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
- List<String> names = settingsState.getSettingNamesLocked();
+ List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_SYSTEM);
final int nameCount = names.size();
@@ -852,6 +889,9 @@
Slog.v(LOG_TAG, "getGlobalSetting(" + name + ")");
}
+ // Ensure the caller can access the setting.
+ enforceSettingReadable(name, SETTINGS_TYPE_GLOBAL, UserHandle.getCallingUserId());
+
// Get the value.
synchronized (mLock) {
return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_GLOBAL,
@@ -921,18 +961,18 @@
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
- getCallingPackage(), forceNotify);
+ getCallingPackage(), forceNotify, CRITICAL_GLOBAL_SETTINGS);
}
case MUTATION_OPERATION_DELETE: {
return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_GLOBAL,
- UserHandle.USER_SYSTEM, name, forceNotify);
+ UserHandle.USER_SYSTEM, name, forceNotify, CRITICAL_GLOBAL_SETTINGS);
}
case MUTATION_OPERATION_UPDATE: {
return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_GLOBAL,
UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
- getCallingPackage(), forceNotify);
+ getCallingPackage(), forceNotify, CRITICAL_GLOBAL_SETTINGS);
}
case MUTATION_OPERATION_RESET: {
@@ -954,8 +994,7 @@
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
synchronized (mLock) {
- List<String> names = mSettingsRegistry.getSettingsNamesLocked(
- SETTINGS_TYPE_SECURE, callingUserId);
+ List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SECURE, callingUserId);
final int nameCount = names.size();
@@ -973,7 +1012,7 @@
continue;
}
- // As of Android O (API 24), the SSAID is read from an app-specific entry in table
+ // As of Android O, the SSAID is read from an app-specific entry in table
// SETTINGS_FILE_SSAID, unless accessed by a system process.
final Setting setting;
if (isNewSsaidSetting(name)) {
@@ -997,6 +1036,9 @@
// Resolve the userId on whose behalf the call is made.
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+ // Ensure the caller can access the setting.
+ enforceSettingReadable(name, SETTINGS_TYPE_SECURE, callingUserId);
+
// Determine the owning user as some profile settings are cloned from the parent.
final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
@@ -1009,7 +1051,7 @@
// Get the value.
synchronized (mLock) {
- // As of Android O (API 24), the SSAID is read from an app-specific entry in table
+ // As of Android O, the SSAID is read from an app-specific entry in table
// SETTINGS_FILE_SSAID, unless accessed by a system process.
if (isNewSsaidSetting(name)) {
return getSsaidSettingLocked(owningUserId);
@@ -1039,7 +1081,7 @@
name);
// Lazy initialize ssaid if not yet present in ssaid table.
- if (ssaid.isNull() || ssaid.getValue() == null) {
+ if (ssaid == null || ssaid.isNull() || ssaid.getValue() == null) {
return mSettingsRegistry.generateSsaidLocked(getCallingPackage(), owningUserId);
}
@@ -1126,18 +1168,18 @@
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, name, value, tag, makeDefault,
- getCallingPackage(), forceNotify);
+ getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
}
case MUTATION_OPERATION_DELETE: {
return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SECURE,
- owningUserId, name, forceNotify);
+ owningUserId, name, forceNotify, CRITICAL_SECURE_SETTINGS);
}
case MUTATION_OPERATION_UPDATE: {
return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, name, value, tag, makeDefault,
- getCallingPackage(), forceNotify);
+ getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
}
case MUTATION_OPERATION_RESET: {
@@ -1159,8 +1201,7 @@
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
synchronized (mLock) {
- List<String> names = mSettingsRegistry.getSettingsNamesLocked(
- SETTINGS_TYPE_SYSTEM, callingUserId);
+ List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SYSTEM, callingUserId);
final int nameCount = names.size();
@@ -1191,6 +1232,9 @@
// Resolve the userId on whose behalf the call is made.
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+ // Ensure the caller can access the setting.
+ enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, callingUserId);
+
// Determine the owning user as some profile settings are cloned from the parent.
final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
@@ -1272,18 +1316,20 @@
case MUTATION_OPERATION_INSERT: {
validateSystemSettingValue(name, value);
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
- owningUserId, name, value, null, false, getCallingPackage(), false);
+ owningUserId, name, value, null, false, getCallingPackage(),
+ false, null);
}
case MUTATION_OPERATION_DELETE: {
return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM,
- owningUserId, name, false);
+ owningUserId, name, false, null);
}
case MUTATION_OPERATION_UPDATE: {
validateSystemSettingValue(name, value);
return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM,
- owningUserId, name, value, null, false, getCallingPackage(), false);
+ owningUserId, name, value, null, false, getCallingPackage(),
+ false, null);
}
}
@@ -1409,9 +1455,15 @@
&& (parentId = getGroupParentLocked(userId)) != userId) {
// The setting has a dependency and the profile has a parent
String dependency = sSystemCloneFromParentOnDependency.get(setting);
- Setting settingObj = getSecureSetting(dependency, userId);
- if (settingObj != null && settingObj.getValue().equals("1")) {
- return parentId;
+ // Lookup the dependency setting as ourselves, some callers may not have access to it.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Setting settingObj = getSecureSetting(dependency, userId);
+ if (settingObj != null && settingObj.getValue().equals("1")) {
+ return parentId;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
return resolveOwningUserIdLocked(userId, sSystemCloneToManagedSettings, setting);
@@ -1479,6 +1531,55 @@
}
}
+ private Set<String> getEphemeralAccessibleSettings(int settingsType) {
+ switch (settingsType) {
+ case SETTINGS_TYPE_GLOBAL:
+ return Settings.Global.EPHEMERAL_SETTINGS;
+ case SETTINGS_TYPE_SECURE:
+ return Settings.Secure.EPHEMERAL_SETTINGS;
+ case SETTINGS_TYPE_SYSTEM:
+ return Settings.System.EPHEMERAL_SETTINGS;
+ default:
+ throw new IllegalArgumentException("Invalid settings type: " + settingsType);
+ }
+ }
+
+ private List<String> getSettingsNamesLocked(int settingsType, int userId) {
+ ApplicationInfo ai = getCallingApplicationInfoOrThrow(userId);
+ if (ai.isEphemeralApp()) {
+ return new ArrayList<String>(getEphemeralAccessibleSettings(settingsType));
+ } else {
+ return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
+ }
+ }
+
+ private void enforceSettingReadable(String settingName, int settingsType, int userId) {
+ if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) {
+ return;
+ }
+ ApplicationInfo ai = getCallingApplicationInfoOrThrow(userId);
+ if (!ai.isEphemeralApp()) {
+ return;
+ }
+ if (!getEphemeralAccessibleSettings(settingsType).contains(settingName)) {
+ throw new SecurityException("Setting " + settingName + " is not accessible from"
+ + " ephemeral package " + getCallingPackage());
+ }
+ }
+
+ private ApplicationInfo getCallingApplicationInfoOrThrow(int userId) {
+ ApplicationInfo ai = null;
+ try {
+ ai = mPackageManager.getApplicationInfo(getCallingPackage(), 0 , userId);
+ } catch (RemoteException ignored) {
+ }
+ if (ai == null) {
+ throw new IllegalStateException("Failed to lookup info for package "
+ + getCallingPackage());
+ }
+ return ai;
+ }
+
private PackageInfo getCallingPackageInfoOrThrow(int userId) {
try {
PackageInfo packageInfo = mPackageManager.getPackageInfo(
@@ -1548,7 +1649,7 @@
value = value.substring(1);
Setting settingValue = getSecureSetting(
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED, owningUserId);
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED, owningUserId);
if (settingValue == null) {
return false;
}
@@ -1602,7 +1703,7 @@
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders,
- tag, makeDefault, getCallingPackage(), forceNotify);
+ tag, makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
}
private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
@@ -1895,12 +1996,12 @@
private void generateUserKeyLocked(int userId) {
// Generate a random key for each user used for creating a new ssaid.
- final byte[] keyBytes = new byte[16];
+ final byte[] keyBytes = new byte[32];
final SecureRandom rand = new SecureRandom();
rand.nextBytes(keyBytes);
// Convert to string for storage in settings table.
- final String userKey = ByteStringUtils.toString(keyBytes);
+ final String userKey = ByteStringUtils.toHexString(keyBytes);
// Store the key in the ssaid table.
final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
@@ -1912,6 +2013,10 @@
}
}
+ private byte[] getLengthPrefix(byte[] data) {
+ return ByteBuffer.allocate(4).putInt(data.length).array();
+ }
+
public Setting generateSsaidLocked(String packageName, int userId) {
final PackageInfo packageInfo;
try {
@@ -1923,36 +2028,50 @@
// Read the user's key from the ssaid table.
Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);
- if (userKeySetting.isNull() || userKeySetting.getValue() == null) {
+ if (userKeySetting == null || userKeySetting.isNull()
+ || userKeySetting.getValue() == null) {
// Lazy initialize and store the user key.
generateUserKeyLocked(userId);
userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);
- if (userKeySetting.isNull() || userKeySetting.getValue() == null) {
+ if (userKeySetting == null || userKeySetting.isNull()
+ || userKeySetting.getValue() == null) {
throw new IllegalStateException("User key not accessible");
}
}
final String userKey = userKeySetting.getValue();
// Convert the user's key back to a byte array.
- final byte[] keyBytes = ByteStringUtils.toByteArray(userKey);
- if (keyBytes == null || keyBytes.length != 16) {
+ final byte[] keyBytes = ByteStringUtils.fromHexToByteArray(userKey);
+
+ // Validate that the key is of expected length.
+ // Keys are currently 32 bytes, but were once 16 bytes during Android O development.
+ if (keyBytes == null || (keyBytes.length != 16 && keyBytes.length != 32)) {
throw new IllegalStateException("User key invalid");
}
- final MessageDigest md;
+ final Mac m;
try {
- // Hash package name and signature.
- md = MessageDigest.getInstance("SHA-256");
+ m = Mac.getInstance("HmacSHA256");
+ m.init(new SecretKeySpec(keyBytes, m.getAlgorithm()));
} catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("HmacSHA256 is not available");
+ throw new IllegalStateException("HmacSHA256 is not available", e);
+ } catch (InvalidKeyException e) {
+ throw new IllegalStateException("Key is corrupted", e);
}
- md.update(keyBytes);
- md.update(packageInfo.packageName.getBytes(StandardCharsets.UTF_8));
- md.update(packageInfo.signatures[0].toByteArray());
+
+ // Mac the package name and each of the signatures.
+ byte[] packageNameBytes = packageInfo.packageName.getBytes(StandardCharsets.UTF_8);
+ m.update(getLengthPrefix(packageNameBytes), 0, 4);
+ m.update(packageNameBytes);
+ for (int i = 0; i < packageInfo.signatures.length; i++) {
+ byte[] sig = packageInfo.signatures[i].toByteArray();
+ m.update(getLengthPrefix(sig), 0, 4);
+ m.update(sig);
+ }
// Convert result to a string for storage in settings table. Only want first 64 bits.
- final String ssaid = ByteStringUtils.toString(md.digest()).substring(0, 16)
- .toLowerCase();
+ final String ssaid = ByteStringUtils.toHexString(m.doFinal()).substring(0, 16)
+ .toLowerCase(Locale.US);
// Save the ssaid in the ssaid table.
final String uid = Integer.toString(packageInfo.applicationInfo.uid);
@@ -2129,7 +2248,8 @@
}
public boolean insertSettingLocked(int type, int userId, String name, String value,
- String tag, boolean makeDefault, String packageName, boolean forceNotify) {
+ String tag, boolean makeDefault, String packageName, boolean forceNotify,
+ Set<String> criticalSettings) {
final int key = makeKey(type, userId);
boolean success = false;
@@ -2139,13 +2259,18 @@
tag, makeDefault, packageName);
}
+ if (success && criticalSettings != null && criticalSettings.contains(name)) {
+ settingsState.persistSyncLocked();
+ }
+
if (forceNotify || success) {
notifyForSettingsChange(key, name);
}
return success;
}
- public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify) {
+ public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify,
+ Set<String> criticalSettings) {
final int key = makeKey(type, userId);
boolean success = false;
@@ -2154,26 +2279,19 @@
success = settingsState.deleteSettingLocked(name);
}
+ if (success && criticalSettings != null && criticalSettings.contains(name)) {
+ settingsState.persistSyncLocked();
+ }
+
if (forceNotify || success) {
notifyForSettingsChange(key, name);
}
return success;
}
- public Setting getSettingLocked(int type, int userId, String name) {
- final int key = makeKey(type, userId);
-
- SettingsState settingsState = peekSettingsStateLocked(key);
- if (settingsState == null) {
- return settingsState.getNullSetting();
- }
-
- // getSettingLocked will return non-null result
- return settingsState.getSettingLocked(name);
- }
-
public boolean updateSettingLocked(int type, int userId, String name, String value,
- String tag, boolean makeDefault, String packageName, boolean forceNotify) {
+ String tag, boolean makeDefault, String packageName, boolean forceNotify,
+ Set<String> criticalSettings) {
final int key = makeKey(type, userId);
boolean success = false;
@@ -2183,6 +2301,10 @@
makeDefault, packageName);
}
+ if (success && criticalSettings != null && criticalSettings.contains(name)) {
+ settingsState.persistSyncLocked();
+ }
+
if (forceNotify || success) {
notifyForSettingsChange(key, name);
}
@@ -2190,6 +2312,18 @@
return success;
}
+ public Setting getSettingLocked(int type, int userId, String name) {
+ final int key = makeKey(type, userId);
+
+ SettingsState settingsState = peekSettingsStateLocked(key);
+ if (settingsState == null) {
+ return null;
+ }
+
+ // getSettingLocked will return non-null result
+ return settingsState.getSettingLocked(name);
+ }
+
public void resetSettingsLocked(int type, int userId, String packageName, int mode,
String tag) {
final int key = makeKey(type, userId);
@@ -2201,56 +2335,78 @@
switch (mode) {
case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
for (String name : settingsState.getSettingNamesLocked()) {
+ boolean someSettingChanged = false;
Setting setting = settingsState.getSettingLocked(name);
if (packageName.equals(setting.getPackageName())) {
if (tag != null && !tag.equals(setting.getTag())) {
continue;
}
- if (settingsState.resetSettingLocked(name, packageName)) {
+ if (settingsState.resetSettingLocked(name)) {
+ someSettingChanged = true;
notifyForSettingsChange(key, name);
}
}
+ if (someSettingChanged) {
+ settingsState.persistSyncLocked();
+ }
}
} break;
case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: {
for (String name : settingsState.getSettingNamesLocked()) {
+ boolean someSettingChanged = false;
Setting setting = settingsState.getSettingLocked(name);
if (!SettingsState.isSystemPackage(getContext(),
setting.getPackageName())) {
- if (settingsState.resetSettingLocked(name, packageName)) {
+ if (settingsState.resetSettingLocked(name)) {
+ someSettingChanged = true;
notifyForSettingsChange(key, name);
}
}
+ if (someSettingChanged) {
+ settingsState.persistSyncLocked();
+ }
}
} break;
case Settings.RESET_MODE_UNTRUSTED_CHANGES: {
for (String name : settingsState.getSettingNamesLocked()) {
+ boolean someSettingChanged = false;
Setting setting = settingsState.getSettingLocked(name);
if (!SettingsState.isSystemPackage(getContext(),
setting.getPackageName())) {
- if (setting.isDefaultSystemSet()) {
- if (settingsState.resetSettingLocked(name, packageName)) {
+ if (setting.isDefaultFromSystem()) {
+ if (settingsState.resetSettingLocked(name)) {
+ someSettingChanged = true;
notifyForSettingsChange(key, name);
}
} else if (settingsState.deleteSettingLocked(name)) {
+ someSettingChanged = true;
notifyForSettingsChange(key, name);
}
}
+ if (someSettingChanged) {
+ settingsState.persistSyncLocked();
+ }
}
} break;
case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
for (String name : settingsState.getSettingNamesLocked()) {
Setting setting = settingsState.getSettingLocked(name);
- if (setting.isDefaultSystemSet()) {
- if (settingsState.resetSettingLocked(name, packageName)) {
+ boolean someSettingChanged = false;
+ if (setting.isDefaultFromSystem()) {
+ if (settingsState.resetSettingLocked(name)) {
+ someSettingChanged = true;
notifyForSettingsChange(key, name);
}
} else if (settingsState.deleteSettingLocked(name)) {
+ someSettingChanged = true;
notifyForSettingsChange(key, name);
}
+ if (someSettingChanged) {
+ settingsState.persistSyncLocked();
+ }
}
} break;
}
@@ -2999,8 +3155,13 @@
// user data or first boot on a new device should use new ssaid generation.
if (isUpgrade) {
// Retrieve the legacy ssaid from the secure settings table.
- final String legacySsaid = getSettingLocked(SETTINGS_TYPE_SECURE, userId,
- Settings.Secure.ANDROID_ID).getValue();
+ final Setting legacySsaidSetting = getSettingLocked(SETTINGS_TYPE_SECURE,
+ userId, Settings.Secure.ANDROID_ID);
+ if (legacySsaidSetting == null || legacySsaidSetting.isNull()
+ || legacySsaidSetting.getValue() == null) {
+ throw new IllegalStateException("Legacy ssaid not accessible");
+ }
+ final String legacySsaid = legacySsaidSetting.getValue();
// Fill each uid with the legacy ssaid to be backwards compatible.
final List<PackageInfo> packages;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
index fecc938..a6fadf9 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);
}
}
@@ -106,7 +113,7 @@
String mKey = null;
String mValue = null;
String mPackageName = null;
- String mToken = null;
+ String mTag = null;
int mResetMode = -1;
boolean mMakeDefault;
@@ -178,7 +185,7 @@
if (peekNextArg() == null) {
valid = true;
} else {
- mToken = getNextArg();
+ mTag = getNextArg();
if (peekNextArg() == null) {
valid = true;
} else {
@@ -211,10 +218,10 @@
// what we have so far is a valid command
valid = true;
// keep going; there may be another PUT arg
- } else if (mToken == null) {
- mToken = arg;
- if ("default".equalsIgnoreCase(mToken)) {
- mToken = null;
+ } else if (mTag == null) {
+ mTag = arg;
+ if ("default".equalsIgnoreCase(mTag)) {
+ mTag = null;
mMakeDefault = true;
if (peekNextArg() == null) {
valid = true;
@@ -275,7 +282,7 @@
pout.println(getForUser(iprovider, mUser, mTable, mKey));
break;
case PUT:
- putForUser(iprovider, mUser, mTable, mKey, mValue, mToken, mMakeDefault);
+ putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault);
break;
case DELETE:
pout.println("Deleted "
@@ -287,7 +294,7 @@
}
break;
case RESET:
- resetForUser(iprovider, mUser, mTable, mToken);
+ resetForUser(iprovider, mUser, mTable, mTag);
break;
default:
perr.println("Unspecified command");
@@ -351,7 +358,7 @@
}
void putForUser(IContentProvider provider, int userHandle, final String table,
- final String key, final String value, String token, boolean makeDefault) {
+ final String key, final String value, String tag, boolean makeDefault) {
final String callPutCommand;
if ("system".equals(table)) {
callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM;
@@ -371,7 +378,9 @@
Bundle arg = new Bundle();
arg.putString(Settings.NameValueTable.VALUE, value);
arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
- arg.putString(Settings.CALL_METHOD_TAG_KEY, token);
+ if (tag != null) {
+ arg.putString(Settings.CALL_METHOD_TAG_KEY, tag);
+ }
if (makeDefault) {
arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
@@ -402,7 +411,7 @@
}
void resetForUser(IContentProvider provider, int userHandle,
- String table, String token) {
+ String table, String tag) {
final String callResetCommand;
if ("secure".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_SECURE;
else if ("global".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_GLOBAL;
@@ -415,7 +424,9 @@
Bundle arg = new Bundle();
arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
arg.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, mResetMode);
- arg.putString(Settings.CALL_METHOD_TAG_KEY, token);
+ if (tag != null) {
+ arg.putString(Settings.CALL_METHOD_TAG_KEY, tag);
+ }
String packageName = mPackageName != null ? mPackageName : resolveCallingPackage();
arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
provider.call(packageName, callResetCommand, null, arg);
@@ -449,17 +460,18 @@
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");
pw.println(" Print this help text.");
pw.println(" get [--user <USER_ID> | current] NAMESPACE KEY");
pw.println(" Retrieve the current value of KEY.");
- pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TOKEN] [default]");
+ pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default]");
pw.println(" Change the contents of KEY to VALUE.");
- pw.println(" TOKEN to associate with the setting.");
+ pw.println(" TAG to associate with the setting.");
pw.println(" {default} to set as the default, case-insensitive only for global/secure namespace");
pw.println(" delete NAMESPACE KEY");
pw.println(" Delete the entry for KEY.");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 8f37b98..56ae618 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;
@@ -55,8 +64,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
-
-import static android.os.Process.FIRST_APPLICATION_UID;
+import java.util.Set;
/**
* This class contains the state for one type of settings. It is responsible
@@ -122,7 +130,7 @@
private static final String HISTORICAL_OPERATION_INITIALIZE = "initialize";
private static final String HISTORICAL_OPERATION_RESET = "reset";
- private static final String SHELL_PACKAGE_NAME = "shell";
+ private static final String SHELL_PACKAGE_NAME = "com.android.shell";
private static final String ROOT_PACKAGE_NAME = "root";
private static final String NULL_VALUE = "null";
@@ -300,7 +308,7 @@
Setting newState;
if (oldState != null) {
- if (!oldState.update(value, makeDefault, packageName, tag)) {
+ if (!oldState.update(value, makeDefault, packageName, tag, false)) {
return false;
}
newState = oldState;
@@ -344,7 +352,7 @@
}
// The settings provider must hold its lock when calling here.
- public boolean resetSettingLocked(String name, String packageName) {
+ public boolean resetSettingLocked(String name) {
if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) {
return false;
}
@@ -355,7 +363,7 @@
String oldValue = setting.getValue();
String oldDefaultValue = setting.getDefaultValue();
- if (!setting.reset(packageName)) {
+ if (!setting.reset()) {
return false;
}
@@ -404,6 +412,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 +584,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 +803,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,14 +811,14 @@
defaultValue = other.defaultValue;
packageName = other.packageName;
id = other.id;
- defaultSystemSet = other.defaultSystemSet;
+ defaultFromSystem = other.defaultFromSystem;
tag = other.tag;
}
public Setting(String name, String value, boolean makeDefault, String packageName,
String tag) {
this.name = name;
- update(value, makeDefault, packageName, tag);
+ update(value, makeDefault, packageName, tag, false);
}
public Setting(String name, String value, String defaultValue,
@@ -798,7 +838,7 @@
this.defaultValue = defaultValue;
this.packageName = packageName;
this.id = id;
- this.defaultSystemSet = fromSystem;
+ this.defaultFromSystem = fromSystem;
}
public String getName() {
@@ -825,8 +865,8 @@
return packageName;
}
- public boolean isDefaultSystemSet() {
- return defaultSystemSet;
+ public boolean isDefaultFromSystem() {
+ return defaultFromSystem;
}
public String getId() {
@@ -838,38 +878,40 @@
}
/** @return whether the value changed */
- public boolean reset(String packageName) {
- return update(this.defaultValue, false, packageName, null);
+ public boolean reset() {
+ return update(this.defaultValue, false, packageName, null, true);
}
- public boolean update(String value, boolean setDefault, String packageName, String tag) {
+ public boolean update(String value, boolean setDefault, String packageName, String tag,
+ boolean forceNonSystemPackage) {
if (NULL_VALUE.equals(value)) {
value = null;
}
- final boolean callerSystem = !isNull() && isSystemPackage(mContext, packageName);
+ final boolean callerSystem = !forceNonSystemPackage &&
+ !isNull() && isSystemPackage(mContext, packageName);
// Settings set by the system are always defaults.
if (callerSystem) {
setDefault = true;
}
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 +921,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 +934,7 @@
return "Setting{name=" + name + " value=" + value
+ (defaultValue != null ? " default=" + defaultValue : "")
+ " packageName=" + packageName + " tag=" + tag
- + " defaultSystemSet=" + defaultSystemSet + "}";
+ + " defaultFromSystem=" + defaultFromSystem + "}";
}
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
index 0454b51..ab23af3 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
@@ -206,19 +206,22 @@
resetToDefaultsViaShell(type, packageName, null);
}
- protected static void resetToDefaultsViaShell(int type, String packageName, String token)
+ protected static void resetToDefaultsViaShell(int type, String packageName, String tag)
throws IOException {
switch (type) {
case SETTING_TYPE_GLOBAL: {
- executeShellCommand("settings reset global " + packageName + " " + token);
+ executeShellCommand("settings reset global " + packageName + " "
+ + (tag != null ? tag : ""));
} break;
case SETTING_TYPE_SECURE: {
- executeShellCommand("settings reset secure " + packageName + " " + token);
+ executeShellCommand("settings reset secure " + packageName + " "
+ + (tag != null ? tag : ""));
} break;
case SETTING_TYPE_SYSTEM: {
- executeShellCommand("settings reset system " + packageName + " " + token);
+ executeShellCommand("settings reset system " + packageName + " "
+ + (tag != null ? tag : ""));
} break;
default: {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index ff76c56..4b8734f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -60,6 +60,7 @@
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+ <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
<uses-permission android:name="android.permission.CONTROL_VPN" />
<uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
<!-- Physical hardware -->
@@ -130,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" />
@@ -289,7 +293,7 @@
android:resumeWhilePausing="true"
android:screenOrientation="behind"
android:resizeableActivity="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
android:theme="@style/RecentsTheme.Wallpaper">
<intent-filter>
<action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
@@ -496,7 +500,6 @@
android:label="@string/accessibility_desc_work_lock"
android:permission="android.permission.MANAGE_USERS"
android:exported="false"
- android:launchMode="singleTop"
android:excludeFromRecents="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
index b31b199..e75ecb7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
@@ -64,7 +64,7 @@
* new PluginListener<OverlayPlugin>() {
* @Override
* public void onPluginConnected(OverlayPlugin plugin) {
- * PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
+ * StatusBar phoneStatusBar = getComponent(StatusBar.class);
* if (phoneStatusBar != null) {
* plugin.setup(phoneStatusBar.getStatusBarWindow(),
* phoneStatusBar.getNavigationBarView());
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..d71b6bd 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -51,6 +51,9 @@
private static final String TAG = "PluginInstanceManager";
private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
+ // must be one of the channels created in NotificationChannels.java
+ private static final String NOTIFICATION_CHANNEL_ID = "ALR";
+
private final Context mContext;
private final PluginListener<T> mListener;
private final String mAction;
@@ -177,8 +180,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");
@@ -308,7 +315,7 @@
.setSmallIcon(icon)
.setWhen(0)
.setShowWhen(false)
- .setPriority(Notification.PRIORITY_MAX)
+ .setChannel(NOTIFICATION_CHANNEL_ID)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setColor(mContext.getColor(color));
String label = cls;
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/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
new file mode 100644
index 0000000..93ba39c
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
@@ -0,0 +1,90 @@
+
+package com.android.systemui.plugins.statusbar;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+import java.util.ArrayList;
+
+import com.android.systemui.plugins.Plugin;
+
+public interface NotificationMenuRowProvider extends Plugin {
+
+ public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
+
+ public static final int VERSION = 1;
+
+ /**
+ * Returns a list of items to populate the menu 'behind' a notification.
+ */
+ public ArrayList<MenuItem> getMenuItems(Context context);
+
+ public interface OnMenuClickListener {
+ public void onMenuClicked(View row, int x, int y, MenuItem menu);
+
+ public void onMenuReset(View row);
+ }
+
+ public interface GutsInteractionListener {
+ public void onInteraction(View view);
+
+ public void closeGuts(View view);
+ }
+
+ public interface GutsContent {
+ public void setInteractionListener(GutsInteractionListener listener);
+
+ public View getContentView();
+
+ public boolean handleCloseControls();
+ }
+
+ public interface SnoozeGutsContent extends GutsContent {
+ public void setSnoozeListener(SnoozeListener listener);
+
+ public void setStatusBarNotification(StatusBarNotification sbn);
+ }
+
+ public interface SnoozeListener {
+ public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption);
+ }
+
+ public static class MenuItem {
+ public Drawable icon;
+ public String menuDescription;
+ public View menuView;
+ public GutsContent gutsContent;
+
+ public MenuItem(Drawable i, String s, GutsContent content) {
+ icon = i;
+ menuDescription = s;
+ gutsContent = content;
+ }
+
+ public View getGutsView() {
+ return gutsContent.getContentView();
+ }
+
+ public boolean onTouch(View v, int x, int y) {
+ return false;
+ }
+ }
+
+ public static class SnoozeOption {
+ public SnoozeCriterion criterion;
+ public int snoozeForMinutes;
+ public CharSequence description;
+ public CharSequence confirmation;
+
+ public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc,
+ CharSequence confirm) {
+ criterion = crit;
+ snoozeForMinutes = minsToSnoozeFor;
+ description = desc;
+ confirmation = confirm;
+ }
+ }
+}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 364885a..6d76798 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -11,7 +11,7 @@
}
-keep class com.android.systemui.statusbar.car.CarStatusBar
--keep class com.android.systemui.statusbar.phone.PhoneStatusBar
+-keep class com.android.systemui.statusbar.phone.StatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
-keep class com.android.systemui.car.CarSystemUIFactory
-keep class com.android.systemui.SystemUIFactory
diff --git a/packages/SystemUI/res/anim/ic_dnd_disable_alpha_animation.xml b/packages/SystemUI/res/anim/ic_dnd_disable_alpha_animation.xml
deleted file mode 100644
index 21caab4..0000000
--- a/packages/SystemUI/res/anim/ic_dnd_disable_alpha_animation.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set
- xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.3"
- android:valueType="floatType"
- android:interpolator="@android:interpolator/linear" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_dnd_total_silence_disable_alpha_animation.xml b/packages/SystemUI/res/anim/ic_dnd_total_silence_disable_alpha_animation.xml
deleted file mode 100644
index 21caab4..0000000
--- a/packages/SystemUI/res/anim/ic_dnd_total_silence_disable_alpha_animation.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set
- xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.3"
- android:valueType="floatType"
- android:interpolator="@android:interpolator/linear" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_hotspot_disable_animation_root.xml b/packages/SystemUI/res/anim/ic_hotspot_disable_animation_root.xml
deleted file mode 100644
index 770c401..0000000
--- a/packages/SystemUI/res/anim/ic_hotspot_disable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.3"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_hotspot_enable_animation_root.xml b/packages/SystemUI/res/anim/ic_hotspot_enable_animation_root.xml
deleted file mode 100644
index 387ca29..0000000
--- a/packages/SystemUI/res/anim/ic_hotspot_enable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.3"
- android:valueTo="1.0"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_root.xml b/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_root.xml
deleted file mode 100644
index 770c401..0000000
--- a/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.3"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_root.xml b/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_root.xml
deleted file mode 100644
index 387ca29..0000000
--- a/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.3"
- android:valueTo="1.0"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_root.xml b/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_root.xml
deleted file mode 100644
index 770c401..0000000
--- a/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.3"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_root.xml b/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_root.xml
deleted file mode 100644
index 387ca29..0000000
--- a/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.3"
- android:valueTo="1.0"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_root.xml b/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_root.xml
deleted file mode 100644
index 770c401..0000000
--- a/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.3"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_root.xml b/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_root.xml
deleted file mode 100644
index 387ca29..0000000
--- a/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.3"
- android:valueTo="1.0"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_location_disable_animation_root.xml b/packages/SystemUI/res/anim/ic_signal_location_disable_animation_root.xml
deleted file mode 100644
index 770c401..0000000
--- a/packages/SystemUI/res/anim/ic_signal_location_disable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.3"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_location_enable_animation_root.xml b/packages/SystemUI/res/anim/ic_signal_location_enable_animation_root.xml
deleted file mode 100644
index 387ca29..0000000
--- a/packages/SystemUI/res/anim/ic_signal_location_enable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.3"
- android:valueTo="1.0"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_workmode_disable_animation_root.xml b/packages/SystemUI/res/anim/ic_signal_workmode_disable_animation_root.xml
deleted file mode 100644
index 72834c9..0000000
--- a/packages/SystemUI/res/anim/ic_signal_workmode_disable_animation_root.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.3"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml
index 5e08fcb..abafb68 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2016 The Android Open Source Project
+Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,20 +19,22 @@
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:fillColor="#00796B"
- android:pathData="M32.0,12.5l0.0,28.0l12.0,-5.0l0.0,-28.0z"/>
+ android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
+ android:fillAlpha="0.066"
+ android:fillColor="#000000"/>
<path
- android:fillColor="#00796B"
- android:pathData="M4.0,40.5l12.0,-5.0l0.0,-11.0l-12.0,-12.0z"/>
+ android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
+ android:fillColor="#FFC107"/>
<path
- android:fillColor="#40000000"
- android:pathData="M44.0,35.5l-12.0,-12.0l0.0,-4.0z"/>
+ android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
+ android:fillColor="#FE9F00"/>
<path
- android:fillColor="#40000000"
- android:pathData="M4.0,12.5l12.0,12.0l0.0,4.0z"/>
+ android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
+ android:fillColor="#FED44F"/>
<path
- android:fillColor="#4DB6AC"
- android:pathData="M32.0,23.5l-16.0,-16.0l-12.0,5.0l0.0,0.0l12.0,12.0l16.0,16.0l12.0,-5.0l0.0,0.0z"/>
+ android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
+ android:fillColor="#FFC107"/>
+ <path
+ android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
+ android:fillColor="#FFFFFF"/>
</vector>
-
-
diff --git a/packages/SystemUI/res/drawable/ic_access_alarms_small.xml b/packages/SystemUI/res/drawable/ic_access_alarms_small.xml
index 994274a..b94cc8e 100644
--- a/packages/SystemUI/res/drawable/ic_access_alarms_small.xml
+++ b/packages/SystemUI/res/drawable/ic_access_alarms_small.xml
@@ -18,9 +18,9 @@
android:height="16dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:tint="?android:attr/textColorTertiary">
<path
- android:fillColor="#64ffffff"
+ android:fillColor="#FFFFFFFF"
android:pathData="M22.0,5.7l-4.6,-3.9l-1.3,1.5l4.6,3.9L22.0,5.7zM7.9,3.4L6.6,1.9L2.0,5.7l1.3,1.5L7.9,3.4zM12.5,8.0L11.0,8.0l0.0,6.0l4.7,2.9l0.8,-1.2l-4.0,-2.4L12.5,8.0zM12.0,4.0c-5.0,0.0 -9.0,4.0 -9.0,9.0c0.0,5.0 4.0,9.0 9.0,9.0s9.0,-4.0 9.0,-9.0C21.0,8.0 17.0,4.0 12.0,4.0zM12.0,20.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.9 3.1,-7.0 7.0,-7.0c3.9,0.0 7.0,3.1 7.0,7.0C19.0,16.9 15.9,20.0 12.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_dnd_disable_animation.xml b/packages/SystemUI/res/drawable/ic_dnd_disable_animation.xml
index 13ed767..d755481 100644
--- a/packages/SystemUI/res/drawable/ic_dnd_disable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_dnd_disable_animation.xml
@@ -23,7 +23,4 @@
<target
android:name="bar01_0"
android:animation="@anim/ic_dnd_disable_bar01_0_animation" />
- <target
- android:name="ic_dnd_disable"
- android:animation="@anim/ic_dnd_disable_alpha_animation" />
</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_dnd_total_silence_disable_animation.xml b/packages/SystemUI/res/drawable/ic_dnd_total_silence_disable_animation.xml
index c0b2d69..f796d62 100644
--- a/packages/SystemUI/res/drawable/ic_dnd_total_silence_disable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_dnd_total_silence_disable_animation.xml
@@ -23,7 +23,4 @@
<target
android:name="outer_ring_merged"
android:animation="@anim/ic_dnd_total_silence_disable_outer_ring_merged_animation" />
- <target
- android:name="ic_dnd_total_silence_disable"
- android:animation="@anim/ic_dnd_total_silence_disable_alpha_animation" />
</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_hotspot_disable_animation.xml b/packages/SystemUI/res/drawable/ic_hotspot_disable_animation.xml
index 694c23f..e446a32 100644
--- a/packages/SystemUI/res/drawable/ic_hotspot_disable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_hotspot_disable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_hotspot_disable" >
<target
- android:name="root"
- android:animation="@anim/ic_hotspot_disable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_hotspot_disable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_hotspot_enable.xml b/packages/SystemUI/res/drawable/ic_hotspot_enable.xml
index 31e7fe1..7a99630 100644
--- a/packages/SystemUI/res/drawable/ic_hotspot_enable.xml
+++ b/packages/SystemUI/res/drawable/ic_hotspot_enable.xml
@@ -16,12 +16,10 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="root"
- android:alpha="0.3"
android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportWidth="48" >
<group
android:name="ic_hotspot"
android:translateX="23.97354"
diff --git a/packages/SystemUI/res/drawable/ic_hotspot_enable_animation.xml b/packages/SystemUI/res/drawable/ic_hotspot_enable_animation.xml
index c5187dd..945e42f 100644
--- a/packages/SystemUI/res/drawable/ic_hotspot_enable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_hotspot_enable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_hotspot_enable" >
<target
- android:name="root"
- android:animation="@anim/ic_hotspot_enable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_hotspot_enable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml b/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml
index 83f46e5..7641998 100644
--- a/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml
+++ b/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml
@@ -19,9 +19,7 @@
android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
- android:viewportWidth="48"
- android:alpha="0.25"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportWidth="48" >
<group
android:name="ic_hotspot"
android:translateX="23.97354"
diff --git a/packages/SystemUI/res/drawable/ic_invert_colors_disable_animation.xml b/packages/SystemUI/res/drawable/ic_invert_colors_disable_animation.xml
index 476c00d..aeda0a5 100644
--- a/packages/SystemUI/res/drawable/ic_invert_colors_disable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_invert_colors_disable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_invert_colors_disable" >
<target
- android:name="root"
- android:animation="@anim/ic_invert_colors_disable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_invert_colors_disable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_invert_colors_enable_animation.xml b/packages/SystemUI/res/drawable/ic_invert_colors_enable_animation.xml
index 879066c..85928ac 100644
--- a/packages/SystemUI/res/drawable/ic_invert_colors_enable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_invert_colors_enable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_invert_colors_enable" >
<target
- android:name="root"
- android:animation="@anim/ic_invert_colors_enable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_invert_colors_enable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_left.xml b/packages/SystemUI/res/drawable/ic_left.xml
new file mode 100644
index 0000000..cea4cfc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_left.xml
@@ -0,0 +1,24 @@
+<!--
+ 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="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.41,16.09l-4.58,-4.59 4.58,-4.59L14.0,5.5l-6.0,6.0 6.0,6.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_menu.xml b/packages/SystemUI/res/drawable/ic_menu.xml
new file mode 100644
index 0000000..994bc5e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_menu.xml
@@ -0,0 +1,24 @@
+<!--
+ 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="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.0,18.0l18.0,0.0l0.0,-2.0L3.0,16.0l0.0,2.0zm0.0,-5.0l18.0,0.0l0.0,-2.0L3.0,11.0l0.0,2.0zm0.0,-7.0l0.0,2.0l18.0,0.0L21.0,6.0L3.0,6.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_battery_saver.xml b/packages/SystemUI/res/drawable/ic_qs_battery_saver.xml
new file mode 100644
index 0000000..7b29740
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_battery_saver.xml
@@ -0,0 +1,28 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:pathData="M5,3
+ l3.5,0 l0,-1.5 l7,0 l0,1.5 l3.5,0 l0,19.5 l-14,0z
+ M10.5,8.5 l0,3 l-3,0 l0,3 l3,0 l0,3 l3,0 l0,-3 l3,0 l0,-3 l-3,0 l0,-3 z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
index 36eb418..3148a2c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
@@ -21,6 +21,6 @@
android:tint="?android:attr/colorControlNormal">
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
index 0fdbe1f..06a0886 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
@@ -17,10 +17,9 @@
android:width="64dp"
android:height="64dp"
android:viewportWidth="48.0"
- android:viewportHeight="48.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="48.0">
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
index d2e9eb2..794eb9e 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
@@ -17,8 +17,7 @@
android:width="64dp"
android:height="64dp"
android:viewportWidth="48.0"
- android:viewportHeight="48.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="48.0">
<path
android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_circle.xml b/packages/SystemUI/res/drawable/ic_qs_circle.xml
index 57223cf..990cec6 100644
--- a/packages/SystemUI/res/drawable/ic_qs_circle.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_circle.xml
@@ -14,14 +14,13 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp"
+ android:width="40dp"
+ android:height="40dp"
android:viewportWidth="2.2"
android:viewportHeight="2.2">
<path
- android:strokeColor="#4DFFFFFF"
- android:strokeWidth=".05"
+ android:fillColor="#FFFFFFFF"
android:pathData="M.1,1.1
c0,.55 .45,1 1,1
c.55,0 1,-.45 1,-1
diff --git a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
index d11b6f4..6264484 100644
--- a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:autoMirrored="true"
- android:width="32.0dp"
- android:height="32.0dp"
- android:viewportWidth="40.0"
- android:viewportHeight="40.0">
+ android:width="17dp"
+ android:height="17.0dp"
+ 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_qs_dnd_off.xml b/packages/SystemUI/res/drawable/ic_qs_dnd_off.xml
index 164a557..9168dbc 100644
--- a/packages/SystemUI/res/drawable/ic_qs_dnd_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_dnd_off.xml
@@ -17,9 +17,7 @@
android:height="64dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
- android:alpha=".3"
- android:width="64dp"
- android:tint="?android:attr/colorControlNormal" >
+ android:width="64dp" >
<path
android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml b/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml
index 7e2eca8..f4c20a9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml
@@ -17,8 +17,7 @@
android:width="64dp"
android:height="64dp"
android:viewportWidth="48.0"
- android:viewportHeight="48.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="48.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M24.0,4.0C12.95,4.0 4.0,12.95 4.0,24.0s8.95,20.0 20.0,20.0 20.0,-8.95 20.0,-20.0S35.05,4.0 24.0,4.0zm10.0,22.0L14.0,26.0l0.0,-4.0l20.0,0.0l0.0,4.0z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml b/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml
index 5f4fa11..fb26c09 100644
--- a/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml
@@ -17,8 +17,7 @@
android:width="64dp"
android:height="64dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_minus.xml b/packages/SystemUI/res/drawable/ic_qs_minus.xml
index 147a94b..6a3410a 100644
--- a/packages/SystemUI/res/drawable/ic_qs_minus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_minus.xml
@@ -17,8 +17,7 @@
android:height="24.0dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
- android:width="24.0dp"
- android:tint="?android:attr/colorControlNormal" >
+ android:width="24.0dp" >
<path
android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_night_display_off.xml b/packages/SystemUI/res/drawable/ic_qs_night_display_off.xml
index b99dc03..aaca663 100644
--- a/packages/SystemUI/res/drawable/ic_qs_night_display_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_night_display_off.xml
@@ -17,12 +17,10 @@
android:width="64dp"
android:height="64dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:alpha="0.3"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24">
<path
- android:fillColor="#FFFFFF"
+ android:fillColor="#FFF"
android:pathData="M6,12c0,5.5,4.5,10,10,10c1,0,2-0.2,3-0.5c-4.1-1.3-7-5.1-7-9.5s2.9-8.3,7-9.5C18.1,2.2,17.1,2,16,2C10.5,2,6,6.5,6,12z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_night_display_on.xml b/packages/SystemUI/res/drawable/ic_qs_night_display_on.xml
index d875592..aaca663 100644
--- a/packages/SystemUI/res/drawable/ic_qs_night_display_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_night_display_on.xml
@@ -17,11 +17,10 @@
android:width="64dp"
android:height="64dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24">
<path
- android:fillColor="#FFFFFF"
+ android:fillColor="#FFF"
android:pathData="M6,12c0,5.5,4.5,10,10,10c1,0,2-0.2,3-0.5c-4.1-1.3-7-5.1-7-9.5s2.9-8.3,7-9.5C18.1,2.2,17.1,2,16,2C10.5,2,6,6.5,6,12z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_no_sim.xml b/packages/SystemUI/res/drawable/ic_qs_no_sim.xml
index 2d831b2..69869fe 100644
--- a/packages/SystemUI/res/drawable/ic_qs_no_sim.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_no_sim.xml
@@ -17,10 +17,9 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M19.0,5.0c0.0,-1.1 -0.9,-2.0 -2.0,-2.0l-7.0,0.0L7.7,5.3L19.0,16.7L19.0,5.0zM3.7,3.9L2.4,5.2L5.0,7.8L5.0,19.0c0.0,1.1 0.9,2.0 2.0,2.0l10.0,0.0c0.4,0.0 0.7,-0.1 1.0,-0.3l1.9,1.9l1.3,-1.3L3.7,3.9z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_plus.xml b/packages/SystemUI/res/drawable/ic_qs_plus.xml
index 1f254d1..393f51c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_plus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_plus.xml
@@ -17,8 +17,7 @@
android:height="24.0dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
- android:width="24.0dp"
- android:tint="?android:attr/colorControlNormal" >
+ android:width="24.0dp" >
<path
android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_0.xml b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
index 0673848..b78d3bf 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
@@ -18,8 +18,7 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillAlpha="0.3"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
index fbf9e71..e055de7 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
@@ -18,8 +18,7 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M10.0,14.6l-8.0,8.0l8.0,0.0l0,-8z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
index 3a55623..195849a 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="16.0dp"
+ android:width="32.0dp"
android:height="32dp"
android:viewportWidth="12.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="12.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3.500000,11.000000L1.800000,11.000000L1.800000,4.400000L0.200000,5.100000L0.200000,3.700000l3.100000,-1.300000l0.200000,0.000000L3.500000,11.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
index e9f5a0b..8a48817 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
@@ -18,8 +18,7 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M14.0,10.6l-12.0,12.0l12.0,0.0L14.0,10.6z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
index 769d648..39cc94c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
@@ -18,8 +18,7 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillAlpha="0.3"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
index ddd8065..68c4307 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="17.333334dp"
+ android:width="32dp"
android:height="32dp"
android:viewportWidth="13.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="13.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.000000,6.000000l0.800000,0.000000c0.300000,0.000000 0.500000,-0.100000 0.700000,-0.300000s0.200000,-0.500000 0.200000,-0.900000c0.000000,-0.300000 -0.100000,-0.600000 -0.200000,-0.800000S3.200000,3.700000 2.900000,3.700000C2.700000,3.700000 2.500000,3.800000 2.300000,4.000000S2.100000,4.400000 2.100000,4.700000L0.500000,4.700000C0.500000,4.000000 0.700000,3.400000 1.100000,3.000000s1.000000,-0.600000 1.700000,-0.600000c0.800000,0.000000 1.400000,0.200000 1.900000,0.600000s0.700000,1.000000 0.700000,1.800000c0.000000,0.400000 -0.100000,0.700000 -0.300000,1.100000S4.600000,6.500000 4.300000,6.600000C4.700000,6.800000 5.000000,7.100000 5.200000,7.400000s0.300000,0.700000 0.300000,1.200000c0.000000,0.800000 -0.200000,1.400000 -0.700000,1.800000s-1.100000,0.700000 -1.900000,0.700000c-0.700000,0.000000 -1.300000,-0.200000 -1.800000,-0.600000s-0.700000,-1.000000 -0.700000,-1.800000L2.000000,8.700000C2.000000,9.000000 2.100000,9.300000 2.300000,9.500000s0.400000,0.300000 0.600000,0.300000c0.300000,0.000000 0.500000,-0.100000 0.700000,-0.300000S3.900000,9.000000 3.900000,8.600000c0.000000,-0.500000 -0.100000,-0.800000 -0.300000,-1.000000S3.200000,7.300000 2.800000,7.300000L2.000000,7.300000L2.000000,6.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
index 1bec1b8..012e95e 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
@@ -18,8 +18,7 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
index 8e1f8eb..61ecc9c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="16.0dp"
+ android:width="32.0dp"
android:height="32dp"
android:viewportWidth="12.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="12.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M4.600000,7.800000l0.700000,0.000000l0.000000,1.300000L4.600000,9.100000L4.600000,11.000000L3.000000,11.000000L3.000000,9.200000L0.100000,9.200000L0.000000,8.100000L3.000000,2.500000l1.700000,0.000000L4.700000,7.800000zM1.600000,7.800000L3.000000,7.800000l0.000000,-3.000000L2.900000,5.000000L1.600000,7.800000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
index e0c6b68..54c52bf 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M4.6,7.8l0.7,0.0l0.0,1.3L4.6,9.1L4.6,11.0L3.0,11.0L3.0,9.2L0.1,9.2L0.0,8.2l3.0,-5.7l1.7,0.0L4.6,7.8L4.6,7.8zM1.7,7.8L3.0,7.8l0.0,-3.0L2.9,5.0L1.7,7.8z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
index 1c068e5..96e2fd4 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
@@ -17,8 +17,7 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:name="dot1"
android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
index 0a85392..dd5843d 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
@@ -18,12 +18,11 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M21.799999,22.299999l-1.199999,-1.299999 0.000000,0.000000 -9.600000,-10.000000 0.000000,0.000000 -6.400000,-6.700000 -1.300000,1.300000 6.400000,6.700000 -8.700000,8.700000 16.900000,0.000000 2.600000,2.700001z"/>
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M21.000000,1.000000l-8.600000,8.600000 8.600000,9.100000z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_e.xml b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
index 4c90421..dd0f271 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="6.6666665dp"
+ android:width="32dp"
android:height="32dp"
android:viewportWidth="5.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="5.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M4.400000,7.300000L1.700000,7.300000l0.000000,2.400000l3.300000,0.000000L5.000000,11.000000L0.000000,11.000000L0.000000,2.500000l4.900000,0.000000l0.000000,1.300000L1.700000,3.800000l0.000000,2.100000l2.800000,0.000000L4.500000,7.300000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml
index db4df76..326373d 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml
@@ -18,8 +18,7 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml
index 6e5439d..8baa4eb 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml
@@ -18,8 +18,7 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M2.0,22.0l20.0,0.0 0.0,-20.0z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml
index 194edc3..bf19a71 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml
@@ -18,8 +18,7 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml
index b57af5c..01839e85 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml
@@ -18,8 +18,7 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml
index 7d754a8..48151ad 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml
@@ -18,8 +18,7 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
index 64aadf9..3b47c0d 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="9.333333dp"
+ android:width="32dp"
android:height="32dp"
android:viewportWidth="7.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="7.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6.500000,9.900000c-0.200000,0.400000 -0.600000,0.700000 -1.000000,0.900000s-1.000000,0.400000 -1.800000,0.400000c-0.900000,0.000000 -1.700000,-0.300000 -2.200000,-0.800000S0.700000,9.000000 0.700000,7.900000L0.700000,5.600000c0.000000,-1.100000 0.300000,-1.900000 0.800000,-2.400000s1.200000,-0.800000 2.100000,-0.800000c1.000000,0.000000 1.700000,0.200000 2.100000,0.700000s0.700000,1.200000 0.700000,2.100000L4.700000,5.200000c0.000000,-0.500000 -0.100000,-0.900000 -0.200000,-1.100000S4.000000,3.700000 3.600000,3.700000c-0.400000,0.000000 -0.700000,0.200000 -0.900000,0.500000S2.300000,5.000000 2.300000,5.600000l0.000000,2.300000c0.000000,0.700000 0.100000,1.100000 0.300000,1.400000s0.600000,0.500000 1.000000,0.500000c0.300000,0.000000 0.600000,0.000000 0.700000,-0.100000s0.300000,-0.200000 0.400000,-0.300000L4.700000,7.800000L3.500000,7.800000L3.500000,6.600000l2.900000,0.000000L6.400000,9.900000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_h.xml b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
index 31918a9..d694e61 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="8.0dp"
+ android:width="32.0dp"
android:height="32dp"
android:viewportWidth="6.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="6.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6.000000,11.000000L4.400000,11.000000L4.400000,7.500000L1.700000,7.500000L1.700000,11.000000L0.000000,11.000000L0.000000,2.500000l1.700000,0.000000l0.000000,3.700000l2.700000,0.000000L4.400000,2.500000L6.000000,2.500000L6.000000,11.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_in.xml b/packages/SystemUI/res/drawable/ic_qs_signal_in.xml
index 4122b76..236fdac 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_in.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_in.xml
@@ -17,8 +17,7 @@
android:width="6.0dp"
android:height="32dp"
android:viewportWidth="6.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6.000000,15.700000l-3.000000,5.599999 -3.000000,-5.599999z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
index 8766075..a381d03e 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="17.333334dp"
+ android:width="32dp"
android:height="32dp"
android:viewportWidth="13.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="13.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.000000,9.700000l2.000000,0.000000L4.000000,11.000000L0.300000,11.000000L0.300000,2.500000L2.000000,2.500000L2.000000,9.700000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
index 5ff7d85..3bed28a 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
@@ -14,11 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.0,9.7l2.0,0.0L4.0,11.0L0.4,11.0L0.4,2.5L2.0,2.5L2.0,9.7z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml b/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml
index 4e65004..c8c857c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml
@@ -18,9 +18,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000L22.000000,2.000000L2.000000,22.000000zM20.000000,20.000000L6.800000,20.000000L20.000000,6.800000L20.000000,20.000000z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_out.xml b/packages/SystemUI/res/drawable/ic_qs_signal_out.xml
index a3823ae..c510972 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_out.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_out.xml
@@ -17,8 +17,7 @@
android:width="6.0dp"
android:height="32dp"
android:viewportWidth="6.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M0.000000,13.700000l3.000000,-5.700000 3.000000,5.700000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_r.xml b/packages/SystemUI/res/drawable/ic_qs_signal_r.xml
deleted file mode 100644
index 2c10dc3f..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_signal_r.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="8.0dp"
- android:height="32dp"
- android:viewportWidth="6.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M2.800000,7.900000l-1.000000,0.000000L1.800000,11.000000L0.200000,11.000000L0.200000,2.500000l2.700000,0.000000c0.900000,0.000000 1.500000,0.200000 2.000000,0.700000s0.700000,1.100000 0.700000,1.900000c0.000000,0.600000 -0.100000,1.100000 -0.300000,1.500000S4.800000,7.200000 4.400000,7.400000l1.500000,3.500000L5.900000,11.000000L4.100000,11.000000L2.800000,7.900000zM1.800000,6.500000l1.100000,0.000000c0.400000,0.000000 0.600000,-0.100000 0.800000,-0.400000S4.000000,5.600000 4.000000,5.200000c0.000000,-0.400000 -0.100000,-0.800000 -0.300000,-1.000000S3.300000,3.800000 2.900000,3.800000L1.800000,3.800000L1.800000,6.500000z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_vpn.xml b/packages/SystemUI/res/drawable/ic_qs_vpn.xml
index e7ef02a..cf24e12 100644
--- a/packages/SystemUI/res/drawable/ic_qs_vpn.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_vpn.xml
@@ -19,6 +19,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M12.700000,10.000000c-0.800000,-2.300000 -3.000000,-4.000000 -5.700000,-4.000000c-3.300000,0.000000 -6.000000,2.700000 -6.000000,6.000000s2.700000,6.000000 6.000000,6.000000c2.600000,0.000000 4.800000,-1.700000 5.700000,-4.000000L17.000000,14.000000l0.000000,4.000000l4.000000,0.000000l0.000000,-4.000000l2.000000,0.000000l0.000000,-4.000000L12.700000,10.000000zM7.000000,14.000000c-1.100000,0.000000 -2.000000,-0.900000 -2.000000,-2.000000c0.000000,-1.100000 0.900000,-2.000000 2.000000,-2.000000s2.000000,0.900000 2.000000,2.000000C9.000000,13.100000 8.100000,14.000000 7.000000,14.000000z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
index fe963b1..e6f9292 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
@@ -17,8 +17,7 @@
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
android:fillAlpha="0.3"
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
index 82d2be2..d423ccb 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
@@ -17,8 +17,7 @@
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M13.8,13.2c-0.1,0.0 -0.3,-0.1 -0.4,-0.1c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,0.0 -0.6,-0.1 -0.9,-0.1c0.0,0.0 0.0,0.0 -0.1,0.0c0.0,0.0 0.0,0.0 0.0,0.0s0.0,0.0 0.0,0.0c0.0,0.0 0.0,0.0 -0.1,0.0c-0.3,0.0 -0.6,0.0 -0.9,0.1c-0.1,0.0 -0.3,0.0 -0.4,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.1,0.0 -0.1,0.0 -0.2,0.1c-1.6,0.5 -2.7,1.3 -2.8,1.5l5.3,6.6l0.0,0.0l0.0,0.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.700002,13.2z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
index f30ba76..1982130 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
@@ -17,8 +17,7 @@
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M13.8,12.2l4.9,0.0c-1.0,-0.7 -3.4,-2.2 -6.7,-2.2c-4.1,0.0 -6.9,2.2 -7.2,2.5l7.2,9.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.800001,12.2z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
index 8a17083..b350111 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
@@ -17,8 +17,7 @@
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M13.8,12.2l5.7,0.0l1.0,-1.2C20.0,10.6 16.8,8.0 12.0,8.0s-8.0,2.6 -8.5,3.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
index fcd57d0..136a004 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
@@ -17,8 +17,7 @@
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml
index bbff2e1..bed37b1 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml
@@ -17,12 +17,11 @@
android:width="32.0dp"
android:height="29.5dp"
android:viewportWidth="26.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M17.500000,16.500000L5.800000,3.400000c0.000000,0.000000 0.000000,0.000000 0.000000,0.000000l-2.700000,-3.000000L1.600000,1.800000l2.200000,2.500000c-2.000000,1.000000 -3.200000,2.000000 -3.400000,2.200000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l3.200000,-3.900000l2.400000,2.700000l1.500000,-1.400000L17.500000,16.500000L17.500000,16.500000z"/>
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000c-1.900000,0.000000 -3.600000,0.300000 -5.200000,0.700000L18.700001,15.000000L25.600000,6.500000z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml
index 071892a..2dcdb71 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml
@@ -17,8 +17,7 @@
android:width="26.0dp"
android:height="24.0dp"
android:viewportWidth="26.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M21.0,8.5
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml
index c0e1037..1bc7438 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml
@@ -17,8 +17,7 @@
android:width="32.0dp"
android:height="29.5dp"
android:viewportWidth="26.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml
index f609295..5856115 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml
@@ -17,8 +17,7 @@
android:width="32.0dp"
android:height="29.5dp"
android:viewportWidth="26.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M13.100000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.500000,6.500000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml
index f44b303..4a5e1f8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml
@@ -17,8 +17,7 @@
android:width="32.0dp"
android:height="29.5dp"
android:viewportWidth="26.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml
index 850f5b9..965442d 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml
@@ -17,8 +17,7 @@
android:width="32.0dp"
android:height="29.5dp"
android:viewportWidth="26.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml
index 8ccc1fd9..b29d3f9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml
@@ -17,8 +17,7 @@
android:width="32.0dp"
android:height="29.5dp"
android:viewportWidth="26.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_no_network.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_no_network.xml
index 45cfc1c..e59e7f4 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_no_network.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_no_network.xml
@@ -17,9 +17,8 @@
android:width="32.0dp"
android:height="29.5dp"
android:viewportWidth="26.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M13.000000,2.000000C7.700000,2.000000 3.700000,3.900000 0.400000,6.400000L13.000000,22.000000L25.600000,6.500000C22.299999,4.000000 18.299999,2.000000 13.000000,2.000000zM13.000000,18.600000L3.300000,7.000000l0.000000,0.000000l0.000000,0.000000C6.000000,5.300000 8.700000,4.000000 13.000000,4.000000s7.000000,1.400000 9.700000,3.000000l0.000000,0.000000l0.000000,0.000000L13.000000,18.600000z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_remove.xml b/packages/SystemUI/res/drawable/ic_remove.xml
new file mode 100644
index 0000000..75b2793
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_remove.xml
@@ -0,0 +1,24 @@
+<!--
+ 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="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.0,13.0L5.0,13.0l0.0,-2.0l14.0,0.0l0.0,2.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_right.xml b/packages/SystemUI/res/drawable/ic_right.xml
new file mode 100644
index 0000000..35699f73
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_right.xml
@@ -0,0 +1,24 @@
+<!--
+ 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="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10.0,5.75l6.0,6.0 -6.0,6.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_settings_20dp.xml b/packages/SystemUI/res/drawable/ic_settings_20dp.xml
index 45ee94f..3170f86 100644
--- a/packages/SystemUI/res/drawable/ic_settings_20dp.xml
+++ b/packages/SystemUI/res/drawable/ic_settings_20dp.xml
@@ -17,8 +17,7 @@
android:width="20dp"
android:height="20dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:viewportHeight="24.0">
<path
android:pathData="M19.4,13.0c0.0,-0.3 0.1,-0.6 0.1,-1.0s0.0,-0.7 -0.1,-1.0l2.1,-1.7c0.2,-0.2 0.2,-0.4 0.1,-0.6l-2.0,-3.5C19.5,5.1 19.3,5.0 19.0,5.1l-2.5,1.0c-0.5,-0.4 -1.1,-0.7 -1.7,-1.0l-0.4,-2.6C14.5,2.2 14.2,2.0 14.0,2.0l-4.0,0.0C9.8,2.0 9.5,2.2 9.5,2.4L9.1,5.1C8.5,5.3 8.0,5.7 7.4,6.1L5.0,5.1C4.7,5.0 4.5,5.1 4.3,5.3l-2.0,3.5C2.2,8.9 2.3,9.2 2.5,9.4L4.6,11.0c0.0,0.3 -0.1,0.6 -0.1,1.0s0.0,0.7 0.1,1.0l-2.1,1.7c-0.2,0.2 -0.2,0.4 -0.1,0.6l2.0,3.5C4.5,18.9 4.7,19.0 5.0,18.9l2.5,-1.0c0.5,0.4 1.1,0.7 1.7,1.0l0.4,2.6c0.0,0.2 0.2,0.4 0.5,0.4l4.0,0.0c0.2,0.0 0.5,-0.2 0.5,-0.4l0.4,-2.6c0.6,-0.3 1.2,-0.6 1.7,-1.0l2.5,1.0c0.2,0.1 0.5,0.0 0.6,-0.2l2.0,-3.5c0.1,-0.2 0.1,-0.5 -0.1,-0.6L19.4,13.0zM12.0,15.5c-1.9,0.0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5s3.5,1.6 3.5,3.5S13.9,15.5 12.0,15.5z"
android:fillColor="#ffffffff" />
diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane_disable.xml b/packages/SystemUI/res/drawable/ic_signal_airplane_disable.xml
index 2ff0de4..09a67e1 100644
--- a/packages/SystemUI/res/drawable/ic_signal_airplane_disable.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_airplane_disable.xml
@@ -20,8 +20,7 @@
android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportWidth="48" >
<group
android:name="ic_signal_airplane"
android:translateX="21.9995"
diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane_disable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_airplane_disable_animation.xml
index 4fe585c..56ae4aa 100644
--- a/packages/SystemUI/res/drawable/ic_signal_airplane_disable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_airplane_disable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_signal_airplane_disable" >
<target
- android:name="root"
- android:animation="@anim/ic_signal_airplane_disable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_signal_airplane_disable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane_enable.xml b/packages/SystemUI/res/drawable/ic_signal_airplane_enable.xml
index 9987279..b7ac0134 100644
--- a/packages/SystemUI/res/drawable/ic_signal_airplane_enable.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_airplane_enable.xml
@@ -16,12 +16,10 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="root"
- android:alpha="0.3"
android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportWidth="48" >
<group
android:name="ic_signal_airplane"
android:translateX="21.9995"
diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane_enable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_airplane_enable_animation.xml
index d64b199..87dfba9 100644
--- a/packages/SystemUI/res/drawable/ic_signal_airplane_enable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_airplane_enable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_signal_airplane_enable" >
<target
- android:name="root"
- android:animation="@anim/ic_signal_airplane_enable_animation_root" />
- <target
android:name="ic_signal_airplane"
android:animation="@anim/ic_signal_airplane_enable_animation_ic_signal_airplane" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight_disable.xml b/packages/SystemUI/res/drawable/ic_signal_flashlight_disable.xml
index 542797a..35844b7 100644
--- a/packages/SystemUI/res/drawable/ic_signal_flashlight_disable.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_flashlight_disable.xml
@@ -20,8 +20,7 @@
android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportWidth="48" >
<group
android:name="ic_signal_flashlight"
android:translateX="21.9995"
diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight_disable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_flashlight_disable_animation.xml
index 61e5287..e228b7c 100644
--- a/packages/SystemUI/res/drawable/ic_signal_flashlight_disable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_flashlight_disable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_signal_flashlight_disable" >
<target
- android:name="root"
- android:animation="@anim/ic_signal_flashlight_disable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_signal_flashlight_disable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight_enable.xml b/packages/SystemUI/res/drawable/ic_signal_flashlight_enable.xml
index a5ba05b..c2215f1 100644
--- a/packages/SystemUI/res/drawable/ic_signal_flashlight_enable.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_flashlight_enable.xml
@@ -16,12 +16,10 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="root"
- android:alpha="0.3"
android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportWidth="48" >
<group
android:name="ic_signal_flashlight"
android:translateX="21.9995"
diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight_enable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_flashlight_enable_animation.xml
index 4bd9188..220c65e 100644
--- a/packages/SystemUI/res/drawable/ic_signal_flashlight_enable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_flashlight_enable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_signal_flashlight_enable" >
<target
- android:name="root"
- android:animation="@anim/ic_signal_flashlight_enable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_signal_flashlight_enable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_signal_location_disable.xml b/packages/SystemUI/res/drawable/ic_signal_location_disable.xml
index e36f270..439851d 100644
--- a/packages/SystemUI/res/drawable/ic_signal_location_disable.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_location_disable.xml
@@ -20,8 +20,7 @@
android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportWidth="48" >
<group
android:name="ic_signal_location"
android:translateX="21.9995"
diff --git a/packages/SystemUI/res/drawable/ic_signal_location_disable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_location_disable_animation.xml
index a598b2d..0e9d1cb 100644
--- a/packages/SystemUI/res/drawable/ic_signal_location_disable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_location_disable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_signal_location_disable" >
<target
- android:name="root"
- android:animation="@anim/ic_signal_location_disable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_signal_location_disable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_signal_location_enable.xml b/packages/SystemUI/res/drawable/ic_signal_location_enable.xml
index 46a72bc..d4b8673 100644
--- a/packages/SystemUI/res/drawable/ic_signal_location_enable.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_location_enable.xml
@@ -16,12 +16,10 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="root"
- android:alpha="0.3"
android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportWidth="48" >
<group
android:name="ic_signal_location"
android:translateX="21.9995"
diff --git a/packages/SystemUI/res/drawable/ic_signal_location_enable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_location_enable_animation.xml
index 127af29..9f1d917 100644
--- a/packages/SystemUI/res/drawable/ic_signal_location_enable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_location_enable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_signal_location_enable" >
<target
- android:name="root"
- android:animation="@anim/ic_signal_location_enable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_signal_location_enable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_signal_workmode_disable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_workmode_disable_animation.xml
index a15a127..9c23126 100644
--- a/packages/SystemUI/res/drawable/ic_signal_workmode_disable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_workmode_disable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_signal_workmode_disable" >
<target
- android:name="root"
- android:animation="@anim/ic_signal_workmode_disable_animation_root" />
- <target
android:name="mask"
android:animation="@anim/ic_signal_workmode_disable_animation_mask" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_signal_workmode_enable.xml b/packages/SystemUI/res/drawable/ic_signal_workmode_enable.xml
index ad14505..f962594 100644
--- a/packages/SystemUI/res/drawable/ic_signal_workmode_enable.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_workmode_enable.xml
@@ -16,7 +16,6 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="root"
- android:alpha="0.3"
android:height="42dp"
android:width="42dp"
android:viewportHeight="42"
diff --git a/packages/SystemUI/res/drawable/ic_signal_workmode_enable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_workmode_enable_animation.xml
index 41d2b42..04ddfad 100644
--- a/packages/SystemUI/res/drawable/ic_signal_workmode_enable_animation.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_workmode_enable_animation.xml
@@ -17,9 +17,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_signal_workmode_enable" >
<target
- android:name="root"
- android:animation="@anim/ic_signal_workmode_enable_animation_root" />
- <target
android:name="ic_signal_briefcase"
android:animation="@anim/ic_signal_workmode_enable_animation_ic_signal_briefcase" />
<target
diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml
new file mode 100644
index 0000000..b0b03a9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_snooze.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
+ android:fillColor="#757575"/>
+ <path
+ android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
+ android:fillColor="#757575"/>
+</vector>
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/minor_a_b.xml b/packages/SystemUI/res/drawable/minor_a_b.xml
index 0622aac..4bb330c 100644
--- a/packages/SystemUI/res/drawable/minor_a_b.xml
+++ b/packages/SystemUI/res/drawable/minor_a_b.xml
@@ -17,12 +17,10 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="minor_a_b"
- android:alpha="0.3"
android:width="16dp"
android:viewportWidth="16"
android:height="8dp"
- android:viewportHeight="8"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportHeight="8" >
<group
android:name="dot_02"
android:translateX="3.25"
diff --git a/packages/SystemUI/res/drawable/minor_b_a.xml b/packages/SystemUI/res/drawable/minor_b_a.xml
index ecb4341..f61deec 100644
--- a/packages/SystemUI/res/drawable/minor_b_a.xml
+++ b/packages/SystemUI/res/drawable/minor_b_a.xml
@@ -17,12 +17,10 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="minor_b_a"
- android:alpha="0.3"
android:width="16dp"
android:viewportWidth="16"
android:height="8dp"
- android:viewportHeight="8"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportHeight="8" >
<group
android:name="dot_02"
android:translateX="8"
diff --git a/packages/SystemUI/res/drawable/minor_b_c.xml b/packages/SystemUI/res/drawable/minor_b_c.xml
index 7f59e34..867af6d 100644
--- a/packages/SystemUI/res/drawable/minor_b_c.xml
+++ b/packages/SystemUI/res/drawable/minor_b_c.xml
@@ -17,12 +17,10 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="minor_b_c"
- android:alpha="0.3"
android:width="16dp"
android:viewportWidth="16"
android:height="8dp"
- android:viewportHeight="8"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportHeight="8" >
<group
android:name="dot_02"
android:translateX="8"
diff --git a/packages/SystemUI/res/drawable/minor_c_b.xml b/packages/SystemUI/res/drawable/minor_c_b.xml
index 97309bc..b2e33cf 100644
--- a/packages/SystemUI/res/drawable/minor_c_b.xml
+++ b/packages/SystemUI/res/drawable/minor_c_b.xml
@@ -17,12 +17,10 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="minor_c_b"
- android:alpha="0.3"
android:width="16dp"
android:viewportWidth="16"
android:height="8dp"
- android:viewportHeight="8"
- android:tint="?android:attr/colorControlNormal" >
+ android:viewportHeight="8" >
<group
android:name="dot_02"
android:translateX="12.75"
diff --git a/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml b/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml
new file mode 100644
index 0000000..0b2bd22
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M14.0,20.0l10.0,10.0 10.0,-10.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_ic_wifi_lock.xml b/packages/SystemUI/res/drawable/qs_ic_wifi_lock.xml
index 2ac223b..b7da30b 100644
--- a/packages/SystemUI/res/drawable/qs_ic_wifi_lock.xml
+++ b/packages/SystemUI/res/drawable/qs_ic_wifi_lock.xml
@@ -17,7 +17,8 @@
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="72.0"
- android:viewportHeight="72.0">
+ android:viewportHeight="72.0"
+ android:tint="?android:attr/textColorPrimary">
<group
android:translateX="52.0"
android:translateY="42.0" >
diff --git a/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml b/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml
index a85beb8..ea03a50 100644
--- a/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml
+++ b/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml
@@ -15,5 +15,5 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#61FFFFFF" />
- <corners android:radius="8dp"/>
+ <corners android:radius="@dimen/recents_grid_task_view_rounded_corners_radius"/>
</shape>
\ 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/drawable/stat_sys_managed_profile_disable_animation.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_disable_animation.xml
index c6a2fdd..1e41a31 100644
--- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_disable_animation.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_disable_animation.xml
@@ -16,7 +16,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="27.0dp"
android:height="22.0dp"
- android:alpha="0.3"
android:viewportWidth="27.0"
android:viewportHeight="22.0">
<clip-path
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_roam.xml b/packages/SystemUI/res/drawable/stat_sys_roaming.xml
similarity index 95%
rename from packages/SystemUI/res/drawable/stat_sys_data_fully_connected_roam.xml
rename to packages/SystemUI/res/drawable/stat_sys_roaming.xml
index 363e231..4baa472 100644
--- a/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_roam.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_roaming.xml
@@ -14,10 +14,10 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="4.25dp"
+ android:width="8.5dp"
android:height="17dp"
android:viewportWidth="6.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="12.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.800000,7.900000l-1.000000,0.000000L1.800000,11.000000L0.200000,11.000000L0.200000,2.500000l2.700000,0.000000c0.900000,0.000000 1.500000,0.200000 2.000000,0.700000s0.700000,1.100000 0.700000,1.900000c0.000000,0.600000 -0.100000,1.100000 -0.300000,1.500000S4.800000,7.200000 4.400000,7.400000l1.500000,3.500000L5.900000,11.000000L4.100000,11.000000L2.800000,7.900000zM1.800000,6.500000l1.100000,0.000000c0.400000,0.000000 0.600000,-0.100000 0.800000,-0.400000S4.000000,5.600000 4.000000,5.200000c0.000000,-0.400000 -0.100000,-0.800000 -0.300000,-1.000000S3.300000,3.800000 2.900000,3.800000L1.800000,3.800000L1.800000,6.500000z"/>
diff --git a/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml b/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml
index e85b76d..46c761a 100644
--- a/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml
+++ b/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml
@@ -27,7 +27,7 @@
<LinearLayout
android:id="@+id/date_time_group"
android:layout_width="wrap_content"
- android:layout_height="19dp"
+ android:layout_height="wrap_content"
android:orientation="horizontal"
android:focusable="true" >
diff --git a/packages/SystemUI/res/layout/data_usage.xml b/packages/SystemUI/res/layout/data_usage.xml
index c943f3d..fdc6f14 100644
--- a/packages/SystemUI/res/layout/data_usage.xml
+++ b/packages/SystemUI/res/layout/data_usage.xml
@@ -59,6 +59,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.QS.DataUsage" />
+
</LinearLayout>
<LinearLayout
@@ -82,4 +83,13 @@
android:textAppearance="@style/TextAppearance.QS.DataUsage.Secondary" />
</LinearLayout>
-</com.android.systemui.qs.tiles.DataUsageDetailView>
\ No newline at end of file
+ <TextView
+ android:id="@+id/roaming_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:text="@string/accessibility_data_connection_roaming"
+ android:textAppearance="@style/TextAppearance.QS.DataUsage.Secondary"
+ android:visibility="gone" />
+
+</com.android.systemui.qs.tiles.DataUsageDetailView>
diff --git a/packages/SystemUI/res/layout/mobile_signal_group.xml b/packages/SystemUI/res/layout/mobile_signal_group.xml
index a20ec8e..8b10074 100644
--- a/packages/SystemUI/res/layout/mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/mobile_signal_group.xml
@@ -43,4 +43,15 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
+ <ImageView
+ android:id="@+id/mobile_roaming"
+ android:layout_width="wrap_content"
+ android:layout_height="17dp"
+ android:paddingStart="22dp"
+ android:paddingTop="1.5dp"
+ android:paddingBottom="3dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/stat_sys_roaming"
+ android:contentDescription="@string/accessibility_data_connection_roaming"
+ android:visibility="gone" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index 3948dc4..9d8ef83 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -23,125 +23,4 @@
android:visibility="gone"
android:clickable="true"
android:gravity="top|start"
- android:orientation="vertical"
- android:paddingStart="@*android:dimen/notification_content_margin_start"
- android:paddingEnd="8dp"
- android:background="@color/notification_guts_bg_color"
- android:theme="@*android:style/Theme.DeviceDefault.Light">
-
- <!-- header -->
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="20dp"
- android:paddingEnd="8dp"
- android:paddingBottom="15dp"
- android:id="@+id/notification_guts_header">
- <TextView
- android:id="@+id/pkgname"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- style="@style/TextAppearance.NotificationGuts.Secondary" />
- <TextView
- android:id="@+id/channel_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/pkgname"
- style="@style/TextAppearance.NotificationGuts.Header" />
- <Switch
- android:id="@+id/channel_enabled_switch"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:background="@null" />
- </RelativeLayout>
- <!-- Importance radio buttons -->
- <LinearLayout
- android:id="@+id/importance"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
- <RadioGroup
- android:id="@+id/importance_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingEnd="@*android:dimen/notification_content_margin_end">
- <RadioButton
- android:id="@+id/high_importance"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_inline_importance_height"
- style="@style/TextAppearance.NotificationGuts.Radio"
- android:buttonTint="@color/notification_guts_buttons" />
- <RadioButton
- android:id="@+id/default_importance"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_inline_importance_height"
- style="@style/TextAppearance.NotificationGuts.Radio"
- android:buttonTint="@color/notification_guts_buttons" />
- <RadioButton
- android:id="@+id/low_importance"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_inline_importance_height"
- style="@style/TextAppearance.NotificationGuts.Radio"
- android:buttonTint="@color/notification_guts_buttons" />
- <RadioButton
- android:id="@+id/min_importance"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_inline_importance_height"
- style="@style/TextAppearance.NotificationGuts.Radio"
- android:buttonTint="@color/notification_guts_buttons" />
- </RadioGroup>
- <LinearLayout
- android:id="@+id/importance_buttons_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <include layout="@layout/notification_guts_importance_text"/>
- <include layout="@layout/notification_guts_importance_text"/>
- <include layout="@layout/notification_guts_importance_text"/>
- <include layout="@layout/notification_guts_importance_text"/>
- </LinearLayout>
- </LinearLayout>
- <!-- Channel Disabled Text -->
- <TextView
- android:id="@+id/channel_disabled"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/notification_channel_disabled"
- style="@style/TextAppearance.NotificationGuts.Secondary" />
- <!-- Settings and Done buttons -->
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="end"
- android:paddingTop="16dp"
- android:paddingBottom="8dp" >
-
- <TextView
- android:id="@+id/more_settings"
- android:text="@string/notification_more_settings"
- android:layout_width="wrap_content"
- android:layout_height="36dp"
- style="@style/TextAppearance.NotificationGuts.Button"
- android:background="@drawable/btn_borderless_rect"
- android:gravity="center"
- android:paddingEnd="8dp"
- android:paddingStart="8dp"
- android:focusable="true" />
-
- <TextView
- android:id="@+id/done"
- android:text="@string/notification_done"
- android:layout_width="wrap_content"
- android:layout_height="36dp"
- style="@style/TextAppearance.NotificationGuts.Button"
- android:background="@drawable/btn_borderless_rect"
- android:gravity="center"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:focusable="true"/>
- </LinearLayout>
-</com.android.systemui.statusbar.NotificationGuts>
+ android:theme="@*android:style/Theme.DeviceDefault.Light"/>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
new file mode 100644
index 0000000..9770ecc
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<com.android.systemui.statusbar.NotificationInfo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/notification_guts"
+ android:clickable="true"
+ android:gravity="top|start"
+ android:orientation="vertical"
+ android:paddingStart="@*android:dimen/notification_content_margin_start"
+ android:paddingEnd="8dp"
+ android:background="@color/notification_guts_bg_color"
+ android:theme="@*android:style/Theme.DeviceDefault.Light">
+
+ <!-- header -->
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="20dp"
+ android:paddingEnd="8dp"
+ android:paddingBottom="15dp"
+ android:id="@+id/notification_guts_header">
+ <TextView
+ android:id="@+id/pkgname"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ style="@style/TextAppearance.NotificationGuts.Secondary" />
+ <TextView
+ android:id="@+id/channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/pkgname"
+ style="@style/TextAppearance.NotificationGuts.Header" />
+ <Switch
+ android:id="@+id/channel_enabled_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:background="@null" />
+ </RelativeLayout>
+ <!-- Importance radio buttons -->
+ <LinearLayout
+ android:id="@+id/importance"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <RadioGroup
+ android:id="@+id/importance_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="@*android:dimen/notification_content_margin_end">
+ <RadioButton
+ android:id="@+id/high_importance"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ style="@style/TextAppearance.NotificationGuts.Radio"
+ android:buttonTint="@color/notification_guts_buttons" />
+ <RadioButton
+ android:id="@+id/default_importance"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ style="@style/TextAppearance.NotificationGuts.Radio"
+ android:buttonTint="@color/notification_guts_buttons" />
+ <RadioButton
+ android:id="@+id/low_importance"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ style="@style/TextAppearance.NotificationGuts.Radio"
+ android:buttonTint="@color/notification_guts_buttons" />
+ <RadioButton
+ android:id="@+id/min_importance"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ style="@style/TextAppearance.NotificationGuts.Radio"
+ android:buttonTint="@color/notification_guts_buttons" />
+ </RadioGroup>
+ <LinearLayout
+ android:id="@+id/importance_buttons_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <include layout="@layout/notification_guts_importance_text"/>
+ <include layout="@layout/notification_guts_importance_text"/>
+ <include layout="@layout/notification_guts_importance_text"/>
+ <include layout="@layout/notification_guts_importance_text"/>
+ </LinearLayout>
+ </LinearLayout>
+ <!-- Channel Disabled Text -->
+ <TextView
+ android:id="@+id/channel_disabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/notification_channel_disabled"
+ style="@style/TextAppearance.NotificationGuts.Secondary" />
+ <!-- Settings and Done buttons -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp" >
+
+ <TextView
+ android:id="@+id/more_settings"
+ android:text="@string/notification_more_settings"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ style="@style/TextAppearance.NotificationGuts.Button"
+ android:background="@drawable/btn_borderless_rect"
+ android:gravity="center"
+ android:paddingEnd="8dp"
+ android:paddingStart="8dp"
+ android:focusable="true" />
+
+ <TextView
+ android:id="@+id/done"
+ android:text="@string/notification_done"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ style="@style/TextAppearance.NotificationGuts.Button"
+ android:background="@drawable/btn_borderless_rect"
+ android:gravity="center"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
+ android:focusable="true"/>
+ </LinearLayout>
+</com.android.systemui.statusbar.NotificationInfo>
diff --git a/packages/SystemUI/res/layout/notification_menu_row.xml b/packages/SystemUI/res/layout/notification_menu_row.xml
new file mode 100644
index 0000000..12bcf81
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_menu_row.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<com.android.systemui.statusbar.NotificationMenuRow
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_settings_icon_row.xml b/packages/SystemUI/res/layout/notification_settings_icon_row.xml
deleted file mode 100644
index da3461b90..0000000
--- a/packages/SystemUI/res/layout/notification_settings_icon_row.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<com.android.systemui.statusbar.NotificationSettingsIconRow
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="invisible"
- >
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/gear_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="@dimen/notification_gear_padding"
- android:src="@drawable/ic_settings"
- android:tint="@color/notification_gear_color"
- android:alpha="0"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- />
-
-</com.android.systemui.statusbar.NotificationSettingsIconRow>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
new file mode 100644
index 0000000..5bd64de
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<com.android.systemui.statusbar.NotificationSnooze
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/snooze_snackbar_min_height"
+ android:id="@+id/notification_snooze"
+ android:clickable="true"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:background="@color/snooze_snackbar_bg">
+
+ <TextView
+ android:id="@+id/snooze_option_default"
+ style="@style/TextAppearance.SnoozeSnackBar"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:drawableTint="@android:color/white"
+ android:drawableEnd="@drawable/notification_expand_more"/>
+
+ <android.widget.Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ />
+
+ <TextView
+ android:id="@+id/undo"
+ style="@style/TextAppearance.SnoozeSnackBar.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="8dp"
+ android:layout_marginStart="8dp"
+ android:background="@drawable/btn_borderless_rect"
+ android:layout_gravity="end"
+ android:text="@string/snooze_undo" />
+
+ <LinearLayout
+ android:id="@+id/snooze_options"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:orientation="vertical"/>
+
+</com.android.systemui.statusbar.NotificationSnooze>
diff --git a/packages/SystemUI/res/layout/qs_customize_tile_frame.xml b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml
index aaa84fd..ff55f99 100644
--- a/packages/SystemUI/res/layout/qs_customize_tile_frame.xml
+++ b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml
@@ -18,7 +18,8 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:paddingStart="8dp"
android:paddingEnd="8dp"
- android:paddingBottom="16dp" />
+ android:paddingTop="8dp"
+ android:gravity="center" />
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index 50ee64a..25d5226 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -20,10 +20,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qs_detail_background"
- android:paddingBottom="8dp"
android:clickable="true"
- android:visibility="invisible"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:paddingBottom="8dp"
+ android:visibility="invisible">
<com.android.systemui.ResizingSpace
android:layout_width="match_parent"
@@ -33,17 +33,16 @@
android:id="@+id/qs_detail_header"
layout="@layout/qs_detail_header"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ />
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/qs_detail_header_progress"
- android:src="@drawable/indeterminate_anim"
- android:alpha="0"
- android:background="@color/qs_detail_progress_track"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:scaleType="fitXY"
- android:translationY="8dp"
+ android:alpha="0"
+ android:background="@color/qs_detail_progress_track"
+ android:src="@drawable/indeterminate_anim"
/>
<com.android.systemui.qs.NonInterceptingScrollView
@@ -51,12 +50,13 @@
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
+
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.android.systemui.qs.NonInterceptingScrollView>
- <include layout="@layout/qs_detail_buttons" />
+ <include layout="@layout/qs_detail_buttons"/>
</com.android.systemui.qs.QSDetail>
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
index 7876ada..6edf135 100644
--- a/packages/SystemUI/res/layout/qs_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_detail_item.xml
@@ -29,7 +29,8 @@
android:layout_width="@dimen/qs_detail_item_icon_size"
android:layout_height="@dimen/qs_detail_item_icon_size"
android:layout_marginStart="@dimen/qs_detail_item_icon_marginStart"
- android:layout_marginEnd="@dimen/qs_detail_item_icon_marginEnd" />
+ android:layout_marginEnd="@dimen/qs_detail_item_icon_marginEnd"
+ android:tint="?android:attr/textColorPrimary"/>
<LinearLayout
android:layout_width="0dp"
@@ -62,6 +63,7 @@
android:focusable="true"
android:scaleType="center"
android:contentDescription="@*android:string/media_route_controller_disconnect"
+ android:tint="?android:attr/textColorPrimary"
android:src="@drawable/ic_qs_cancel" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
index 9a7e1b6..b9cdf28 100644
--- a/packages/SystemUI/res/layout/qs_detail_items.xml
+++ b/packages/SystemUI/res/layout/qs_detail_items.xml
@@ -43,7 +43,8 @@
<ImageView
android:id="@android:id/icon"
android:layout_width="56dp"
- android:layout_height="56dp"/>
+ android:layout_height="56dp"
+ android:tint="?android:attr/textColorSecondary" />
<TextView
android:id="@android:id/title"
diff --git a/packages/SystemUI/res/layout/qs_page_indicator.xml b/packages/SystemUI/res/layout/qs_page_indicator.xml
new file mode 100644
index 0000000..02bd31a
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_page_indicator.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.systemui.qs.PageIndicator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/page_indicator"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_gravity="center"
+ android:layout_marginTop="40dp"
+ android:layout_marginBottom="24dp"
+ android:focusable="true"
+ android:gravity="center"
+ android:importantForAccessibility="yes"
+ android:visibility="gone"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
index 8b6060f..8ff1d1e 100644
--- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
+++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
@@ -28,15 +28,6 @@
android:layout_height="48dp"
android:layout_gravity="bottom">
- <com.android.systemui.qs.PageIndicator
- android:id="@+id/page_indicator"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:importantForAccessibility="yes"
- android:focusable="true" />
-
</FrameLayout>
</com.android.systemui.qs.PagedTileLayout>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 9e0a6fe..895185b 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -26,10 +26,9 @@
<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel"
android:background="#0000"
- android:layout_marginTop="80dp"
+ android:layout_marginTop="52dp"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="8dp" />
+ android:layout_height="wrap_content" />
<include layout="@layout/quick_status_bar_expanded_header" />
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 5b0f0df..a093b87 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -18,11 +18,12 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:paddingTop="16dp">
<TextView android:id="@+id/tile_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="?android:attr/textColorSecondary"
+ android:textColor="?android:attr/textColorPrimary"
android:gravity="center_horizontal"
android:minLines="2"
android:padding="0dp"
@@ -36,4 +37,4 @@
android:layout_marginLeft="@dimen/restricted_padlock_pading"
android:scaleType="centerInside"
android:visibility="gone" />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
index 080f553..6988c76 100644
--- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
@@ -17,6 +17,7 @@
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_height="48dp"
android:layout_width="match_parent"
+ android:layout_marginBottom="24dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
style="@style/BrightnessDialogContainer">
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 53acb9f..174776c 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -23,30 +23,35 @@
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_header_height"
android:layout_gravity="@integer/notification_panel_layout_gravity"
- android:clipChildren="false"
- android:clipToPadding="false"
android:baselineAligned="false"
android:clickable="false"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingTop="0dp"
+ android:paddingEnd="0dp"
+ android:paddingStart="0dp"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="48dp"
- android:gravity="center"
+ android:layout_alignParentEnd="true"
android:clipChildren="false"
android:clipToPadding="false"
- android:orientation="horizontal"
- android:layout_alignParentEnd="true"
- android:layout_marginTop="4dp"
- android:layout_marginEnd="12dp">
+ android:gravity="center"
+ android:paddingEnd="4dp"
+ android:orientation="horizontal">
- <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
+ <com.android.systemui.statusbar.phone.MultiUserSwitch
+ android:id="@+id/multi_user_switch"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
- android:focusable="true"
- android:background="@drawable/ripple_drawable" >
- <ImageView android:id="@+id/multi_user_avatar"
+ android:background="@drawable/ripple_drawable"
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/multi_user_avatar"
android:layout_width="@dimen/multi_user_avatar_expanded_size"
android:layout_height="@dimen/multi_user_avatar_expanded_size"
android:layout_gravity="center"
@@ -57,13 +62,42 @@
android:id="@android:id/edit"
android:layout_width="48dp"
android:layout_height="48dp"
- android:clipToPadding="false"
- android:clickable="true"
- android:focusable="true"
- android:src="@drawable/ic_mode_edit"
android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:clickable="true"
+ android:clipToPadding="false"
android:contentDescription="@string/accessibility_quick_settings_edit"
- android:padding="14dp" />
+ android:focusable="true"
+ android:padding="14dp"
+ android:src="@drawable/ic_mode_edit"
+ android:tint="?android:attr/colorForeground"/>
+
+ <LinearLayout
+ android:id="@+id/system_icons_super_container"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/status_bar_header_height"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_toStartOf="@id/multi_user_switch"
+ android:background="@drawable/ripple_drawable">
+
+ <FrameLayout
+ android:id="@+id/system_icons_container"
+ android:layout_width="wrap_content"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical">
+
+ <include layout="@layout/system_icons" />
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/battery_level"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="@dimen/header_battery_margin_expanded"
+ android:importantForAccessibility="noHideDescendants"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/battery_level_text_size"/>
+ </LinearLayout>
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/settings_button_container"
@@ -78,16 +112,18 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/accessibility_quick_settings_settings"
android:src="@drawable/ic_settings_20dp"
- android:contentDescription="@string/accessibility_quick_settings_settings" />
+ android:tint="?android:attr/colorForeground"/>
+
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/tuner_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="36dp"
- android:alpha="0.3"
- android:visibility="invisible"
- android:src="@drawable/tuner" />
+ android:src="@drawable/tuner"
+ android:tint="?android:attr/textColorTertiary"
+ android:visibility="invisible"/>
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
@@ -106,23 +142,26 @@
<TextView
android:id="@+id/header_emergency_calls_only"
- android:layout_height="wrap_content"
android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:paddingTop="6dp"
- android:visibility="gone"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.EmergencyCallsOnly"
- android:text="@*android:string/emergency_calls_only"
- android:singleLine="true"
+ android:focusable="true"
android:gravity="center_vertical"
- android:focusable="true" />
+ android:paddingStart="16dp"
+ android:paddingTop="6dp"
+ android:singleLine="true"
+ android:text="@*android:string/emergency_calls_only"
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.EmergencyCallsOnly"
+ android:visibility="gone"/>
<include
android:id="@+id/date_time_alarm_group"
layout="@layout/status_bar_alarm_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="12dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
@@ -130,38 +169,41 @@
android:id="@+id/quick_qs_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="52dp"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
android:layout_alignParentEnd="true"
+ android:layout_marginTop="54dp"
+ android:layout_marginBottom="24dp"
+ android:layout_alignParentTop="true"
+ android:accessibilityTraversalAfter="@+id/date_time_group"
+ android:accessibilityTraversalBefore="@id/expand_indicator"
android:clipChildren="false"
android:clipToPadding="false"
- android:importantForAccessibility="yes"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
android:focusable="true"
- android:accessibilityTraversalAfter="@+id/date_time_group"
- android:accessibilityTraversalBefore="@id/expand_indicator" />
+ android:importantForAccessibility="yes"
+ android:paddingTop="0dp"/>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/qs_detail_header_progress"
- android:src="@drawable/indeterminate_anim"
- android:alpha="0"
- android:background="@color/qs_detail_progress_track"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
+ android:alpha="0"
+ android:background="@color/qs_detail_progress_track"
+ android:src="@drawable/indeterminate_anim"
/>
<TextView
android:id="@+id/header_debug_info"
- android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:fontFamily="sans-serif-condensed"
+ android:padding="2dp"
+ android:textColor="#00A040"
android:textSize="11dp"
android:textStyle="bold"
- android:textColor="#00A040"
- android:padding="2dp"
+ android:visibility="invisible"
/>
</com.android.systemui.statusbar.phone.QuickStatusBarHeader>
diff --git a/packages/SystemUI/res/layout/recents_grid_task_view.xml b/packages/SystemUI/res/layout/recents_grid_task_view.xml
index 53bec70..1c9b9ac 100644
--- a/packages/SystemUI/res/layout/recents_grid_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_grid_task_view.xml
@@ -18,7 +18,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true">
- <com.android.systemui.recents.views.TaskViewThumbnail
+ <com.android.systemui.recents.views.grid.GridTaskViewThumbnail
android:id="@+id/task_view_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent" />
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/layout/split_clock_view.xml b/packages/SystemUI/res/layout/split_clock_view.xml
index ae5136f..8198f03 100644
--- a/packages/SystemUI/res/layout/split_clock_view.xml
+++ b/packages/SystemUI/res/layout/split_clock_view.xml
@@ -28,6 +28,7 @@
android:singleLine="true"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
android:textSize="@dimen/qs_time_collapsed_size"
+ android:textColor="?android:attr/textColorPrimary"
/>
<TextClock
android:id="@+id/am_pm_view"
@@ -36,6 +37,7 @@
android:singleLine="true"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
android:textSize="@dimen/qs_time_collapsed_size"
+ android:textColor="?android:attr/textColorPrimary"
android:importantForAccessibility="no"
/>
@@ -44,8 +46,9 @@
android:id="@+id/empty_time_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:visibility="invisible"
+ android:visibility="gone"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+ android:textColor="?android:attr/textColorPrimary"
/>
</com.android.systemui.statusbar.policy.SplitClockView>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 6784254..63af3e0 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -42,7 +42,7 @@
<LinearLayout android:id="@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingStart="@dimen/status_bar_padding_start"
+ android:paddingStart="6dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
>
diff --git a/packages/SystemUI/res/layout/status_bar_alarm_group.xml b/packages/SystemUI/res/layout/status_bar_alarm_group.xml
index a02e9af..745320e 100644
--- a/packages/SystemUI/res/layout/status_bar_alarm_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_alarm_group.xml
@@ -41,6 +41,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/ic_access_alarms_small"
+ android:tint="?android:attr/textColorPrimary"
android:paddingStart="6dp"
android:gravity="center"
android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index e456984..d62cc18 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -24,10 +24,10 @@
>
<ViewStub
- android:layout="@layout/notification_settings_icon_row"
- android:id="@+id/settings_icon_row_stub"
- android:inflatedId="@+id/notification_settings_icon_row"
- android:layout_width="wrap_content"
+ android:layout="@layout/notification_menu_row"
+ android:id="@+id/menu_row_stub"
+ android:inflatedId="@+id/notification_menu_row"
+ android:layout_width="match_parent"
android:layout_height="match_parent"
/>
diff --git a/packages/SystemUI/res/layout/tuner_shortcut_item.xml b/packages/SystemUI/res/layout/tuner_shortcut_item.xml
new file mode 100644
index 0000000..e9eae3b
--- /dev/null
+++ b/packages/SystemUI/res/layout/tuner_shortcut_item.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ android:clickable="true"
+ android:gravity="center"
+ android:background="?android:attr/selectableItemBackground">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp" />
+
+ <TextView android:id="@android:id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorPrimary" />
+
+ <com.android.systemui.statusbar.phone.ExpandableIndicator
+ android:id="@+id/expand"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp"
+ android:visibility="gone" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_root.xml b/packages/SystemUI/res/layout/tuner_shortcut_list.xml
similarity index 62%
copy from packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_root.xml
copy to packages/SystemUI/res/layout/tuner_shortcut_list.xml
index 85573e0..9aaffb4 100644
--- a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_root.xml
+++ b/packages/SystemUI/res/layout/tuner_shortcut_list.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 The Android Open Source Project
+<!-- 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.
@@ -14,11 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.3"
- android:valueTo="1.0"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
+
+<android.support.v7.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="200dp"
+ android:gravity="center" />
diff --git a/packages/SystemUI/res/layout/zen_mode_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml
index ca4f727..2b4a0f5 100644
--- a/packages/SystemUI/res/layout/zen_mode_condition.xml
+++ b/packages/SystemUI/res/layout/zen_mode_condition.xml
@@ -63,6 +63,7 @@
android:scaleType="center"
android:layout_toStartOf="@android:id/button2"
android:contentDescription="@string/accessibility_quick_settings_less_time"
+ android:tint="?android:attr/textColorPrimary"
android:src="@drawable/ic_qs_minus" />
<ImageView
@@ -75,6 +76,7 @@
android:scaleType="center"
android:layout_centerVertical="true"
android:contentDescription="@string/accessibility_quick_settings_more_time"
+ android:tint="?android:attr/textColorPrimary"
android:src="@drawable/ic_qs_plus" />
</RelativeLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 2d10668..d145f7e 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> waarskuwing"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Werkmodus"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Aandbeligting"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Geen onlangse items nie"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Jy het alles toegemaak"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Programinligting"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Verdeel horisontaal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Verdeel vertikaal"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Verdeel gepasmaak"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Maak toe"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Maak oop"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Verdeel skerm na bo"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Verdeel skerm na links"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Verdeel skerm na regs"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Gelaai"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Ontkoppel VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Jou toestel word bestuur deur <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> gebruik <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> om jou toestel te bestuur."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Jou administrateur kan instellings, korporatiewe toegang, programme, data wat met jou toestel geassosieer word en jou toestel se ligginginligting monitor en bestuur."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Kom meer te wete"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Jy is gekoppel aan <xliff:g id="VPN_APP">%1$s</xliff:g>, wat jou netwerkaktiwiteit, insluitend e-posse, programme en webwerwe, kan monitor."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Maak VPN-instellings oop"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Jou administrateur het netwerkloglêers aangeskakel wat verkeer op jou toestel monitor.\n\nKontak jou administrateur vir meer inligting."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Jy het \'n program toestemming gegee om \'n VPN-verbinding op te stel.\n\nHierdie program kan jou toestel- en netwerkaktiwiteit monitor, insluitend e-posse, programme en webwerwe."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Jou werkprofiel word deur <xliff:g id="ORGANIZATION">%1$s</xliff:g> bestuur.\n\nJou administrateur is in staat om jou netwerkaktiwiteit, insluitend e-posse, programme en webwerwe, te monitor.\n\nKontak jou administrateur vir meer inligting.\n\nJy is ook aan \'n VPN gekoppel wat jou netwerkaktiwiteit kan monitor."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Jy is gekoppel aan <xliff:g id="APPLICATION">%1$s</xliff:g>, wat jou netwerkaktiwiteit, insluitend e-posse, programme en webwerwe, kan monitor."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Jy is gekoppel aan <xliff:g id="APPLICATION">%1$s</xliff:g>, wat jou persoonlike netwerkaktiwiteit, insluitend e-posse, programme en webwerwe, kan monitor."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Jy is gekoppel aan <xliff:g id="APPLICATION">%1$s</xliff:g>, wat jou persoonlike netwerkaktiwiteit, insluitend e-posse, programme en webwerwe, kan monitor."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Jou werkprofiel word deur <xliff:g id="ORGANIZATION">%1$s</xliff:g> bestuur. Dit is gekoppel aan <xliff:g id="APPLICATION">%2$s</xliff:g>, wat jou werknetwerkaktiwiteit, insluitend e-posse, programme en webwerwe, kan monitor.\n\nKontak jou administrateur vir meer inligting."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Jou werkprofiel word deur <xliff:g id="ORGANIZATION">%1$s</xliff:g> bestuur. Dit is gekoppel aan <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, wat jou werknetwerkaktiwiteit, insluitend e-posse, programme en webwerwe, kan monitor.\n\nJy is ook gekoppel aan <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, wat jou persoonlike netwerkaktiwiteit kan monitor."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Toestel sal gesluit bly totdat jy dit handmatig ontsluit"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Kry kennisgewings vinniger"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index a45dd18..c2bd45f 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"የ<xliff:g id="DATA_LIMIT">%s</xliff:g> ማስጠንቀቂያ"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"የሥራ ሁነታ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"የምሽት ብርሃን"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"ኤንኤፍሲ"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"ኤንኤፍሲ ተሰናክሏል"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"ኤንኤፍሲ ነቅቷል"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ሁሉንም ነገር አጽድተዋል"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"የመተግበሪያ መረጃ"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"አግድም ክፈል"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ቁልቁል ክፈል"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"በብጁ ክፈል"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"አሰናብት"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"ክፈት"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ማያ ገጽ ወደ ላይ ክፈል"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"ማያ ገጽ ወደ ግራ ክፈል"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"ማያ ገጽ ወደ ቀኝ ክፈል"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"ባትሪ ሞልቷል"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"የVPN ግንኙነት አቋርጥ"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"የእርስዎ መሣሪያ በ<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> ነው የሚቀናበረው።"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> የእርስዎን መሣሪያ ለማቀናበር <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>ን ይጠቀማል።"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"የእርስዎ አስተዳዳሪ ከዚህ መሣሪያ ጋር የተጎዳኙ ቅንብሮችን፣ የኮርፖሬት መዳረሻን፣ መተግበሪያዎችን፣ እና የመሣሪያዎን አካባቢ መከታተል እና ማቀናበር ይችላሉ።"</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"የእርስዎ አስተዳዳሪ ቅንብሮችን፣ የኮርፖሬት መዳረሻን፣ መተግበሪያዎችን፣ ከዚህ መሣሪያ ጋር የተጎዳኘ ውሂብን እና የመሣሪያዎን አካባቢ መከታተል እና ማቀናበር ይችላሉ።"</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"የበለጠ ለመረዳት"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"እርስዎ ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችንም ጨምሮ የግል የአውታረ መረብ እንቅስቃሴዎን መከታተል ከሚችለው <xliff:g id="VPN_APP">%1$s</xliff:g> ጋር ተገናኝተዋል።"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"የVPN ቅንብሮችን ይክፈቱ"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"የእርስዎ አስተዳዳሪ የአውታረ መረብ ምዝግብ ማስታወሻ መያዝን አብርተዋል፣ ይህም በመሣሪያዎ ላይ ያለው ትራፊክ ይከታተላል።\n\nተጨማሪ መረጃ ለማግኘት አስተዳዳሪዎን ያነጋግሩ።"</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"የእርስዎ አስተዳዳሪ የአውታረ መረብ ምዝግብ ማስታወሻ መያዝን አብርተዋል፣ ይህም በመሣሪያዎ ላይ ያለውን ትራፊክ ይከታተላል።\n\nተጨማሪ መረጃ ለማግኘት አስተዳዳሪዎን ያነጋግሩ።"</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"አንድ መተግበሪያ የVPN ግንኙነት እንዲያዋቅር ፍቃድ ሰጥተውታል።\n\nይህ መተግበሪያ ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችንም ጨምሮ የመሣሪያዎን እና የአውታረ መረብ እንቅስቃሴዎን መከታተል ይችላል።"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"የስራ መገለጫዎ በ<xliff:g id="ORGANIZATION">%1$s</xliff:g> ነው የሚተዳደረው።\n\nየእርስዎ አስተዳዳሪ ቅንብሮችን፣ የኮርፖሬት መዳረሻን፣ መተግበሪያዎችን፣ ከመሣሪያዎ ጋር የተጎዳኘ ውሂብን እና የመሣሪያዎ የአካባቢ መረጃን መከታተል እና ማቀናበር ይችላል።\n\nተጨማሪ መረጃ ለማግኘት አስተዳዳሪዎን ያነጋግሩ።\n\nእንዲሁም የአውታረ መረብ ግንኙነትዎን መከታተል ከሚችል አንድ VPN ጋር ተገናኝተዋል።"</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"የእርስዎ የስራ መገለጫ በ<xliff:g id="ORGANIZATION">%1$s</xliff:g> ነው የሚቀናበረው።\n\nየእርስዎ አስተዳዳሪ ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችን ጨምሮ የአውታረ መረብ እንቅስቃሴዎን መከታተል ይችላል።\n\nተጨማሪ መረጃ ለማግኘት አስተዳዳሪዎን ያነጋግሩ።\n\nእርስዎ እንዲሁም የአውታረ መረብ እንቅስቃሴዎን መከታተል ከሚችል ቪፒኤን ጋር ተገናኝተዋል።"</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"እርስዎ ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችንም ጨምሮ የአውታረ መረብ እንቅስቃሴዎን መከታተል ከሚችለው <xliff:g id="APPLICATION">%1$s</xliff:g> ጋር ተገናኝተዋል።"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"እርስዎ ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችንም ጨምሮ የግል የአውታረ መረብ እንቅስቃሴዎን መከታተል ከሚችለው <xliff:g id="APPLICATION">%1$s</xliff:g> ጋር ተገናኝተዋል።"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"እርስዎ ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችንም ጨምሮ የግል የአውታረ መረብ እንቅስቃሴዎን ከሚከታተለው ከ<xliff:g id="APPLICATION">%1$s</xliff:g> ጋር ተገናኝተዋል።"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"የስራ መገለጫዎ በ<xliff:g id="ORGANIZATION">%1$s</xliff:g> ነው እየተዳደረ ያለው። ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችንም ጨምሮ የአውታረ መረብ እንቅስቃሴዎን መከታተል ከሚችለው <xliff:g id="APPLICATION">%2$s</xliff:g> ጋር ተገናኝተዋል።\n\nተጨማሪ መረጃ ለማግኘት አስተዳዳሪዎን ያነጋግሩ።"</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"የእርስዎ የስራ መገለጫ በ<xliff:g id="ORGANIZATION">%1$s</xliff:g> ነው የሚቀናበረው። ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችን ጨምሮ የአውታረ መረብ እንቅስቃሴዎን መከታተል ከሚችለው <xliff:g id="APPLICATION">%2$s</xliff:g> ጋር ተገናጥቷል።\n\nተጨማሪ መረጃ ለማግኘት አስተዳዳሪዎን ያነጋግሩ።"</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"የስራ መገለጫዎ በ<xliff:g id="ORGANIZATION">%1$s</xliff:g> ነው እየተዳደረ ያለው። ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችንም ጨምሮ የአውታረ መረብ እንቅስቃሴዎን መከታተል ከሚችለው <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ጋር ተገናኝተዋል።\n\nእንዲሁም የግል አውታረ መረብ እንቅስቃሴዎን መከታተል ከሚችለው <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ጋርም ተገናኝተዋል።"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"እራስዎ እስኪከፍቱት ድረስ መሣሪያ እንደተቆለፈ ይቆያል"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ማሳወቂያዎችን ፈጥነው ያግኙ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 074f754..2de58fe 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -329,6 +329,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"تحذير <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"وضع العمل"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"إضاءة ليلية"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"الاتصال القريب المدى (NFC)"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"تم تعطيل الاتصال القريب المدى"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"تم تمكين الاتصال القريب المدى"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ليست هناك عناصر تم استخدامها مؤخرًا"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"لقد محوتَ كل شيء"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"معلومات التطبيق"</string>
@@ -342,6 +345,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"تقسيم أفقي"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"تقسيم رأسي"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"تقسيم مخصص"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"تجاهل"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"فتح"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"تقسيم الشاشة بمحاذاة الجزء العلوي"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"تقسيم الشاشة بمحاذاة اليسار"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"تقسيم الشاشة بمحاذاة اليمين"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"تم الشحن"</string>
@@ -422,20 +430,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"قطع الاتصال بشبكة VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"تتم إدارة جهازك بواسطة <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"تستخدم <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> تطبيق <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> لإدارة جهازك."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"يمكن للمشرف مراقبة الإعدادات وإدارتها والدخول إلى المؤسسة والتطبيقات والبيانات المقترنة بجهازك ومعلومات موقع الجهاز."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"يمكن للمشرف مراقبة الإعدادات وإدارتها والدخول إلى المؤسسة والتطبيقات والبيانات المرتبطة بجهازك ومعلومات موقع الجهاز."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"مزيد من المعلومات"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"لقد اتصلت بتطبيق <xliff:g id="VPN_APP">%1$s</xliff:g>، الذي يمكن أن يراقب نشاط الشبكة، بما في ذلك رسائل البريد الإلكتروني والتطبيقات والمواقع الإلكترونية."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"فتح إعدادات الشبكة الظاهرية الخاصة (VPN)"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"شغَّل المشرف ميزة تسجيل بيانات الشبكة، والتي يتم من خلالها مراقبة حركة البيانات على جهازك.\n\nللحصول على المزيد من المعلومات، اتصل بالمشرف."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"شغَّل المشرف ميزة تسجيل بيانات الشبكة، والتي يتم من خلالها مراقبة حركة البيانات على جهازك.\n\nللحصول على المزيد من المعلومات، اتصل بالمشرف."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"لقد منحت تطبيقًا الإذن لإعداد اتصال شبكة ظاهرية خاصة (VPN).\n\nيمكن لهذا التطبيق مراقبة أنشطتك على الجهاز والشبكة، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"تتم إدارة ملفك الشخصي للعمل عن طريق <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nبإمكان المشرف مراقبة أنشطتك على الشبكة، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب.\n\nللمزيد من المعلومات، اتصل بالمشرف.\n\nأنت متصل أيضًا بشبكة ظاهرية خاصة (VPN)، يمكنها مراقبة أنشطتك على الشبكة."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"تتم إدارة ملفك الشخصي للعمل بواسطة <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nويمكن للمشرف مراقبة نشاط الشبكة، بما في ذلك رسائل البريد الإلكتروني والتطبيقات والمواقع الإلكترونية.\n\nللحصول على المزيد من المعلومات، اتصل بالمشرف.\n\nوتجدر الإشارة إلى أنك متصل أيضًا بشبكة ظاهرية خاصة يمكن أن تراقب نشاط الشبكة."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"شبكة ظاهرية خاصة"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"أنت متصل بـ <xliff:g id="APPLICATION">%1$s</xliff:g>، الذي يمكنه مراقبة أنشطتك على الشبكة، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"أنت متصل بـ <xliff:g id="APPLICATION">%1$s</xliff:g>، الذي يمكنه مراقبة أنشطتك الشخصية على الشبكة، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"أنت متصل بـ <xliff:g id="APPLICATION">%1$s</xliff:g>، الذي يمكنه مراقبة أنشطتك الشخصية على الشبكة، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"تتم إدارة ملفك الشخصي للعمل عن طريق <xliff:g id="ORGANIZATION">%1$s</xliff:g>. وهذا الملف الشخصي للعمل متصل بـ <xliff:g id="APPLICATION">%2$s</xliff:g>، الذي يمكنه مراقبة أنشطتك على شبكة العمل، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب.\n\nللمزيد من المعلومات، اتصل بالمشرف."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"تتم إدارة ملفك الشخصي للعمل بواسطة <xliff:g id="ORGANIZATION">%1$s</xliff:g>، وهو متصل بتطبيق <xliff:g id="APPLICATION">%2$s</xliff:g>، الذي يمكن أن يراقب نشاطك على شبكة العمل، بما في ذلك رسائل البريد الإلكتروني والتطبيقات والمواقع الإلكترونية.\n\nللحصول على المزيد من المعلومات، اتصل بالمشرف."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"تتم إدارة ملفك الشخصي للعمل عن طريق <xliff:g id="ORGANIZATION">%1$s</xliff:g>. وهذا الملف الشخصي للعمل متصل بـ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>، الذي يمكنه مراقبة أنشطتك على شبكة العمل، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب.\n\nأنت متصل أيضًا بـ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>، الذي يمكنه مراقبة أنشطتك الشخصية على الشبكة."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"سيظل الجهاز مقفلاً إلى أن يتم إلغاء قفله يدويًا"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"الحصول على الإشعارات بشكل أسرع"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 7169d54..a60ee26 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> xəbərdarlığı"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"İş rejimi"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Gecə işığı"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Son elementlər yoxdur"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Hərşeyi təmizlədiniz"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Tətbiq haqqında"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Üfüqi Böl"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Şaquli Böl"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Fərdi Böl"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Rədd edin"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Açın"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ekranı yuxarıdan ayırın"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ekranı soldan ayırın"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ekranı sağdan ayırın"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Dolub"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN-i bağlantıdan ayırın"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Cihaz <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> tərəfindən idarə olunur."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> cihazınızı idarə etmək üçün <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> istifadə edir."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administratorunuz ayarlara, korporativ girişə, tətbiqlərə, cihaz ilə əlaqədar dataya və cihazın məkan məlumatına nəzarət və idarə edə bilər."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" ("</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Ətraflı məlumat"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"<xliff:g id="VPN_APP">%1$s</xliff:g> tətbiqinə qoşulmusunuz və o, e-məktublar, tətbiq və veb saytlar daxil olmaqla şəbəkə fəaliyyətinizə nəzarət edə bilər."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" ("</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN Ayarlarını açın"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Admin, cihazdakı trafikə nəzarət edən şəbəkə loqlarını aktiv etdi.\n\nƏtraflı məlumat üçün admin ilə əlaqə saxlayın."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN bağlantısı quraşdırmağa icazə vermisiniz.\n\nBu tətbiq cihazınızı və şəbəkə fəaliyyətinizi, həmçinin, e-məktubları, tətbiq və veb saytları izləyə bilər."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Sizin iş profile tərəfindən idarə olunur <xliff:g id="ORGANIZATION">%1$s</xliff:g> . \n\n Sizin administrator e-poçt, apps, və web o cümlədən şəbəkə fəaliyyəti monitorinq qadirdir. \n\n Daha ətraflı məlumat üçün, administratora müraciət. \n\n Siz həmçinin şəbəkə fəaliyyətinə nəzarət edə bilərsiniz bir VPN, bağlı olduğunuz."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN (Virtual Şəxsi Şəbəkələr)"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"<xliff:g id="APPLICATION">%1$s</xliff:g> tətbiqinə qoşulmusunuz və o, e-məktublar, tətbiq və veb saytlar daxil olmaqla şəbəkə fəaliyyətinizə nəzarət edə bilər."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g> tətbiqinə qoşulmusunuz və o, e-məktublar, tətbiq və veb saytlar daxil olmaqla şəxsi şəbəkə fəaliyyətinizə nəzarət edə bilər."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> tətbiqinə qoşulmusunuz və o, e-məktublar, tətbiq və veb saytlar daxil olmaqla şəxsi şəbəkə fəaliyyətinizə nəzarət edə bilər."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"İş profiliniz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tərəfindən idarə olunur. <xliff:g id="APPLICATION">%2$s</xliff:g> tətbiqinə qoşuludur və iş şəbəkə fəaliyyətinizə nəzarət edə bilər, bura e-məktubıar, tətbiq və veb saytlar daxildir.\n\nƏtraflı məlumat üçün administratorunuz ilə əlaqə saxlayın."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"İş profiliniz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tərəfindən idarə olunur. <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> tətbiqinə qoşuludur və iş şəbəkə fəaliyyətinizi idarə edə bilər, bura e-məktubıar, tətbiq və veb saytlar daxildir\n\nSiz, həmçinin, <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> tətbiqinə də qoşulsunuz və o, şəxsi şəbəkə fəaliyyətinizə nəzarət edə bilər."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Bildirişləri daha sürətlə əldə edin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 465bf70..4738057 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozorenje za <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Režim rada"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Noćno svetlo"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC je onemogućen"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC je omogućen"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nema nedavnih stavki"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Obrisali ste sve"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacije o aplikaciji"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Podeli horizontalno"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Podeli vertikalno"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Prilagođeno deljenje"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Odbaci"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Otvori"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Podeli ekran nagore"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Podeli ekran nalevo"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Podeli ekran nadesno"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Napunjena je"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Prekini vezu sa VPN-om"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Uređajem upravlja <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> koristi <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> za upravljanje uređajem."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administrator može da nadgleda podešavanja, korporativni pristup, aplikacije, podatke povezane sa uređajem i informacije o lokaciji uređaja, kao i da upravlja njima."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Administrator može da nadgleda podešavanja, korporativni pristup, aplikacije, podatke povezane sa uređajem i informacije o lokaciji uređaja, kao i da upravlja njima."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Saznajte više"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Povezani ste sa aplikacijom <xliff:g id="VPN_APP">%1$s</xliff:g>, koja može da nadgleda aktivnosti na mreži, uključujući imejlove, aplikacije i veb-sajtove."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Otvorite podešavanja VPN-a"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administrator je uključio evidentiranje mreže, koje prati saobraćaj na uređaju.\n\nKontaktirajte administratora za više informacija."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Administrator je uključio evidentiranje mreže, koje prati saobraćaj na uređaju.\n\nKontaktirajte administratora za više informacija."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Dali ste dozvolu aplikaciji da podešava VPN vezu.\n\nTa aplikacija može da nadgleda aktivnosti na uređaju i mreži, uključujući imejlove, aplikacije i veb-sajtove."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Profilom za Work upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministrator može da nadgleda aktivnosti na mreži, uključujući imejlove, aplikacije i veb-sajtove.\n\nViše informacija potražite od administratora.\n\nPovezani ste i na VPN, koji može da nadgleda aktivnosti na ličnoj mreži."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> upravlja profilom za Work.\n\nAdministrator može da prati aktivnosti na mreži, uključujući imejlove, aplikacije i veb-sajtove.\n\nKontaktirajte administratora za više informacija.\n\nPovezani ste i sa VPN-om, koji može da prati aktivnosti na mreži."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Povezani ste sa aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g>, koja može da nadgleda aktivnosti na mreži, uključujući imejlove, aplikacije i veb-sajtove."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Povezani ste sa aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g>, koja može da nadgleda aktivnosti na ličnoj mreži, uključujući imejlove, aplikacije i veb-sajtove."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Povezani ste sa aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g>, koja može da nadgleda aktivnosti na ličnoj mreži, uključujući imejlove, aplikacije i veb-sajtove."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Profilom za Work upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je sa aplikacijom <xliff:g id="APPLICATION">%2$s</xliff:g>, koja može da nadgleda aktivnosti na poslovnoj mreži, uključujući imejlove, aplikacije i veb-sajtove.\n\nViše informacija potražite od administratora."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> upravlja profilom za Work. On je povezan sa aplikacijom <xliff:g id="APPLICATION">%2$s</xliff:g>, koja može da prati aktivnosti na poslovnoj mreži, uključujući imejlove, aplikacije i veb-sajtove.\n\nKontaktirajte administratora za više informacija."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Profilom za Work upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je sa aplikacijom <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, koja može da nadgleda aktivnosti na poslovnoj mreži, uključujući imejlove, aplikacije i veb-sajtove.\n\nPovezani ste i sa aplikacijom <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, koja može da nadgleda aktivnosti na ličnoj mreži."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Uređaj će ostati zaključan dok ga ne otključate ručno"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Brže dobijajte obaveštenja"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index ee65b49..efbc1d2 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -327,6 +327,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Папярэджанне: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Рэжым працы"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Начная падсветка"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC адключаны"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC уключаны"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Няма нядаўніх элементаў"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Вы ачысцілі усё"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Звесткі аб праграме"</string>
@@ -340,6 +343,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Падзяліць гарызантальна"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Падзяліць вертыкальна"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Падзяліць іншым чынам"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Адхіліць"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Адкрыць"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Падзяліць экран зверху"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Падзяліць экран злева"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Падзяліць экран справа"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Зараджаны"</string>
@@ -420,20 +428,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Адлучыць VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Ваша прылада знаходзіцца пад кіраваннем <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> выкарыстоўвае <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> для кіравання вашай прыладай."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Адміністратар можа маніторыць налады, карп. доступ, прагр., даныя, звяз. з прыл., і звесткі пра месцазн. прылады і кіраваць гэтым."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Адмін-р можа сачыць за наладамі, карп. доступам, прагр., данымі, звяз. з прыл., звесткамі пра месцазн. прылады і кіраваць гэтым."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Даведацца больш"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Вы падключаны да праграмы <xliff:g id="VPN_APP">%1$s</xliff:g>, якая можа сачыць за вашай сеткавай дзейнасцю, уключаючы электронную пошту, праграмы і вэб-сайты."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" ,"</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Адкрыйце налады VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Ваш адміністратар уключыў вядзенне журнала сеткі, з дапамогай якога адсочваецца трафік на вашай прыладзе.\n\nДля атрымання дадатковай інфармацыі звярніцеся да адміністратара."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Ваш адміністратар уключыў вядзенне журнала сеткі, з дапамогай якога адсочваецца трафік на вашай прыладзе.\n\nДля атрымання дадатковай інфармацыі звярніцеся да адміністратара."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Вы далі праграме дазвол на наладжванне злучэння VPN.\n\nГэта праграма можа сачыць за актыўнасцю вашай прылады і вашай сеткавай актыўнасцю, уключаючы электронную пошту, праграмы і вэб-сайты."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Ваш працоўны профіль знаходзіцца пад кіраваннем <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nВаш адміністратар можа сачыць і кіраваць наладамі, доступам да карпаратыўных рэсурсаў, праграмамі, данымі, звязанымі з вашай прыладай, і звесткамі пра месцазнаходжанне прылады.\n\nДля атрымання дадатковай інфармацыі звярніцеся да адміністратара.\n\nВы таксама падключаны да VPN, які можа сачыць за вашай сеткавай актыўнасцю."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Ваш працоўны профіль знаходзіцца пад кіраваннем <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nВаш адміністратар можа сачыць за вашай сеткавай дзейнасцю, уключаючы электронную пошту, праграмы і вэб-сайты.\n\nДля атрымання дадатковай інфармацыі звярніцеся да адміністратара.\n\nВы таксама падключаны да сеткі VPN, якая можа сачыць за вашай сеткавай дзейнасцю."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Вы падлучаны да праграмы <xliff:g id="APPLICATION">%1$s</xliff:g>, якая можа сачыць за вашай сеткавай актыўнасцю, уключаючы электронную пошту, праграмы і вэб-сайты."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Вы падлучаны да праграмы <xliff:g id="APPLICATION">%1$s</xliff:g>, якая сачыць за вашай асабістай сеткавай актыўнасцю, уключаючы электронную пошту, праграмы і вэб-сайты."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Вы падключаны да праграмы <xliff:g id="APPLICATION">%1$s</xliff:g>, якая можа сачыць за вашай асабістай сеткавай дзейнасцю, уключаючы электронную пошту, праграмы і вэб-сайты."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Ваш працоўны профіль знаходзіцца пад кіраваннем <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ён падлучаны да праграмы <xliff:g id="APPLICATION">%2$s</xliff:g>, якая можа сачыць за вашай сеткавай актыўнасцю, уключаючы электронную пошту, праграмы і вэб-сайты.\n\nДля атрымання дадатковай інфармацыі звярніцеся да адміністратара."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Ваш працоўны профіль знаходзіцца пад кіраваннем <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ён падключаны да праграмы <xliff:g id="APPLICATION">%2$s</xliff:g>, якая можа сачыць за вашай сеткавай дзейнасцю, уключаючы электронную пошту, праграмы і вэб-сайты.\n\nДля атрымання дадатковай інфармацыі звярніцеся да адміністратара."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Ваш працоўны профіль знаходзіцца пад кіраваннем <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ён падлучаны да праграмы <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, якая можа сачыць за вашай сеткавай актыўнасцю, уключаючы электронную пошту, праграмы і вэб-сайты.\n\nВы таксама падлучаны да праграмы <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, якая можа сачыць за вашай асабістай сеткавай актыўнасцю."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Прылада будзе заставацца заблакіраванай, пакуль вы не разблакіруеце яе ўручную"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Атрымлівайце апавяшчэнні хутчэй"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index bc07ed2..8cb61d9 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Предупреждение: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Работен режим"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Нощно осветление"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"КБП"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"КБП е деактивирана"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"КБП е активирана"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Няма скорошни елементи"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Изчистихте всичко"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Информация за приложението"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Хоризонтално разделяне"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Вертикално разделяне"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Персонализирано разделяне"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Отхвърляне"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Отваряне"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Разделяне на екрана нагоре"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Разделяне на екрана наляво"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Разделяне на екрана надясно"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Заредена"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Прекратяване на връзката с VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Устройството ви се управлява от <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> използва <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>, за да управлява устройството ви."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Администраторът ви може да набл. и управл. настройките, корпор. достъп, прилож., данните, свързани с у-вото, както и информ. за местоп. на у-вото ви."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Администраторът ви може да набл. и управл. настройките, корпор. достъп, прилож., данните, свързани с у-вото, както и информ. за местоп. му."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Научете повече"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Установена е връзка с приложението <xliff:g id="VPN_APP">%1$s</xliff:g>, което може да наблюдава активността ви в мрежата, вкл. имейли, приложения и уебсайтове."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Отваряне на настройките за VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Администраторът ви е включил функцията за регистриране на мрежовата активност, която следи трафика на устройството ви.\n\nЗа повече информация се свържете с администратора си."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Администраторът ви е включил функцията за регистриране на мрежовата активност, която следи трафика на устройството ви.\n\nЗа повече информация се свържете с администратора си."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Разрешихте на приложение да настрои връзка с виртуална частна мрежа (VPN).\n\nТова приложение може да наблюдава активността ви на устройството и в мрежата, включително имейли, приложения и уебсайтове."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Служебният ви потребителски профил се управлява от <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nАдминистраторът ви може да наблюдава активността ви в мрежата, включително имейли, приложения и уебсайтове.\n\nЗа още информация се свържете с администратора си.\n\nСъщо така е установена връзка с виртуална частна мрежа (VPN) и активността ви в нея може да се наблюдава."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Служебният ви потребителски профил се управлява от <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nАдминистраторът ви може да наблюдава активността ви в мрежата, включително имейли, приложения и уебсайтове.\n\nЗа повече информация се свържете с администратора си.\n\nСъщо така е установена връзка с виртуална частна мрежа (VPN) и активността ви в нея може да се наблюдава."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Установена е връзка с приложението <xliff:g id="APPLICATION">%1$s</xliff:g>, което може да наблюдава активността ви в мрежата, включително имейли, приложения и уебсайтове."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Установена е връзка с приложението <xliff:g id="APPLICATION">%1$s</xliff:g>, което може да наблюдава личната ви активност в мрежата, включително имейли, приложения и уебсайтове."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Установена е връзка с приложението <xliff:g id="APPLICATION">%1$s</xliff:g>, което може да наблюдава личната ви активност в мрежата, включително имейли, приложения и уебсайтове."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Служебният ви потребителски профил се управлява от <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Той е свързан с приложението <xliff:g id="APPLICATION">%2$s</xliff:g>, което може да наблюдава служебната ви активност в мрежата, включително имейли, приложения и уебсайтове.\n\nЗа още информация се свържете с администратора си."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Служебният ви потребителски профил се управлява от <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Той е свързан с приложението <xliff:g id="APPLICATION">%2$s</xliff:g>, което може да наблюдава служебната ви активност в мрежата, включително имейли, приложения и уебсайтове.\n\nЗа повече информация се свържете с администратора си."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Служебният ви потребителски профил се управлява от <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Той е свързан с приложението <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, което може да наблюдава служебната ви активност в мрежата, включително имейли, приложения и уебсайтове.\n\nУстановена е връзка и с приложението <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, което може да наблюдава личната ви активност в мрежата."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Устройството ще остане заключено, докато не го отключите ръчно"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Получавайте известия по-бързо"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 65f74c8..ef0063f 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> সতর্কতা"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"কাজের মোড"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"নাইট লাইট"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC অক্ষম করা আছে"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC সক্ষম করা আছে"</string>
<string name="recents_empty_message" msgid="808480104164008572">"কোনো সাম্প্রতিক আইটেম নেই"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"আপনি সবকিছু সাফ করেছেন"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"অ্যাপ্লিকেশানের তথ্য"</string>
@@ -334,6 +337,16 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"অনুভূমিক স্প্লিট"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"উল্লম্ব স্প্লিট"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"কাস্টম স্প্লিট করুন"</string>
+ <!-- no translation found for recents_accessibility_dismissed (2354459747918667050) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_open (1651449827614876864) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_top (9056056469282256287) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_left (8987144699630620019) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_right (275069779299592867) -->
+ <skip />
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"চার্জ হয়েছে"</string>
@@ -414,20 +427,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN এর সংযোগ বিচ্ছিন্ন করুন"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"আপনার ডিভাইসটি <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> এর দ্বারা পরিচালিত৷"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> আপনার ডিভাইস পরিচালনা করার জন্য <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> ব্যবহার করে৷"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"আপনার প্রশাসক আপনার ডিভাইসের অবস্থান তথ্য সহ এই ডিভাইসের সেটিংস, কর্পোরেট অ্যাক্সেস, অ্যাপ্স, ডেটা নিরীক্ষণ ও পরিচালনা করতে পারেন৷"</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"আপনার প্রশাসক আপনার ডিভাইসের অবস্থান তথ্য সহ এই ডিভাইসের সেটিংস, কর্পোরেট অ্যাক্সেস, অ্যাপ্স, ডেটা নিরীক্ষণ ও পরিচালনা করতে পারেন।"</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"আরো জানুন"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"আপনি <xliff:g id="VPN_APP">%1$s</xliff:g> এ সংযুক্ত হয়েছেন, যা ইমেল, অ্যাপ এবং ওয়েবসাইটগুলি সহ আপনার নেটওয়ার্ক কার্যকলাপ নিরীক্ষণ করবে৷"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN সেটিংস খুলুন"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"আপনার প্রশাসক \'নেটওয়ার্ক লগিং\' চালু করেছেন, যা আপনার ডিভাইসের ট্রাফিক মনিটর করে৷\n\nআরো তথ্যের জন্য আপনার প্রশাসকের সাথে যোগাযোগ করুন৷"</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"আপনার প্রশাসক নেটওয়ার্ক লগিং চালু করেছেন, যা আপনার ডিভাইসের ট্রাফিক নিরীক্ষণ করে।\n\nআরো তথ্যের জন্য আপনার প্রশাসকের সাথে যোগাযোগ করুন।"</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"আপনি VPN সংযোগ সেট আপ করার জন্য একটি অ্যাপ্লিকেশানকে অনুমতি দিন৷\n\nএই অ্যাপ্লিকেশানটি ইমেল, অ্যাপ্লিকেশান ও ওয়েবসাইটগুলি সহ আপনার ডিভাইস এবং নেটওয়ার্কের কার্যকলাপ নিরীক্ষণ করতে পারে।"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> আপনার কাজের প্রোফাইল পরিচালনা করে৷\n\nআপনার প্রশাসক ইমেল, অ্যাপ্লিকেশান ও ওয়েবসাইটগুলি সহ আপনার নেটওয়ার্কের কার্যকলাপ নিরীক্ষণ করতে সক্ষম৷\n\nআরো তথ্যের জন্য, আপনার প্রশাসকের সাথে যোগাযোগ করুন৷\n\nএছাড়াও আপনি একটি VPN, এর সাথে সংযুক্ত রয়েছেন যা আপনার নেটওয়ার্কের কার্যকলাপ নিরীক্ষণ করতে পারে৷"</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"আপনার কর্মস্থলের প্রোফাইলটি <xliff:g id="ORGANIZATION">%1$s</xliff:g> দ্বারা পরিচালিত হয়।\n\nআপনার প্রশাসক আপনার ইমেল, অ্যাপ্স ও ওয়েবসাইট সহ কর্মস্থলের নেটওয়ার্ক কার্যকলাপ নিরীক্ষণ করতে পারেন।\n\nআরো তথ্যের জন্য আপনার প্রশাসকের সঙ্গে যোগাযোগ করুন।\n\nএছাড়া আপনি একটি VPN এর সাথেও সংযুক্ত যা আপনার নেটওয়ার্ক কার্যকলাপ নিরীক্ষণ করতে পারে।"</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"আপনি <xliff:g id="APPLICATION">%1$s</xliff:g> -এ সংযুক্ত হয়েছেন, যা ইমেল, অ্যাপ্লিকেশান এবং ওয়েবসাইটগুলি সমেত আপনার নেটওয়ার্ক কার্যকলাপ নিরীক্ষণ করতে পারে৷"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"আপনি <xliff:g id="APPLICATION">%1$s</xliff:g> -এ সংযুক্ত হয়েছেন, যা ইমেল, অ্যাপ্লিকেশান এবং ওয়েবসাইটগুলি সমেত আপনার ব্যক্তিগত নেটওয়ার্ক কার্যকলাপ নিরীক্ষণ করতে পারে৷"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"আপনি <xliff:g id="APPLICATION">%1$s</xliff:g> এর সাথে সংযুক্ত হয়েছেন, যা ইমেল, অ্যাপ এবং ওয়েবসাইটগুলি সহ আপনার ব্যক্তিগত নেটওয়ার্কের কার্যকলাপ নিরীক্ষণ করবে৷"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> আপনার কাজের প্রোফাইল পরিচালনা করে৷ এটি <xliff:g id="APPLICATION">%2$s</xliff:g> -এ সংযুক্ত রয়েছে যা আপনার ইমেল, অ্যাপ্লিকেশান ও ওয়েবসাইটগুলি সহ আপনার কাজের নেটওয়ার্কের কার্যকলাপ নিরীক্ষণ করতে পারে৷\n\nআরো তথ্যের জন্য, আপনার প্রশাসকের সাথে যোগাযোগ করুন৷"</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"আপনার কর্মস্থলের প্রোফাইলটি <xliff:g id="ORGANIZATION">%1$s</xliff:g> দ্বারা পরিচালিত হয়। সেটি <xliff:g id="APPLICATION">%2$s</xliff:g> এর সাথে সংযুক্ত যা আপনার ইমেল, অ্যাপ্স ও ওয়েবসাইট সহ কর্মস্থলের নেটওয়ার্ক কার্যকলাপ নিরীক্ষণ করতে পারে।\n\nআরো তথ্যের জন্য আপনার প্রশাসকের সঙ্গে যোগাযোগ করুন।"</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> আপনার কাজের প্রোফাইল পরিচালনা করে৷ এটি <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> -এ সংযুক্ত রয়েছে যা আপনার ইমেল, অ্যাপ্লিকেশান ও ওয়েবসাইটগুলি সহ আপনার কাজের নেটওয়ার্কের কার্যকলাপ নিরীক্ষণ করতে পারে৷\n\nএছাড়াও আপনি <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> এর সাথে সংযুক্ত রয়েছেন যা আপনার ব্যক্তিগত নেটওয়ার্কের কার্যকলাপ নিরীক্ষণ করতে পারে৷"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"আপনি নিজে আনলক না করা পর্যন্ত ডিভাইসটি লক হয়ে থাকবে"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"বিজ্ঞপ্তিগুলি আরো দ্রুত পান"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 48fb824..b694b4b 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozorenje <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Poslovni režim"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Noćno svjetlo"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC je onemogućen"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC je omogućen"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nema nedavnih stavki"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Sve ste obrisali"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacije o aplikaciji"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Podjela po horizontali"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Podjela po vertikali"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Prilagođena podjela"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Odbaci"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Otvori"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Podijeli ekran nagore"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Podijeli ekran nalijevo"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Podijeli ekran nadesno"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Napunjeno"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Prekini VPN vezu"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Vašim uređajem upravlja aplikacija <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> koristi aplikaciju <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> za upravljanje vašim uređajem."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Vaš administrator može pratiti postavke, korporativni pristup, aplikacije, podatke povezane s vašim uređajem i informacije o lokaciji vašeg uređaja."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Vaš administrator može pratiti postavke, korporativni pristup, aplikacije, podatke povezane s vašim uređajem i informacije o lokaciji vašeg uređaja."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Saznajte više"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Povezani ste s aplikacijom <xliff:g id="VPN_APP">%1$s</xliff:g>, koja može pratiti vašu aktivnost na mreži, uključujući e-poruke i web lokacije."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Postavke otvorene VPN mreže"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Vaš administrator je uključio zapisivanje na mreži, čime se prati saobraćaj na vašem uređaju.\n\nZa više informacija obratite se administratoru."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Vaš administrator je uključio zapisivanje na mreži, čime se prati saobraćaj na vašem uređaju.\n\nZa više informacija, obratite se administratoru."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Jednoj aplikaciji ste dali odobrenje da uspostavi VPN vezu.\n\nTa aplikacija može pratiti vašu aktivnost na uređaju i mreži, uključujući e-poštu, aplikacije i web-lokacije."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Vašim profilom za posao upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nVaš administrator može pratiti vašu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije.\n\nZa više informacija kontaktirajte svog administratora.\n\nPovezani ste i na VPN, koji može pratiti vašu aktivnost na mreži."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Vašim radnim profilom upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nVaš administrator može pratiti vašu aktivnost na radnoj mreži, uključujući e-poruke, aplikacije i web lokacije.\n\nZa više informacija, obratite se administratoru.\n\nPovezani ste i na VPN, koji može pratiti vašu aktivnost na mreži."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Povezani ste sa aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g>, koja može pratiti vašu aktivnost na mreži, uključujući e-mailove, aplikacije i web-lokacije."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Povezani ste sa aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g>, koja može pratiti vašu aktivnost na privatnoj mreži, uključujući e-mailove, aplikacije i web-lokacije."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Povezani ste na aplikaciju <xliff:g id="APPLICATION">%1$s</xliff:g>, koja može pratiti vaše privatne aktivnosti na mreži, uključujući e-poštu, aplikacije i web stranice."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Profilom za posao upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je sa aplikacijom <xliff:g id="APPLICATION">%2$s</xliff:g>, koja može pratiti vašu aktivnost na radnoj mreži, uključujući e-poštu, aplikacije i web-lokacije.\n\nZa više informacija kontaktirajte svog administratora."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Vašim radnim profilom upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je s aplikacijom <xliff:g id="APPLICATION">%2$s</xliff:g>, koja može pratiti vašu aktivnost na radnoj mreži, uključujući e-poruke, aplikacije i web lokacije.\n\nZa više informacija, obratite se svom administratoru."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Profilom za posao upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je sa aplikacijom <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, koja može pratiti vašu aktivnost na radnoj mreži, uključujući e-poštu, aplikacije i web-lokacije.\n\nPovezani ste i sa aplikacijom <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, koja može pratiti vašu aktivnost na privatnoj mreži."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Uređaj će ostati zaključan dok ga ručno ne otključate"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Brže primaj obavještenja"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 691bc6b..fc61ab0 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advertiment: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mode de feina"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Llum nocturna"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"L\'NFC està desactivada"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"L\'NFC està activada"</string>
<string name="recents_empty_message" msgid="808480104164008572">"No hi ha cap element recent"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Ho has esborrat tot"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informació de l\'aplicació"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisió horitzontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisió vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisió personalitzada"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Ignora"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Obre"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Divideix la pantalla cap amunt"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Divideix la pantalla cap a l\'esquerra"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Divideix la pantalla cap a la dreta"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Desconnecta la VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> gestiona el teu dispositiu."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> utilitza <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> per gestionar el teu dispositiu."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"L\'administrador pot supervisar i gestionar la configuració, l\'accés corporatiu, les aplicacions, la informació d\'ubicació del dispositiu i les dades associades."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"L\'administrador pot supervisar i gestionar la configuració, l\'accés corporatiu, les aplicacions, la ubicació i les dades del dispositiu."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Més informació"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Estàs connectat a <xliff:g id="VPN_APP">%1$s</xliff:g>, que pot supervisar la teva activitat a la xarxa, com els correus electrònics, les aplicacions i els llocs web."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Obre la configuració de la VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"L\'administrador ha activat el registre de xarxa, que supervisa el trànsit del teu dispositiu.\n\nPer obtenir més informació, contacta amb l\'administrador."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"L\'administrador ha activat el registre de xarxa, que supervisa el trànsit del teu dispositiu.\n\nPer obtenir més informació, contacta amb l\'administrador."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Has donat permís a una aplicació per configurar una connexió VPN.\n\nAquesta aplicació pot supervisar el dispositiu i l\'activitat a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> gestiona el perfil de Work.\n\nL\'administrador pot supervisar l\'activitat a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web.\n\nPer obtenir més informació, contacta amb l\'administrador.\n\nA més, estàs connectat a una VPN, que pot supervisar l\'activitat a la xarxa."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> gestiona el teu perfil professional.\n\nL\'administrador pot supervisar la teva activitat a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web.\n\nPer obtenir més informació, contacta amb l\'administrador.\n\nA més, estàs connectat a una VPN, que també pot supervisar la teva activitat a la xarxa."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Estàs connectat a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pot supervisar la teva activitat a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Estàs connectat a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pot supervisar la teva activitat personal a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Estàs connectat a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pot supervisar la teva activitat personal a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> gestiona el teu perfil professional. Aquest perfil està connectat a <xliff:g id="APPLICATION">%2$s</xliff:g>, que pot supervisar la teva activitat professional a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web.\n\nPer obtenir més informació, contacta amb l\'administrador."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> gestiona el teu perfil professional. Aquest perfil està connectat a <xliff:g id="APPLICATION">%2$s</xliff:g>, que pot supervisar la teva activitat professional a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web.\n\nPer obtenir més informació, contacta amb l\'administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> gestiona el teu perfil professional. Aquest perfil està connectat a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pot supervisar la teva activitat professional a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web.\n\nA més, estàs connectat a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que també pot supervisar la teva activitat personal a la xarxa."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"El dispositiu continuarà bloquejat fins que no el desbloquegis manualment."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Obtén notificacions més ràpidament"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 2810d77..9cddbc3 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -327,6 +327,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozornění při <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Pracovní režim"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Noční režim"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Žádné nedávné položky"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vše je vymazáno"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informace o aplikaci"</string>
@@ -340,6 +346,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Vodorovné rozdělení"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikální rozdělení"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Vlastní rozdělení"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Zrušit"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Otevřít"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Rozdělit obrazovku nahoru"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Rozdělit obrazovku vlevo"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Rozdělit obrazovku vpravo"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Nabito"</string>
@@ -420,20 +431,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Odpojit VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Vaše zařízení je spravováno aplikací <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"Organizace <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> používá ke správě tohoto zařízení aplikaci <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administrátor může sledovat a spravovat nastavení, firemní přístup, aplikace, data přidružená k tomuto zařízení a jeho polohu."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Další informace"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Jste připojeni k aplikaci <xliff:g id="VPN_APP">%1$s</xliff:g>, která může sledovat vaši aktivitu v síti, včetně e-mailů, aplikací a webů."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Otevřít nastavení VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administrátor zapnul protokolování sítě, které monitoruje síťový provoz v zařízení.\n\nDalší informace vám poskytne administrátor."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Udělili jste aplikaci oprávnění k nastavení připojení VPN.\n\nTato aplikace může sledovat vaši aktivitu v zařízení a v síti, včetně e-mailů, aplikací a webů."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Váš pracovní profil spravuje organizace <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministrátor může monitorovat vaši síťovou aktivitu, včetně e-mailů, aplikací a webů.\n\nO další informace požádejte svého administrátora.\n\nJste také připojeni k síti VPN, která může sledovat vaši aktivitu v síti."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Jste připojeni k aplikaci <xliff:g id="APPLICATION">%1$s</xliff:g>, která může sledovat vaši aktivitu v síti, včetně e-mailů, aplikací a webů."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Jste připojeni k aplikaci <xliff:g id="APPLICATION">%1$s</xliff:g>, která může sledovat vaši osobní aktivitu v síti, včetně e-mailů, aplikací a webů."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Jste připojeni k aplikaci <xliff:g id="APPLICATION">%1$s</xliff:g>, která může sledovat vaši osobní aktivitu v síti, včetně e-mailů, aplikací a webů."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Váš pracovní profil spravuje organizace <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Je připojen k aplikaci <xliff:g id="APPLICATION">%2$s</xliff:g>, která může sledovat vaši aktivitu v síti, včetně e-mailů, aplikací a webů.\n\nO další informace požádejte svého administrátora."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Váš pracovní profil spravuje organizace <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Je připojen k aplikaci <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, která může sledovat vaši aktivitu v síti, včetně e-mailů, aplikací a webů.\n\nTaké jste připojeni k aplikaci <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, která může sledovat vaši osobní aktivitu v síti."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Zařízení zůstane uzamčeno, dokud je ručně neodemknete"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Čtěte si oznámení rychleji"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 675029d..3b8783a 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advarsel ved <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Arbejdstilstand"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nattelys"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Ingen nye elementer"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Du har ryddet alt"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Oplysninger om applikationen"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Opdel vandret"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Opdel lodret"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Opdel brugerdefineret"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Afvis"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Åbn"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Del skærm øverst"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Del skærm til venstre"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Del skærm til højre"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Opladet"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Afbryd VPN-forbindelse"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Din enhed administreres af <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> bruger <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> til at administrere din enhed."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Din administrator kan overvåge og administrere indstillinger, virksomhedsadgang, apps og data, der er knyttet til denne enhed, samt enhedens placeringsoplysninger."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Få flere oplysninger"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Du har forbindelse til <xliff:g id="VPN_APP">%1$s</xliff:g>, som kan overvåge din netværksaktivitet, bl.a. e-mails, apps og websites."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Åbn VPN-indstillinger"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Din administrator har aktiveret netværksregistrering, som overvåger trafik på din enhed.\n\nKontakt din administrator for at få flere oplysninger."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Du gav en app tilladelse til at konfigurere en VPN-forbindelse.\n\nDenne app kan overvåge din enhed og netværksaktivitet, bl.a. e-mails, apps og websites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Din arbejdsprofil administreres af <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nDin administrator kan overvåge din netværksaktivitet, bl.a. e-mails, apps og websites.\n\nKontakt din administrator for at få flere oplysninger.\n\nDu er også forbundet til en VPN-forbindelse, som kan overvåge din netværksaktivitet."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Du har forbindelse til <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan overvåge din netværksaktivitet, bl.a. e-mails, apps og websites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Du har forbindelse til <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan overvåge din private netværksaktivitet, bl.a. e-mails, apps og websites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Du har forbindelse til <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan overvåge din private netværksaktivitet, bl.a. e-mails, apps og websites."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Din arbejdsprofil administreres af <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Den er forbundet til <xliff:g id="APPLICATION">%2$s</xliff:g>, som kan overvåge din arbejdsrelaterede netværksaktivitet, bl.a. e-mails, apps og websites.\n\nKontakt din administrator for at få flere oplysninger."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Din arbejdsprofil administreres af <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Den er forbundet til <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, som kan overvåge din arbejdsrelaterede netværksaktivitet, bl.a. e-mails, apps og websites.\n\nDu er også forbundet til <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, som kan overvåge din private netværksaktivitet."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Enheden vil forblive låst, indtil du manuelt låser den op"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Modtag underretninger hurtigere"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index fb580cd..b2ad211 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Warnung für <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Arbeitsmodus"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nachtlicht"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC ist deaktiviert"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC ist aktiviert"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Keine kürzlich verwendeten Elemente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Du hast alles gelöscht"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"App-Info"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Geteilte Schaltfläche – horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Geteilte Schaltfläche – vertikal"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Geteilte Schaltfläche – benutzerdefiniert"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Schließen"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Öffnen"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Geteilten Bildschirm oben anzeigen"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Geteilten Bildschirm auf linker Seite anzeigen"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Geteilten Bildschirm auf der rechten Seite anzeigen"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Aufgeladen"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN-Verbindung trennen"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Dein Gerät wird von <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> verwaltet."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> verwaltet dein Gerät mit <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Dein Administrator kann Einstellungen, Zugriffsrechte, Apps und Daten deines Geräts und dessen Standortinformationen überwachen."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Dein Administrator kann Einstellungen, Zugriffsrechte, Apps und Daten deines Geräts und dessen Standortinformationen überwachen und verwalten."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Weitere Informationen"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Du bist mit <xliff:g id="VPN_APP">%1$s</xliff:g> verbunden. Die VPN-App kann deine Netzwerkaktivitäten (E-Mails, Apps und Websites) erfassen."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN-Einstellungen öffnen"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Dein Administrator hat die Netzwerkprotokollierung aktiviert. Damit wird der Verkehr auf deinem Gerät erfasst.\n\nWeitere Informationen erhältst du von deinem Administrator."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Dein Administrator hat die Netzwerkprotokollierung aktiviert. Damit wird der Verkehr auf deinem Gerät erfasst.\n\nWeitere Informationen erhältst du von deinem Administrator."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Du hast einer App gestattet, eine VPN-Verbindung einzurichten.\n\nDiese App kann dein Gerät und deine Netzwerkaktivitäten überwachen, einschließlich E-Mails, Apps und Websites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Dein Arbeitsprofil wird von <xliff:g id="ORGANIZATION">%1$s</xliff:g> verwaltet.\n\nDein Administrator kann deine Netzwerkaktivität überwachen, einschließlich E-Mails, Apps und Websites.\n\nWeitere Informationen erhältst du bei deinem Administrator.\n\nDu bist außerdem mit einem VPN verbunden, das deine persönliche Netzwerkaktivität überwachen kann."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Dein Arbeitsprofil wird von <xliff:g id="ORGANIZATION">%1$s</xliff:g> verwaltet.\n\nDein Administrator kann deine Netzwerkaktivitäten einschließlich E-Mails, Apps und Websites überwachen.\n\nWeitere Informationen erhältst du von deinem Administrator.\n\nAußerdem bist du mit einem VPN verbunden, das deine Netzwerkaktivitäten erfassen kann."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Du bist mit der App <xliff:g id="APPLICATION">%1$s</xliff:g> verbunden, die deine Netzwerkaktivität überwachen kann, einschließlich E-Mails, Apps und Websites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Du bist mit der App <xliff:g id="APPLICATION">%1$s</xliff:g> verbunden, die deine persönliche Netzwerkaktivität überwachen kann, einschließlich E-Mails, Apps und Websites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Du bist mit der App \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" verbunden. Diese kann deine persönlichen Netzwerkaktivitäten erfassen, einschließlich E-Mails, Apps und Websites."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Dein Arbeitsprofil wird von <xliff:g id="ORGANIZATION">%1$s</xliff:g> verwaltet. Das Profil ist mit der App <xliff:g id="APPLICATION">%2$s</xliff:g> verbunden, die deine geschäftlichen Netzwerkaktivitäten überwachen kann, einschließlich E-Mails, Apps und Websites.\n\nWeitere Informationen erhältst du von deinem Administrator."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Dein Arbeitsprofil wird von <xliff:g id="ORGANIZATION">%1$s</xliff:g> verwaltet. Es ist mit der App <xliff:g id="APPLICATION">%2$s</xliff:g> verbunden, die deine berufliche Netzwerkaktivitäten einschließlich E-Mails, Apps und Websites erfassen kann.\n\nWeitere Informationen erhältst du von deinem Administrator."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Dein Arbeitsprofil wird von <xliff:g id="ORGANIZATION">%1$s</xliff:g> verwaltet. Das Profil ist mit der App <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> verbunden, die deine geschäftliche Netzwerkaktivität überwachen kann, einschließlich E-Mails, Apps und Websites.\n\nDu bist außerdem mit der App <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> verbunden, die deine persönliche Netzwerkaktivität überwachen kann."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Das Gerät bleibt gesperrt, bis du es manuell entsperrst."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Benachrichtigungen schneller erhalten"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 6190f07..94965f1 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Προειδοποίηση για <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Λειτουργία εργασίας"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Νυχτερινός φωτισμός"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"Το NFC είναι απενεργοποιημένο"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"Το NFC είναι ενεργοποιημένο"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Δεν υπάρχουν πρόσφατα στοιχεία"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Έχει γίνει διαγραφή όλων των στοιχείων"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Πληροφορίες εφαρμογής"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Οριζόντιος διαχωρισμός"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Κάθετος διαχωρισμός"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Προσαρμοσμένος διαχωρισμός"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Παράβλεψη"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Άνοιγμα"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Διαχωρισμός οθόνης στην κορυφή"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Διαχωρισμός οθόνης στα αριστερά"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Διαχωρισμός οθόνης στα δεξιά"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Φορτίστηκε"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Αποσύνδεση VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Η διαχείριση της συσκευής σας γίνεται από <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> χρησιμοποιεί <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> για τη διαχείριση της συσκευής σας."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Ο διαχειριστής σας μπορεί να παρακολουθεί και να διαχειρίζεται τις ρυθμίσεις, την εταιρική πρόσβαση, τις εφαρμογές και τα δεδομένα που σχετίζονται με τη συσκευή σας, καθώς και τις πληροφορίες τοποθεσίας της συσκευής σας."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Ο διαχειριστής μπορεί να παρακολουθεί και να διαχειρίζεται ρυθμίσεις, εταιρική πρόσβαση, εφαρμογές και δεδομένα που σχετίζονται με τη συσκευή, καθώς και τις πληροφορίες τοποθεσίας."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Μάθετε περισσότερα"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Έχετε συνδεθεί στην εφαρμογή <xliff:g id="VPN_APP">%1$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα δικτύου σας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστοτόπων."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Άνοιγμα Ρυθμίσεων VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Ο διαχειριστής σας έχει ενεργοποιήσει την καταγραφή δικτύου, η οποία παρακολουθεί την επισκεψιμότητα στη συσκευή σας.\n\nΓια περισσότερες πληροφορίες, επικοινωνήστε με τον διαχειριστή σας."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Ο διαχειριστής σας έχει ενεργοποιήσει την καταγραφή δικτύου, η οποία παρακολουθεί την επισκεψιμότητα στη συσκευή σας.\n\nΓια περισσότερες πληροφορίες, επικοινωνήστε με τον διαχειριστή σας."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Παραχωρήσατε σε μια εφαρμογή άδεια για τη ρύθμιση σύνδεσης VPN.\n\nΑυτή η εφαρμογή μπορεί να παρακολουθεί τη δραστηριότητα της συσκευής και του δικτύου σας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστότοπων."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Η διαχείριση του προφίλ εργασίας γίνεται από τον οργανισμό <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nΟ διαχειριστής έχει τη δυνατότητα παρακολούθησης της δραστηριότητας του δικτύου σας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστότοπων.\n\nΓια περισσότερες πληροφορίες, επικοινωνήστε με το διαχειριστή.\n\nΕπίσης, είστε συνδεδεμένοι σε VPN, το οποίο μπορεί να παρακολουθεί τη δραστηριότητα του δικτύου σας."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Η διαχείριση του προφίλ εργασίας γίνεται από τον οργανισμό <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nΟ διαχειριστής έχει τη δυνατότητα παρακολούθησης της δραστηριότητας του δικτύου σας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστότοπων.\n\nΓια περισσότερες πληροφορίες, επικοινωνήστε με τον διαχειριστή.\n\nΕπίσης, είστε συνδεδεμένοι σε VPN, το οποίο μπορεί να παρακολουθεί τη δραστηριότητα του δικτύου σας."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Έχετε συνδεθεί στην εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα του δικτύου σας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστότοπων."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Έχετε συνδεθεί στην εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα του προσωπικού σας δικτύου, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστότοπων."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Έχετε συνδεθεί στην εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα του προσωπικού σας δικτύου, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστοτόπων."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Η διαχείριση του προφίλ εργασίας γίνεται από τον οργανισμό <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Είναι συνδεδεμένο στην εφαρμογή <xliff:g id="APPLICATION">%2$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα του δικτύου εργασίας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστότοπων.\n\nΓια περισσότερες πληροφορίες, επικοινωνήστε με το διαχειριστή."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Η διαχείριση του προφίλ εργασίας γίνεται από τον οργανισμό <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Είναι συνδεδεμένο στην εφαρμογή <xliff:g id="APPLICATION">%2$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα του δικτύου εργασίας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστότοπων.\n\nΓια περισσότερες πληροφορίες, επικοινωνήστε με το διαχειριστή."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Η διαχείριση του προφίλ εργασίας γίνεται από τον οργανισμό <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Είναι συνδεδεμένο στην εφαρμογή <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα του δικτύου εργασίας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστότοπων.\n\nΕπίσης, είστε συνδεδεμένοι στην εφαρμογή <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα του προσωπικού σας δικτύου."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Η συσκευή θα παραμείνει κλειδωμένη έως ότου την ξεκλειδώσετε μη αυτόματα"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Λάβετε ειδοποιήσεις γρηγορότερα"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 9d74a2f..5f2beba 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work mode"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Night Light"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"No recent items"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"You\'ve cleared everything"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Application Info"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Customised"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Dismiss"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Open"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Split screen to the top"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Split screen to the left"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Split screen to the right"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Charged"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Disconnect VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Your device is managed by <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> uses <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> to manage your device."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Your administrator can monitor and manage settings, corporate access, apps, data associated with your device and your device\'s location information."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Find out more"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"You\'re connected to <xliff:g id="VPN_APP">%1$s</xliff:g>, which can monitor your network activity, including emails, apps and websites."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Open VPN Settings"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information contact your admin."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps and websites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nYour administrator is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your administrator.\n\nYou\'re also connected to a VPN, which can monitor your network activity."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your network activity including emails, apps and websites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. It is connected to <xliff:g id="APPLICATION">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nFor more information, contact your administrator."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. It is connected to <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nYou\'re also connected to <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, which can monitor your personal network activity."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Get notifications faster"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 9d74a2f..5f2beba 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work mode"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Night Light"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"No recent items"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"You\'ve cleared everything"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Application Info"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Customised"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Dismiss"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Open"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Split screen to the top"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Split screen to the left"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Split screen to the right"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Charged"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Disconnect VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Your device is managed by <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> uses <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> to manage your device."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Your administrator can monitor and manage settings, corporate access, apps, data associated with your device and your device\'s location information."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Find out more"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"You\'re connected to <xliff:g id="VPN_APP">%1$s</xliff:g>, which can monitor your network activity, including emails, apps and websites."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Open VPN Settings"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information contact your admin."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps and websites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nYour administrator is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your administrator.\n\nYou\'re also connected to a VPN, which can monitor your network activity."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your network activity including emails, apps and websites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. It is connected to <xliff:g id="APPLICATION">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nFor more information, contact your administrator."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. It is connected to <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nYou\'re also connected to <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, which can monitor your personal network activity."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Get notifications faster"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 9d74a2f..5f2beba 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work mode"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Night Light"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"No recent items"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"You\'ve cleared everything"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Application Info"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Customised"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Dismiss"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Open"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Split screen to the top"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Split screen to the left"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Split screen to the right"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Charged"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Disconnect VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Your device is managed by <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> uses <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> to manage your device."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Your administrator can monitor and manage settings, corporate access, apps, data associated with your device and your device\'s location information."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Find out more"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"You\'re connected to <xliff:g id="VPN_APP">%1$s</xliff:g>, which can monitor your network activity, including emails, apps and websites."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Open VPN Settings"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information contact your admin."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps and websites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nYour administrator is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your administrator.\n\nYou\'re also connected to a VPN, which can monitor your network activity."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your network activity including emails, apps and websites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. It is connected to <xliff:g id="APPLICATION">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nFor more information, contact your administrator."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. It is connected to <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nYou\'re also connected to <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, which can monitor your personal network activity."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Get notifications faster"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 24a82ab..beeb773 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advertencia de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabajo"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luz nocturna"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"La tecnología NFC está inhabilitada"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"La tecnología NFC está habilitada"</string>
<string name="recents_empty_message" msgid="808480104164008572">"No hay elementos recientes"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Todo borrado"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Información de la aplicación"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"División horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"División vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"División personalizada"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Descartar"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Abrir"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir pantalla en la parte superior"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir pantalla a la izquierda"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir pantalla a la derecha"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Cargada"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Desconectar VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> administra tu dispositivo."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> usa <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> para administrar tu dispositivo."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Tu administrador puede controlar la configuración, el acceso corporativo, las apps, los datos asociados y la información de ubicación de tu dispositivo."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Tu administrador controla la configuración, el acceso corporativo, las apps, los datos asociados a tu dispositivo y la información de ubicación."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Más información"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Estás conectado a <xliff:g id="VPN_APP">%1$s</xliff:g>, que puede controlar la actividad de tu red, incluidos los correos electrónicos, las apps y los sitios web."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Abrir configuración de VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Tu administrador activó el registro de red, que controla el tráfico en tu dispositivo.\n\nComunícate con él para obtener más información."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Tu administrador activó el registro de red, que controla el tráfico en tu dispositivo.\n\nComunícate con él para obtener más información."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Permitiste que una aplicación configurara una conexión VPN.\n\nEsta aplicación puede supervisar la actividad de la red y del dispositivo, incluidos los correos electrónicos, las aplicaciones y los sitios web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> administra tu perfil de trabajo.\n\nEl administrador puede supervisar la actividad de la red, incluidos los correos electrónicos, las aplicaciones y los sitios web.\n\nPara obtener más información, comunícate con el administrador.\n\nTambién tienes conexión a una VPN, que puede supervisar la actividad de tu red."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> administra tu perfil de trabajo.\n\nTu administrador puede controlar tu actividad en la red, como los correos electrónicos, las apps y los sitios web.\n\nComunícate con él para obtener más información.\n\nTambién estás conectado a una VPN, que puede controlar tu actividad en la red."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Tienes conexión a la aplicación <xliff:g id="APPLICATION">%1$s</xliff:g>, que puede supervisar la actividad de la red, incluidos los correos electrónicos, las aplicaciones y los sitios web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Tienes conexión a la aplicación <xliff:g id="APPLICATION">%1$s</xliff:g>, que puede supervisar la actividad de la red personal, incluidos los correos electrónicos, las aplicaciones y los sitios web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Te conectaste a <xliff:g id="APPLICATION">%1$s</xliff:g>, que puede supervisar la actividad de tu red personal, incluidos los correos electrónicos, las apps y los sitios web."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> administra tu perfil de trabajo. Tiene conexión a <xliff:g id="APPLICATION">%2$s</xliff:g>, que puede supervisar la actividad de tu red de trabajo, incluidos los correos electrónicos, las aplicaciones y los sitios web.\n\nPara obtener más información, comunícate con el administrador."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> administra tu perfil de trabajo. Está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que puede controlar la actividad de tu red laboral, como los correos electrónicos, las apps y los sitios web.\n\nPara obtener más información, comunícate con tu administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> administra tu perfil de trabajo. Tiene conexión a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que puede supervisar la actividad de tu red de trabajo, incluidos los correos electrónicos, las aplicaciones y los sitios web.\n\nTambién tienes conexión a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que puede supervisar la actividad de la red personal."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"El dispositivo permanecerá bloqueado hasta que lo desbloquees manualmente."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Recibe notificaciones más rápido"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 76ea158..ea2d0b6 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -212,10 +212,10 @@
<string name="accessibility_quick_settings_bluetooth_connected" msgid="4306637793614573659">"Bluetooth conectado."</string>
<string name="accessibility_quick_settings_bluetooth_changed_off" msgid="2730003763480934529">"Bluetooth desactivado."</string>
<string name="accessibility_quick_settings_bluetooth_changed_on" msgid="8722351798763206577">"Bluetooth activado."</string>
- <string name="accessibility_quick_settings_location_off" msgid="5119080556976115520">"Informes de Ubicación desactivados."</string>
- <string name="accessibility_quick_settings_location_on" msgid="5809937096590102036">"Informes de Ubicación activados."</string>
- <string name="accessibility_quick_settings_location_changed_off" msgid="8526845571503387376">"Informes de Ubicación desactivados."</string>
- <string name="accessibility_quick_settings_location_changed_on" msgid="339403053079338468">"Informes de Ubicación activados."</string>
+ <string name="accessibility_quick_settings_location_off" msgid="5119080556976115520">"Informes de ubicación desactivados."</string>
+ <string name="accessibility_quick_settings_location_on" msgid="5809937096590102036">"Informes de ubicación activados."</string>
+ <string name="accessibility_quick_settings_location_changed_off" msgid="8526845571503387376">"Informes de ubicación desactivados."</string>
+ <string name="accessibility_quick_settings_location_changed_on" msgid="339403053079338468">"Informes de ubicación activados."</string>
<string name="accessibility_quick_settings_alarm" msgid="3959908972897295660">"La alarma sonará a la(s) <xliff:g id="TIME">%s</xliff:g>."</string>
<string name="accessibility_quick_settings_close" msgid="3115847794692516306">"Cerrar panel."</string>
<string name="accessibility_quick_settings_more_time" msgid="3659274935356197708">"Más tiempo."</string>
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advertencia de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabajo"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luz nocturna"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"La conexión NFC está inhabilitada"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"La conexión NFC está habilitada"</string>
<string name="recents_empty_message" msgid="808480104164008572">"No hay elementos recientes"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Has rechazado todo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Información de la aplicación"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"División horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"División vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"División personalizada"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Descartar"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Abrir"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir la pantalla en la parte superior"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir la pantalla a la izquierda"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir la pantalla a la derecha"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Cargada"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Desconectar VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Tu dispositivo está administrado por <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> utiliza <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> para administrar tu dispositivo."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Tu admin. puede controlar y gestionar ajustes, acceso corporativo, apps, datos asociados al dispositivo y sus datos de ubicación."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Tu administrador puede controlar y gestionar su configuración, acceso corporativo y aplicaciones, así como los datos y la ubicación del dispositivo."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Más información"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Te has conectado a <xliff:g id="VPN_APP">%1$s</xliff:g>, que puede controlar tu actividad de red, como los correos electrónicos, las aplicaciones y los sitios web."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Abrir Ajustes de red VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Tu administrador ha activado el registro de la red para controlar el tráfico en tu dispositivo.\n\nPonte en contacto con él para obtener más información."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Tu administrador ha activado el registro de la red para supervisar el tráfico en tu dispositivo.\n\nPonte en contacto con él para obtener más información."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Has concedido permiso a una aplicación para configurar una conexión VPN.\n\nEsta aplicación puede controlar tu dispositivo y tu actividad de red, como correos electrónicos, aplicaciones y sitios web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"El administrador de tu perfil de trabajo es <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nTu administrador puede supervisar tu actividad de red, como correos electrónicos, aplicaciones y sitios web.\n\nPara obtener más información, ponte en contacto con tu administrador.\n\nTambién estás conectado a una red VPN que puede supervisar tu actividad de red."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"El administrador de tu perfil de trabajo es <xliff:g id="ORGANIZATION">%1$s</xliff:g>,\n\n que puede supervisar tu actividad de red, como correos electrónicos, aplicaciones y sitios web.\n\nPara obtener más información, ponte en contacto con tu administrador.\n\nTambién estás conectado a una red VPN, que puede supervisar tu actividad de red."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Estás conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que puede controlar tu actividad de red, como correos electrónicos, aplicaciones y sitios web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Estas conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que puede controlar tu actividad de red personal, como correos electrónicos, aplicaciones y sitios web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Estas conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que puede controlar tu actividad de red personal, como correos electrónicos, aplicaciones y sitios web."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"El administrador de tu perfil de trabajo es <xliff:g id="ORGANIZATION">%1$s</xliff:g> y está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que puede controlar tu actividad de red del trabajo, como correos electrónicos, aplicaciones y sitios web.\n\nPara obtener más información, ponte en contacto con el administrador."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"El administrador de tu perfil de trabajo es <xliff:g id="ORGANIZATION">%1$s</xliff:g> y está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que puede supervisar tu actividad de red en el trabajo, como correos electrónicos, aplicaciones y sitios web.\n\nPara obtener más información, ponte en contacto con tu administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"El administrador de tu perfil de trabajo es <xliff:g id="ORGANIZATION">%1$s</xliff:g> y está conectado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que puede controlar tu actividad de red, como correos electrónicos, aplicaciones y sitios web.\n\nTú también estás conectado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que puede controlar tu actividad de red personal."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"El dispositivo permanecerá bloqueado hasta que se desbloquee manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Recibe notificaciones más rápido"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 696585b..01a3f7c 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> hoiatus"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Töörežiim"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Öövalgus"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC on keelatud"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC on lubatud"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Hiljutisi üksusi pole"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Olete kõik ära kustutanud"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Rakenduste teave"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horisontaalne poolitamine"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikaalne poolitamine"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Kohandatud poolitamine"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Loobu"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Ava"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Poolita ekraan üles"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Poolita ekraan vasakule"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Poolita ekraan paremale"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Laetud"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Katkesta VPN-i ühendus"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Teie seadet haldab rakendus <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"Organisatsioon <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> kasutab teie seadme haldamiseks rakendust <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administraator saab jälgida ja hallata teie seadmega seotud seadeid, ettevõtte juurdepääsu, rakendusi ning andmeid ja teie seadme asukohateavet."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Administraator saab jälgida ja hallata teie seadmega seotud seadeid, ettevõtte juurdepääsu, rakendusi ja andmeid ning seadme asukohateavet."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Lisateave"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Olete ühendatud rakendusega <xliff:g id="VPN_APP">%1$s</xliff:g>, mis saab jälgida teie võrgutegevusi, sh meile, rakendusi ja veebisaite."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Ava VPN-i seaded"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Teie administraator on sisse lülitanud võrgu logimise funktsiooni, mis jälgib liiklust teie seadmes.\n\nLisateabe saamiseks pöörduge administraatori poole."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Teie administraator on sisse lülitanud võrgu logimise funktsiooni, mis jälgib teie seadmes liiklust.\n\nLisateabe saamiseks võtke ühendust administraatoriga."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Andsite rakendusele loa VPN-i ühenduse seadistamiseks.\n\nSee rakendus võib jälgida teie seadet ja võrgutegevusi, sh meile, rakendusi ja veebisaite."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Teie tööprofiili haldab organisatsioon <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministraator saab jälgida teie võrgutegevusi, sh meile, rakendusi ja veebisaite.\n\nLisateabe saamiseks võtke ühendust administraatoriga.\n\nTeil on ühendus ka VPN-iga, mis saab jälgida teie võrgutegevusi."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Teie tööprofiili haldab <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministraator saab jälgida teie töökoha võrgutegevusi, sh meile, rakendusi ja veebisaite.\n\nLisateabe saamiseks võtke ühendust administraatoriga.\n\nTeil on ühendus ka VPN-iga, mis saab teie võrgutegevusi jälgida."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Teie seade on ühendatud rakendusega <xliff:g id="APPLICATION">%1$s</xliff:g>, mis võib jälgida teie võrgutegevusi, sh meile, rakendusi ja veebisaite."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Teie seade on ühendatud rakendusega <xliff:g id="APPLICATION">%1$s</xliff:g>, mis võib jälgida teie isiklikke võrgutegevusi, sh meile, rakendusi ja veebisaite."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Olete ühendatud rakendusega <xliff:g id="APPLICATION">%1$s</xliff:g>, mis võib jälgida teie isiklikke võrgutegevusi, sh meile, rakendusi ja veebisaite."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Teie tööprofiili haldab organisatsioon <xliff:g id="ORGANIZATION">%1$s</xliff:g>. See on ühendatud rakendusega <xliff:g id="APPLICATION">%2$s</xliff:g>, mis võib jälgida teie töökoha võrgutegevusi, sh meile, rakendusi ja veebisaite.\n\nLisateabe saamiseks võtke ühendust administraatoriga."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Teie tööprofiili haldab <xliff:g id="ORGANIZATION">%1$s</xliff:g>. See on ühendatud rakendusega <xliff:g id="APPLICATION">%2$s</xliff:g>, mis võib jälgida teie töökoha võrgutegevusi, sh meile, rakendusi ja veebisaite.\n\nLisateabe saamiseks võtke ühendust administraatoriga."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Teie tööprofiili haldab organisatsioon <xliff:g id="ORGANIZATION">%1$s</xliff:g>. See on ühendatud rakendusega <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, mis võib jälgida teie töökoha võrgutegevusi, sh meile, rakendusi ja veebisaite.\n\nTeie seade on ühendatud ka rakendusega <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, mis võib jälgida teie isiklikke võrgutegevusi."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Seade jääb lukku, kuni selle käsitsi avate"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Saate märguandeid kiiremini"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 72cd63d..9d48236 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Abisua: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Lan modua"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Gaueko argia"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"Desgaituta dago NFC"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"Gaituta dago NFC"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Ez dago azkenaldi honetako ezer"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Dena garbitu duzu"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Aplikazioaren informazioa"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Zatitze horizontala"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Zatitze bertikala"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Zatitze pertsonalizatua"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Baztertu"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Ireki"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Banandu pantaila eta ezarri goian"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Banandu pantaila eta ezarri ezkerrean"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Banandu pantaila eta ezarri eskuinean"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Kargatuta"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Deskonektatu VPN sarea"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> aplikazioak kudeatzen du gailu hau."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> erakundeak <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> erabiltzen du gailua kudeatzeko."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Gailuko ezarpenak, enpresa-sarbidea, aplikazioak eta datuak gainbegira eta kudea ditzake administratzaileak, baita gailuaren kokapen-informazioa ere."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Gailuko ezarpenak, enpresa-sarbidea, aplikazioak eta datuak gainbegira eta kudea ditzake administratzaileak, baita gailuaren kokapen-informazioa ere."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Informazio gehiago"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"<xliff:g id="VPN_APP">%1$s</xliff:g> aplikaziora konektatuta zaude eta hark sareko jarduerak gainbegira ditzake, mezu elektronikoak, aplikazioak eta webguneak barne."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Ireki VPN ezarpenak"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administratzaileak sare-erregistroak aktibatu ditu; horrela, zure gailuko trafikoa gainbegira dezake.\n\nInformazio gehiago lortzeko, jarri administratzailearekin harremanetan."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Administratzaileak sare-erregistroak aktibatu ditu; horrela, zure gailuko trafikoa gainbegira dezake.\n\nInformazio gehiago lortzeko, jarri administratzailearekin harremanetan."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Aplikazio bati VPN konexio bat konfiguratzeko baimena eman diozu.\n\nAplikazio horrek gailuko eta sareko jarduerak kontrola ditzake, mezu elektronikoak, aplikazioak eta webguneak barne."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> da laneko profilaren kudeatzailea.\n\nAdministratzaileak sareko jarduerak (mezu elektronikoak, aplikazioak eta webguneak barne) kontrola ditzake.\n\nInformazio gehiago lortzeko, jarri administratzailearekin harremanetan.\n\nHorrez gain, sareko jarduerak kontrola ditzakeen VPN batera konektatuta zaude."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> erakundeak kudeatzen du zure laneko profila.\n\nAdministratzaileak sareko jarduerak kontrola diezazkizuke, besteak beste, posta elektronikoa, aplikazioak eta webguneak.\n\nInformazio gehiago lortzeko, jarri administratzailearekin harremanetan.\n\nHorrez gain, VPN batera zaude konektatuta, eta hark ere kontrola ditzake zure sareko jarduerak."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN konexioa"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"<xliff:g id="APPLICATION">%1$s</xliff:g> aplikaziora konektatuta zaude. Aplikazio horrek sarean egiten dituzun jarduerak kontrola ditzake, mezu elektronikoak, aplikazioak eta webguneak barne."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g> aplikaziora konektatuta zaude. Aplikazio horrek sarean egiten dituzun jarduera pertsonalak kontrola ditzake, mezu elektronikoak, aplikazioak eta webguneak barne."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> aplikaziora konektatuta zaude. Aplikazio horrek sarean egiten dituzun jarduera pertsonalak kontrola ditzake, mezu elektronikoak, aplikazioak eta webguneak barne."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> da laneko profilaren kudeatzailea, eta profila <xliff:g id="APPLICATION">%2$s</xliff:g> aplikaziora konektatuta dago. Aplikazio horrek sarean egiten dituzun laneko jarduerak kontrola ditzake, mezu elektronikoak, aplikazioak eta webguneak barne.\n\nInformazio gehiago lortzeko, jarri administratzailearekin harremanetan."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> erakundeak kudeatzen du zure laneko profila. Erakundea <xliff:g id="APPLICATION">%2$s</xliff:g> aplikaziora dago konektatuta, bera arduratzen baita laneko sareko jarduerak kontrolatzeaz, besteak beste, posta elektronikoa, aplikazioak eta webguneak.\n\nInformazio gehiago lortzeko, jarri administratzailearekin harremanetan."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> da laneko profilaren kudeatzailea, eta profila <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> aplikaziora konektatuta dago. Aplikazio horrek sarean egiten dituzun laneko jarduerak kontrola ditzake, mezu elektronikoak, aplikazioak eta webguneak barne.\n\nHorrez gain, sarean egiten dituzun jarduera pertsonalak kontrola ditzakeen <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> aplikaziora konektatuta zaude."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Gailua blokeatuta egongo da eskuz desblokeatu arte"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Eskuratu jakinarazpenak azkarrago"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 7c63beb..d65b218 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"هشدار <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"حالت کار"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"نور شب"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"بدون موارد اخیر"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"همهچیز را پاک کردهاید"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"اطلاعات برنامه"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"تقسیم افقی"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"تقسیم عمودی"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"سفارشی کردن تقسیم"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"نپذیرفتن"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"باز کردن"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"تقسیم کردن صفحه به بالا"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"تقسیم کردن صفحه به چپ"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"تقسیم کردن صفحه به راست"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"شارژ کامل شد"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"قطع اتصال VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> دستگاه شما را مدیریت میکند."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> با استفاده از <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> دستگاهتان را مدیریت میکند."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"سرپرست سیستم شما میتواند بر تنظیمات، دسترسی شرکتی، برنامهها، دادههای مرتبط با دستگاه و اطلاعات مکان دستگاهتان نظارت داشته باشد و آنها را مدیریت کند."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"بیشتر بدانید"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"به <xliff:g id="VPN_APP">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شما را (ازجمله رایانامهها، برنامهها و وبسایتها) کنترل کند."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"باز کردن تنظیمات VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"سرپرست شما گزارشگیری شبکه را (که بر ترافیک دستگاهتان نظارت میکند) روشن کرده است.\n\nبرای اطلاعات بیشتر با سرپرست خود تماس بگیرید."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"شما به برنامهای برای تنظیم اتصال VPN اجازه دادید.\n\n این برنامه میتواند دستگاه و فعالیت شبکهتان را کنترل کند، از جمله رایانامه، برنامه و وبسایتها."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"نمایه کاری شما توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود.\n\nسرپرستتان میتواند فعالیت شبکهتان از جمله رایانامه، برنامه و وبسایتها را کنترل کند.\n\nبرای دریافت اطلاعات بیشتر با سرپرستتان تماس بگیرید.\n\nهمچنین به یک VPN وصل هستید که میتواند فعالیت شبکه شما را کنترل کند."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"شما به <xliff:g id="APPLICATION">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شما از جمله رایانامه، برنامه و وبسایتها را کنترل کند."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"شما به <xliff:g id="APPLICATION">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شخصی شما از جمله رایانامه، برنامه و وبسایتها را کنترل کند."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"به <xliff:g id="APPLICATION">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شخصی شما را (ازجمله رایانامهها، برنامهها و وبسایتها) کنترل کند."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"نمایه کاریتان توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود. این به <xliff:g id="APPLICATION">%2$s</xliff:g> وصل است که فعالیت شبکه کاریتان از جمله رایانامه، برنامه و وبسایتها را کنترل میکند.\n\nبرای دریافت اطلاعات بیشتر، با سرپرستتان تماس بگیرید."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"نمایه کاری شما توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود. این به <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> متصل است که میتواند فعالیت شبکه کاریتان از جمله رایانامه، برنامه و وبسایتها را کنترل کند.\n\nشما همچنین به <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> متصل هستید که میتواند فعالیت شبکه شخصیتان را کنترل کند."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"دستگاه قفل باقی میماند تا زمانی که قفل آن را به صورت دستی باز کنید"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"دریافت سریعتر اعلانها"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index d6ce213..dda508f 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> – varoitus"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Työtila"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Yövalo"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Ei viimeaikaisia kohteita"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Kaikki on hoidettu."</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Sovellustiedot"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Vaakasuuntainen jako"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Pystysuuntainen jako"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Muokattu jako"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Hylkää"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Avaa"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Jaa näyttö ylös"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Jaa näyttö vasemmalle"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Jaa näyttö oikealle"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Ladattu"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Katkaise VPN-yhteys"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Laitettasi hallinnoi <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> hallinnoi laitettasi sovelluksen <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> avulla."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Järjestelmänvalvoja voi valvoa ja hallinnoida asetuksia, yrityskäyttöä, sovelluksia, laitteen sijaintitietoja ja muita tietoja."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Lisätietoja"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Olet yhteydessä sovellukseen <xliff:g id="VPN_APP">%1$s</xliff:g>, joka voi valvoa verkkotoimintaasi, esimerkiksi sähköposteja, sovelluksia ja verkkosivustoja."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Avaa VPN-asetukset"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Järjestelmänvalvoja on ottanut käyttöön verkkolokitiedostojen tallentamisen. Sen avulla seurataan laitteellasi tapahtuvaa liikennettä.\n\nPyydä lisätietoja järjestelmänvalvojalta."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Olet myöntänyt sovellukselle oikeuden VPN-yhteyden muodostamiseen.\n\nSovellus voi valvoa laitettasi ja toimintaasi verkossa, esimerkiksi avaamiasi sähköposteja, sovelluksia ja verkkosivustoja."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Työprofiiliasi hallinnoi <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nJärjestelmänvalvojasi voi valvoa toimintaasi verkossa. Hän voi seurata esimerkiksi avaamiasi sähköposteja, sovelluksia ja verkkosivustoja.\n\nLisätietoja saat järjestelmänvalvojaltasi.\n\nLisäksi olet muodostanut yhteyden VPN-palveluun, joka voi valvoa toimintaasi verkossa."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Olet muodostanut yhteyden sovellukseen <xliff:g id="APPLICATION">%1$s</xliff:g>, joka voi valvoa toimintaasi verkossa. Sovellus voi seurata esimerkiksi avaamiasi sähköposteja, sovelluksia ja verkkosivustoja."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Olet muodostanut yhteyden sovellukseen <xliff:g id="APPLICATION">%1$s</xliff:g>, joka voi valvoa henkilökohtaista toimintaasi verkossa. Sovellus voi seurata esimerkiksi avaamiasi sähköposteja, sovelluksia ja verkkosivustoja."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Olet muodostanut yhteyden sovellukseen <xliff:g id="APPLICATION">%1$s</xliff:g>, joka voi valvoa henkilökohtaista toimintaasi verkossa. Sovellus voi esimerkiksi seurata avaamiasi sähköposteja, sovelluksia ja verkkosivustoja."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Työprofiiliasi hallinnoi <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Se on yhteydessä sovellukseen <xliff:g id="APPLICATION">%2$s</xliff:g>, joka voi valvoa työhön liittyvää toimintaasi verkossa. Sovellus voi seurata esimerkiksi avaamiasi sähköposteja, sovelluksia ja verkkosivustoja.\n\nSaat lisätietoja järjestelmänvalvojaltasi."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Työprofiiliasi hallinnoi <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Se on yhteydessä sovellukseen <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, joka voi valvoa työhön liittyvää toimintaasi verkossa. Sovellus voi seurata esimerkiksi avaamiasi sähköposteja, sovelluksia ja verkkosivustoja.\n\nLisäksi olet yhteydessä sovellukseen <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, joka voi valvoa henkilökohtaista toimintaasi verkossa."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Laite pysyy lukittuna, kunnes se avataan käsin"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Näe ilmoitukset nopeammin"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 897890a..0790fb4 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -323,6 +323,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Avertissement : <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mode Travail"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Éclairage nocturne"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Aucun élément récent"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vous avez tout effacé"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Détails de l\'application"</string>
@@ -336,6 +342,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Séparation horizontale"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Séparation verticale"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Séparation personnalisée"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Fermer"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Ouvrir"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Écran partagé dans le haut"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Écran partagé à la gauche"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Écran partagé à la droite"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Chargée"</string>
@@ -416,20 +427,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Déconnecter le RPV"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Votre appareil est géré par <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> utilise <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> pour gérer votre appareil."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"L\'admin. de l\'entreprise peut surv. et gérer les param., l\'accès, les applis et les données de cet app., y compris sa localisation"</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"En savoir plus"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Vous êtes connecté à <xliff:g id="VPN_APP">%1$s</xliff:g>, qui peut contrôler votre activité réseau, y compris les courriels, les applications et les sites Web."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Paramètres RPV ouverts"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Votre administrateur a activé la journalisation réseau, qui surveille le trafic sur votre appareil.\n\nPour en savoir plus, communiquez avec lui."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Vous avez autorisé une application à configurer une connexion RPV.\n\nCette application peut contrôler l\'activité de votre appareil et votre activité sur le réseau, y compris les courriels, les applications et les sites Web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nVotre administrateur peut contrôler votre activité sur le réseau, y compris les courriels, les applications et les sites Web.\n\nPour en savoir plus, communiquez avec votre administrateur.\n\nVous êtes également connecté à un RPV qui peut contrôler votre activité sur le réseau."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"RPV"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Vous êtes connecté à <xliff:g id="APPLICATION">%1$s</xliff:g>. Cette application peut contrôler votre activité sur le réseau, y compris les courriels, les applications et les sites Web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Vous êtes connecté à <xliff:g id="APPLICATION">%1$s</xliff:g>. Cette application peut contrôler votre activité personnelle sur le réseau, y compris les courriels, les applications et les sites Web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Vous êtes connecté à <xliff:g id="APPLICATION">%1$s</xliff:g>. Cette application peut contrôler votre activité personnelle sur le réseau, y compris les courriels, les applications et les sites Web."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Il est connecté à <xliff:g id="APPLICATION">%2$s</xliff:g>. Cette application peut contrôler votre activité sur le réseau, y compris les courriels, les applications et les sites Web.\n\nPour en savoir plus, communiquez avec votre administrateur."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Il est connecté à <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>. Cette application peut contrôler votre activité sur le réseau, y compris les courriels, les applications et les sites Web.\n\nVous êtes également connecté à <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>. Cette application peut contrôler votre activité personnelle sur le réseau."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"L\'appareil restera verrouillé jusqu\'à ce que vous le déverrouilliez manuellement"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Voir les notifications plus rapidement"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 90bec35..f693c98 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -323,6 +323,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Avertissement : <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mode Travail"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Éclairage nocturne"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Aucun élément récent"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vous avez tout effacé."</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Infos application"</string>
@@ -336,6 +342,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Séparation horizontale"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Séparation verticale"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Séparation personnalisée"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Ignorer"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Ouvrir"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Partager l\'écran en haut"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Partager l\'écran sur la gauche"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Partager l\'écran sur la droite"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Chargé"</string>
@@ -416,20 +427,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Déconnecter le VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Votre appareil est géré par <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> utilise <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> pour gérer votre appareil."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Votre administrateur peut contrôler et gérer paramètres, accès aux contenus entreprise, applications, données et informations de localisation associées à cet appareil."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"En savoir plus"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Vous êtes connecté à <xliff:g id="VPN_APP">%1$s</xliff:g>, qui peut contrôler votre activité sur le réseau, y compris les e-mails, les applications et les sites Web."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Ouvrir les paramètres VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Votre administrateur a activé la journalisation réseau, qui surveille le trafic sur votre appareil.\n\nPour en savoir plus, contactez-le."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Vous avez autorisé une application à configurer une connexion VPN.\n\nCette application peut contrôler l\'activité de votre appareil et votre activité sur le réseau, y compris votre activité relative aux e-mails, aux applications et aux sites Web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nVotre administrateur peut contrôler votre activité sur le réseau (e-mails, applications et sites Web).\n\nPour en savoir plus, contactez votre administrateur.\n\nVous êtes également connecté à un VPN qui peut contrôler votre activité sur le réseau."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Vous êtes connecté à <xliff:g id="APPLICATION">%1$s</xliff:g>. Cette application peut contrôler votre activité sur le réseau, y compris l\'activité relative aux e-mails, aux applications et aux sites Web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Vous êtes connecté à <xliff:g id="APPLICATION">%1$s</xliff:g>. Cette application peut contrôler votre activité personnelle sur le réseau, y compris votre activité relative aux e-mails, aux applications et aux sites Web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Vous êtes connecté à <xliff:g id="APPLICATION">%1$s</xliff:g>. Cette application peut contrôler votre activité personnelle sur le réseau, y compris les e-mails, les applications et les sites Web."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Il est connecté à <xliff:g id="APPLICATION">%2$s</xliff:g>. Cette application peut contrôler l\'activité de ce profil sur le réseau, y compris l\'activité relative aux e-mails, aux applications et aux sites Web.\n\nPour en savoir plus, contactez votre administrateur."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Il est connecté à <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>. Cette application peut contrôler l\'activité de ce profil sur le réseau, y compris l\'activité relative aux e-mails, aux applications et aux sites Web.\n\nVous êtes également connecté à <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>. Cette application peut surveiller votre activité personnelle sur le réseau."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"L\'appareil restera verrouillé jusqu\'à ce que vous le déverrouilliez manuellement."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Recevoir les notifications plus vite"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 7d9b137..fda9ffe 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -323,6 +323,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advertencia <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de traballo"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luz nocturna"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Non hai elementos recentes"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Borraches todo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Información da aplicación"</string>
@@ -336,6 +342,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Dividir en horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dividir en vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Dividir de xeito personalizado"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Ignorar"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Abrir"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir pantalla na parte superior"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir pantalla á esquerda"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir pantalla á dereita"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Cargada"</string>
@@ -416,20 +427,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Desconectar VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"O teu dispositivo está xestionado por <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> utiliza <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> para xestionar o teu dispositivo."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Para este dispositivo, o administrador pode controlar: configuración, acceso, aplicacións, datos e información de localización."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Máis información"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Estás conectado a <xliff:g id="VPN_APP">%1$s</xliff:g>, que pode controlar a túa actividade na rede, mesmo os correos electrónicos, as aplicacións e os sitios web."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Abrir configuración da VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"O administrador activou o rexistro na rede, que controla o tráfico do teu dispositivo.\n\nPara obter máis información, contacta co teu administrador."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Outorgaches permiso a unha aplicación para configurar unha conexión VPN.\n\nEsta aplicación pode supervisar a túa actividade na rede, incluídos os correos electrónicos, as aplicacións e os sitios web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"O teu perfil de traballo está xestionado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nO teu administrador é capaz de supervisar a túa actividade na rede, incluídos os correos electrónicos, as aplicacións e os sitios web.\n\nPara obter máis información, ponte en contacto co teu administrador.\n\nTamén estás conectado a unha VPN, que pode supervisar a túa actividade na rede."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Estás conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode supervisar a túa actividade na rede, incluídos os correos electrónicos, as aplicacións e os sitios web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Estás conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode supervisar a túa actividade persoal na rede, incluídos os correos electrónicos, as aplicacións e os sitios web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Estás conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode supervisar a túa actividade persoal na rede, incluídos os correos electrónicos, as aplicacións e os sitios web."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"O teu perfil de traballo está xestionado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode supervisar a túa actividade na rede, incluídos os correos electrónicos, as aplicacións e os sitios web.\n\nPara obter máis información, ponte en contacto co teu administrador."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"O teu perfil de traballo está xestionado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Está conectado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pode supervisar a túa actividade na rede, incluídos os correos electrónicos, as aplicacións e os sitios web.\n\nTamén estás conectado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode supervisar a túa actividade persoal na rede."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"O dispositivo permanecerá bloqueado ata que o desbloquees manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Recibir notificacións máis rápido"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 3bc21e1..540ab16 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ચેતવણી"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"કાર્ય મોડ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"રાત્રિ પ્રકાશ"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC અક્ષમ કરેલ છે"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC સક્ષમ કરેલ છે"</string>
<string name="recents_empty_message" msgid="808480104164008572">"કોઇ તાજેતરની આઇટમ્સ નથી"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"તમે બધું સાફ કર્યું"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ઍપ્લિકેશન માહિતી"</string>
@@ -334,6 +337,16 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"આડું વિભક્ત કરો"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ઊભું વિભક્ત કરો"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"કસ્ટમ વિભક્ત કરો"</string>
+ <!-- no translation found for recents_accessibility_dismissed (2354459747918667050) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_open (1651449827614876864) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_top (9056056469282256287) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_left (8987144699630620019) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_right (275069779299592867) -->
+ <skip />
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"ચાર્જ થઈ ગયું"</string>
@@ -414,20 +427,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN ડિસ્કનેક્ટ કરો"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"તમારું ઉપકરણ <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> દ્વારા સંચાલિત થાય છે."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>, તમારા ઉપકરણનું સંચાલન કરવા માટે <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> નો ઉપયોગ કરે છે."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"તમારા વ્યવસ્થાપક સેટિંગ્સ, કૉર્પોરેટ ઍક્સેસ, ઍપ્સ, તમારા ઉપકરણ સાથે સંકળાયેલ ડેટા અને તમારા ઉપકરણની સ્થાન માહિતીને મૉનિટર અને સંચાલિત કરી શકે છે."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"વ્યવસ્થાપક સેટિંગ્સ, કૉર્પોરેટ ઍક્સેસ, ઍપ્સ, તમારા ઉપકરણ સંબંદ્ધ ડેટા અને ઉપકરણની સ્થાન માહિતીનું નિરીક્ષણ અને સંચાલન કરી શકે છે."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"વધુ જાણો"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"તમે <xliff:g id="VPN_APP">%1$s</xliff:g> સાથે કનેક્ટ થયાં છો, જે ઇમેઇલ્સ, ઍપ્લિકેશનો અને વેબસાઇટ્સ સહિત તમારી નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN સેટિંગ્સ ખોલો"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"તમારા વ્યવસ્થાપકે નેટવર્ક લૉગિંગ ચાલુ કરેલ છે, જે તમારા ઉપકરણ પર ટ્રાફિકને મૉનિટર કરે છે.\n\nવધુ માહિતી માટે, તમારા વ્યવસ્થાપકનો સંપર્ક કરો."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"તમારા વ્યવસ્થાપકે નેટવર્ક લૉગિંગ ચાલુ કર્યુ છે, જે તમારા ઉપકરણ પર ટ્રાફિકનું નિરીક્ષણ કરે છે.\n\nવધુ માહિતી માટે, તમારા વ્યવસ્થાપકનો સંપર્ક કરો."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"તમે VPN કનેક્શન સેટ કરવા માટે ઍપ્લિકેશન પરવાનગી આપી.\n\nઆ ઍપ્લિકેશન ઇમેઇલ્સ, ઍપ્લિકેશનો અને વેબસાઇટ્સ સહિત તમારા ઉપકરણ અને નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"તમારી કાર્ય પ્રોફાઇલ <xliff:g id="ORGANIZATION">%1$s</xliff:g> દ્વારા સંચાલિત થાય છે.\n\nતમારા વ્યવસ્થાપક ઇમેઇલ્સ, ઍપ્લિકેશનો અને વેબસાઇટ્સ સહિતની તમારી નેટવર્ક પ્રવૃત્તિને મૉનિટર કરવામાં સમર્થ છે.\n\nવધુ માહિતી માટે, તમારા વ્યવસ્થાપકનો સંપર્ક કરો.\n\nતમે VPN સાથે પણ કનેક્ટ છો, જે તમારી નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"તમારી કાર્ય પ્રોફાઇલનું સંચાલન <xliff:g id="ORGANIZATION">%1$s</xliff:g> દ્વારા કરવામાં આવે છે.\n\n તમારા વ્યવસ્થાપક ઇમેઇલ, ઍપ્લિકેશનો, અને વેબસાઇટો સહિતની તમારી કાર્ય નેટવર્ક પ્રવૃત્તિનું નિરીક્ષણ કરવામાં સક્ષમ છે.\n\nવધુ માહિતી માટે, તમારા વ્યવસ્થાપકનો સંપર્ક કરો.\n\nતમે VPN સાથે પણ કનેક્ટ કરેલ છે, જે તમારી નેટવર્ક પ્રવૃત્તિનું નિરીક્ષણ કરી શકે છે."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"તમે <xliff:g id="APPLICATION">%1$s</xliff:g> સાથે કનેક્ટ થયાં છો, જે ઇમેઇલ્સ, ઍપ્લિકેશનો અને વેબસાઇટ્સ સહિતની તમારી નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"તમે <xliff:g id="APPLICATION">%1$s</xliff:g> સાથે કનેક્ટ થયાં છો, જે ઇમેઇલ્સ, ઍપ્લિકેશનો અને વેબસાઇટ્સ સહિતની તમારી વ્યક્તિગત નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"તમે <xliff:g id="APPLICATION">%1$s</xliff:g> સાથે કનેક્ટ થયાં છો, જે ઇમેઇલ્સ, ઍપ્લિકેશનો અને વેબસાઇટ્સ સહિત તમારી વ્યક્તિગત નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"તમારી કાર્ય પ્રોફાઇલ <xliff:g id="ORGANIZATION">%1$s</xliff:g> દ્વારા સંચાલિત થાય છે. તે <xliff:g id="APPLICATION">%2$s</xliff:g> સાથે કનેક્ટ થયેલ છે, જે ઇમેઇલ્સ, ઍપ્લિકેશનો અને વેબસાઇટ્સ સહિતની તમારી કાર્ય નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે.\n\nવધુ માહિતી માટે, તમારા વ્યવસ્થાપકનો સંપર્ક કરો."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"તમારી કાર્ય પ્રોફાઇલનું સંચાલન <xliff:g id="ORGANIZATION">%1$s</xliff:g> દ્વારા કરવામાં આવે છે. તેને <xliff:g id="APPLICATION">%2$s</xliff:g> સાથે કનેક્ટ કરેલ છે, જે ઇમેઇલ, ઍપ્લિકેશનો અને વેબસાઇટો સહિતની તમારી કાર્ય નેટવર્ક પ્રવૃત્તિનું નિરીક્ષણ કરી શકે છે. \n\nવધુ માહિતી માટે, તમારા વ્યવસ્થાપકનો સંપર્ક કરો."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"તમારી કાર્ય પ્રોફાઇલ <xliff:g id="ORGANIZATION">%1$s</xliff:g> દ્વારા સંચાલિત થાય છે. તે <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> સાથે કનેક્ટ થયેલ છે, જે ઇમેઇલ્સ, ઍપ્લિકેશનો અને વેબસાઇટ્સ સહિતની તમારી કાર્ય નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે.\n\nતમે <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> સાથે પણ કનેક્ટ થયેલ છો, જે તમારી વ્યક્તિગત નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"તમે ઉપકરણને મેન્યુઅલી અનલૉક કરશો નહીં ત્યાં સુધી તે લૉક રહેશે"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"વધુ ઝડપથી સૂચનાઓ મેળવો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index e019e09..3308662 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> चेतावनी"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"कार्य मोड"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"नाइट लाइट"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC बंद है"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC चालू है"</string>
<string name="recents_empty_message" msgid="808480104164008572">"हाल ही का कोई आइटम नहीं"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"आपने सब कुछ साफ़ कर दिया है"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"एप्लिकेशन जानकारी"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"क्षैतिज रूप से विभाजित करें"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"लम्बवत रूप से विभाजित करें"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"कस्टम रूप से विभाजित करें"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"खारिज करें"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"खुला"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ऊपर की ओर दो स्क्रीन बनाएं"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"बाईं ओर दो स्क्रीन बनाएं"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"दाईं ओर दो स्क्रीन बनाएं"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"चार्ज हो गई है"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN डिस्कनेक्ट करें"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> आपका डिवाइस प्रबंधित करता है."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> आपका डिवाइस प्रबंधित करने के लिए <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> का उपयोग करता है."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"व्यवस्थापक डिवाइस से संबद्ध सेटिंग, कॉर्पोरेट एक्सेस, ऐप्लिकेशन, डेटा और डिवाइस की स्थान जानकारी को मॉनिटर और प्रबंधित कर सकता है."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"आपका व्यवस्थापक आपके डिवाइस से जुड़ी सेटिंग, कॉर्पोरेट एक्सेस, ऐप्लिकेशन, डेटा और आपके डिवाइस की स्थान जानकारी की निगरानी और उसका प्रबंधन कर सकता है."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"अधिक जानें"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"आप <xliff:g id="VPN_APP">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्लिकेशन और वेबसाइट सहित आपकी नेटवर्क गतिविधि को मॉनिटर कर सकता है."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN सेटिंग खोलें"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"आपके व्यवस्थापक ने नेटवर्क लॉगिंग को चालू कर दिया है, जो आपके डिवाइस पर ट्रैफ़िक को मॉनीटर करती है.\n\nअधिक जानकारी के लिए अपने व्यवस्थापक से संपर्क करें."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"आपके व्यवस्थापक ने नेटवर्क लॉग करना चालू कर दिया है, जो आपके डिवाइस पर ट्रैफ़िक की निगरानी करता है.\n\nअधिक जानकारी के लिए अपने व्यवस्थापक से संपर्क करें."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"आपने किसी ऐप को VPN कनेक्शन सेट करने की अनुमति दी है.\n\nयह ऐप ईमेल, ऐप्स और सुरक्षित वेबसाइटों सहित आपके डिवाइस और नेटवर्क की गतिविधि की निगरानी कर सकता है."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"आपकी कार्य प्रोफ़ाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> के द्वारा प्रबंधित है.\n\nआपका नियंत्रक ईमेल, ऐप्स और सुरक्षित वेबसाइटों सहित आपकी नेटवर्क गतिविधि की निगरानी करने में सक्षम है.\n\nअधिक जानकारी के लिए, अपने नियंत्रक से संपर्क करें.\n\nआप एक ऐसे VPN से भी कनेक्ट हैं, जो आपकी नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> आपकी कार्य प्रोफ़ाइल को प्रबंधित करता है.\n\nआपका व्यवस्थापक ईमेल, ऐप्लिकेशन और वेबसाइटों सहित आपकी नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nअधिक जानकारी के लिए अपने व्यवस्थापक से संपर्क करें.\n\nआप ऐसे VPN से भी कनेक्ट हैं, जो आपकी नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्स और वेबसाइटों सहित आपकी नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्स और वेबसाइटों सहित आपकी व्यक्तिगत नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्लिकेशन और वेबसाइट सहित आपकी व्यक्तिगत नेटवर्क गतिविधि को मॉनिटर कर सकता है."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"आपकी कार्य प्रोफ़ाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> के द्वारा प्रबंधित है. वह <xliff:g id="APPLICATION">%2$s</xliff:g> से कनेक्ट है, जो ईमेल, ऐप्स और वेबसाइटों सहित आपकी कार्य नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nअधिक जानकारी के लिए, अपने नियंत्रक से संपर्क करें."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> आपकी कार्य प्रोफ़ाइल को प्रबंधित करता है. वह ऐसे <xliff:g id="APPLICATION">%2$s</xliff:g> से कनेक्ट है, जो ईमेल, ऐप्लिकेशन और वेबसाइटों सहित आपकी कार्य नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nअधिक जानकारी के लिए अपने व्यवस्थापक से संपर्क करें."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"आपकी कार्य प्रोफ़ाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> के द्वारा प्रबंधित है. वह <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> से कनेक्ट है, जो ईमेल, ऐप्स और वेबसाइटों सहित आपकी कार्य नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nआप <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> से भी कनेक्ट हैं, जो आपकी व्यक्तिगत नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"जब तक कि आप मैन्युअल रूप से अनलॉक नहीं करते तब तक डिवाइस लॉक रहेगा"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"सूचनाएं अधिक तेज़ी से प्राप्त करें"</string>
@@ -648,7 +656,7 @@
<string name="accessibility_quick_settings_open_details" msgid="4230931801728005194">"विवरण खोलें."</string>
<string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> सेटिंग खोलें."</string>
<string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"सेटिंग का क्रम संपादित करें."</string>
- <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"पृष्ठ <xliff:g id="ID_2">%2$d</xliff:g> में से <xliff:g id="ID_1">%1$d</xliff:g>"</string>
+ <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"पेज <xliff:g id="ID_2">%2$d</xliff:g> में से <xliff:g id="ID_1">%1$d</xliff:g>"</string>
<string name="pip_phone_expand" msgid="5889780005575693909">"विस्तृत करें"</string>
<string name="pip_phone_minimize" msgid="1079119422589131792">"छोटा करें"</string>
<string name="pip_phone_dismiss" msgid="1305916715402775904">"खारिज करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 6414afc..6a2ce8c 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozorenje <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Način rada"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Noćno svjetlo"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC je onemogućen"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC je omogućen"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nema nedavnih stavki"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Izbrisali ste sve"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacije o aplikaciji"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Podijeli vodoravno"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Podijeli okomito"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Podijeli prilagođeno"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Odbaci"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Otvori"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Podijeli zaslon na vrhu"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Podijeli zaslon slijeva"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Podijeli zaslon zdesna"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Napunjeno"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Prekini vezu s VPN-om"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Vašim uređajem upravlja aplikacija <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> upotrebljava aplikaciju <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> za upravljanje vašim uređajem."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administrator može nadzirati postavke, korporacijski pristup, aplikacije, podatke o uređaju i lokaciji uređaja te upravljati njima"</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Administrator može nadzirati postavke, korporacijski pristup, aplikacije, podatke o uređaju i lokaciji uređaja te upravljati njima"</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Saznajte više"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Povezani ste s aplikacijom <xliff:g id="VPN_APP">%1$s</xliff:g> koja može nadzirati vašu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Otvorite postavke VPN-a"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administrator je uključio mrežni zapisnik koji prati promet na vašem uređaju.\n\nViše informacija možete dobiti od administratora."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Administrator je uključio mrežni zapisnik koji prati promet na vašem uređaju.\n\nViše informacija možete saznati od administratora."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Dali ste dopuštenje aplikaciji za postavljanje VPN veze.\n\nTa aplikacija može nadzirati vašu aktivnost na uređaju i mreži, uključujući e-poštu, aplikacije i web-lokacije."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Vašim poslovnim profilom upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nVaš administrator može nadzirati vašu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije.\n\nObratite se administratoru za više informacija.\n\nPovezani ste i s VPN-om koji može nadzirati vašu aktivnost na mreži."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Vašim radnim profilom upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nVaš administrator može nadzirati vašu mrežnu aktivnost, uključujući e-poštu, aplikacije i web-lokacije.\n\nViše informacija možete saznati od administratora.\n\nPovezani ste i s VPN-om koji može nadzirati vašu mrežnu aktivnost."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Povezani ste s aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g> koja može nadzirati vašu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Povezani ste s aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g> koja može nadzirati vašu osobnu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Povezani ste s aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g> koja može nadzirati vašu osobnu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Vašim poslovnim profilom upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je s aplikacijom <xliff:g id="APPLICATION">%2$s</xliff:g> koja može nadzirati vašu poslovnu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije.\n\nObratite se svojem administratoru za više informacija."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Vašim radnim profilom upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil je povezan s aplikacijom <xliff:g id="APPLICATION">%2$s</xliff:g> koja može nadzirati vašu poslovnu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije.\n\nViše informacija možete saznati od administratora."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Vašim poslovnim profilom upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je s aplikacijom <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> koja može nadzirati vašu poslovnu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije.\n\nPovezani ste i s aplikacijom <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> koja može nadzirati vašu osobnu aktivnost na mreži."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Uređaj će ostati zaključan dok ga ručno ne otključate"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Primajte obavijesti brže"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 2a891b7..334427f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Figyelem! <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Munka mód"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Éjszakai fény"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"Az NFC ki van kapcsolva"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"Az NFC be van kapcsolva"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nincsenek mostanában használt elemek"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Mindent törölt"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Az alkalmazás adatai"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Osztott vízszintes"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Osztott függőleges"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Osztott egyéni"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Elvetés"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Megnyitás"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Osztott képernyő felülre"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Osztott képernyő balra"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Osztott képernyő jobbra"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Feltöltve"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN-kapcsolat bontása"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Az eszközt a(z) <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> kezeli."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"A(z) <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> a(z) <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> alkalmazást használja az eszközkezeléshez."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"A rendszergazda kezelheti az eszközzel kapcsolatos beállításokat, vállalati hozzáférést, alkalmazásokat, adatokat és helyadatokat."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"A rendszergazda felügyelheti és kezelheti az eszköz beállításait, vállalatszintű hozzáférését, alkalmazásait, adatait és helyadatait."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"További információ"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Ön kapcsolódik ehhez: <xliff:g id="VPN_APP">%1$s</xliff:g>, amely figyelheti hálózati tevékenységét, köztük a levelezést, az alkalmazás- és webhelyhasználatot."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN-beállítások megnyitása"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Rendszergazdája bekapcsolta a hálózati naplózást, amely figyeli eszköze forgalmát.\n\nHa további információra van szüksége, vegye fel a kapcsolatot rendszergazdájával."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"A rendszergazda bekapcsolta az eszköz forgalmát figyelő hálózati naplózást.\n\nHa további információra van szüksége, forduljon a rendszergazdához."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Engedélyezte egy alkalmazásnak, hogy VPN-kapcsolatot létesítsen.\n\nEz az alkalmazás (az e-mailekre, alkalmazásokra és a webhelyekre is kiterjedően) figyelemmel kísérheti az Ön eszközét és hálózati tevékenységét."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Munkaprofilját a következő felügyeli: <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nA rendszergazda (az e-mailekre, alkalmazásokra és a webhelyekre is kiterjedően) figyelemmel kísérheti hálózati tevékenységét.\n\nTovábbi információért forduljon a rendszergazdához.\n\nÖn egy VPN-hez is csatlakozik, amely szintén figyelemmel kísérheti hálózati tevékenységét."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Munkaprofilját a(z) <xliff:g id="ORGANIZATION">%1$s</xliff:g> kezeli.\n\nA rendszergazda felügyelheti hálózati tevékenységét, köztük az e-maileket, az alkalmazásokat és a webhelyeket.\n\nHa további információra van szüksége, forduljon a rendszergazdához.\n\nVPN-hez is kapcsolódik, amely szintén felügyelheti hálózati tevékenységét."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Csatlakoztatta a(z) <xliff:g id="APPLICATION">%1$s</xliff:g> alkalmazást, amely figyelheti hálózati tevékenységét, beleértve az e-maileket, az alkalmazásokat és a webhelyeket."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Csatlakoztatta a(z) <xliff:g id="APPLICATION">%1$s</xliff:g> alkalmazást, amely figyelheti személyes hálózati tevékenységét, beleértve az e-maileket, az alkalmazásokat és a webhelyeket."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Ön a(z) <xliff:g id="APPLICATION">%1$s</xliff:g> alkalmazáshoz csatlakozik, amely figyelheti személyes hálózati tevékenységét, beleértve az e-maileket, alkalmazásokat és webhelyeket."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Munkaprofilját a(z) <xliff:g id="ORGANIZATION">%1$s</xliff:g> felügyeli. Csatlakoztatva van hozzá a(z) <xliff:g id="APPLICATION">%2$s</xliff:g> alkalmazás, amely figyelheti az Ön hálózati tevékenységét, beleértve az e-maileket, az alkalmazásokat és a webhelyeket.\n\nTovábbi információért forduljon a rendszergazdájához."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Munkaprofilját a(z) <xliff:g id="ORGANIZATION">%1$s</xliff:g> kezeli. Össze van kapcsolva a(z) <xliff:g id="APPLICATION">%2$s</xliff:g> alkalmazással, amely felügyelheti az Ön munkahelyi hálózati tevékenységét, beleértve az e-maileket, az alkalmazásokat és a webhelyeket.\n\nHa további információra van szüksége, forduljon a rendszergazdához."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Munkaprofilját a(z) <xliff:g id="ORGANIZATION">%1$s</xliff:g> felügyeli. Csatlakoztatva van hozzá a(z) <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> alkalmazás, amely figyelheti az Ön hálózati tevékenységét, beleértve az e-maileket, az alkalmazásokat és a webhelyeket.\n\nCsatlakoztatta továbbá a(z) <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> alkalmazást, amely szintén figyelemmel kísérheti személyes hálózati tevékenységét."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Az eszköz addig zárolva marad, amíg kézileg fel nem oldja"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Gyorsabban megkaphatja az értesítéseket"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index c17c522..ef351bb9 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> զգուշացում"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Աշխատանքային ռեժիմ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Գիշերային լույս"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC-ն անջատված է"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC-ն միացված է"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Վերջին տարրեր չկան"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Դուք ջնջել եք ամենը"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Հավելվածի մասին"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Հորիզոնական տրոհում"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Ուղղահայաց տրոհում"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Հատուկ տրոհում"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Փակել"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Բացել"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Տրոհել էկրանը վերևից"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Տրոհել էկրանը ձախից"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Տրոհել էկրանն աջից"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Լիցքավորված է"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Անջատել VPN-ը"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Ձեր սարքը կառավարվում է <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> հավելվածի կողմից:"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>-ը ձեր սարքը կառավարելու համար օգտագործում է <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> հավելվածը:"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Ձեր ադմինիստրատորը կարող է վերահսկել և կառավարել ձեր սարքի հետ կապակցված կարգավորումները, կորպորատիվ մուտքը, հավելվածները և տվյալները, ինչպես նաև ձեր սարքի տեղադրության տվյալները:"</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Ձեր ադմինիստրատորը կարող է վերահսկել և կառավարել ձեր սարքի հետ կապակցված կարգավորումները, կորպորատիվ մուտքը, հավելվածները և տվյալները, ինչպես նաև ձեր սարքի տեղադրության տվյալները:"</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Իմանալ ավելին"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Դուք կապակցված եք <xliff:g id="VPN_APP">%1$s</xliff:g> հավելվածին, որը կարող է վերահսկել ձեր ցանցային գործողությունը, այդ թվում նաև էլփոստը, հավելվածները և կայքերը:"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Բացել VPN-ի կարգավորումները"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Ձեր ադմինիստրատորը միացրել է ցանցային իրադարձությունների գրանցումը, որը վերահսկում է ձեր սարքի թրաֆիկը։\n\nԼրացուցիչ տեղեկություններ ստանալու համար դիմեք ձեր ադմինիստրատորին։"</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Ձեր ադմինիստրատորը միացրել է ցանցային իրադարձությունների գրանցումը, որը վերահսկում է ձեր սարքի թրաֆիկը։\n\nԼրացուցիչ տեղեկություններ ստանալու համար դիմեք ձեր ադմինիստրատորին։"</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Ինչ-որ հավելվածի թույլ եք տվել հաստատել VPN կապակցում:\n\nԱյդ հավելվածը կարող է վերահսկել ձեր սարքի և ցանցի գործունեությունը, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Աշխատանքային պրոֆիլի կառավարիչն է՝ <xliff:g id="ORGANIZATION">%1$s</xliff:g>:\n\nԱդմինիստրատորը կարող է վերահսկել ցանցում ունեցած գործունեությունը, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:\n\nԼրացուցիչ տեղեկությունների համար դիմեք ադմինիստրատորին:\n\nՆաև ունեք VPN կապակցում, ինչը կարող է վերահսկել ձեր ցանցում կատարած գործողությունները:"</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Ձեր աշխատանքային պրոֆիլի կառավարիչն է <xliff:g id="ORGANIZATION">%1$s</xliff:g> կազմակերպությունը։\n\nԱդմինիստրատորը կարող է վերահսկել ձեր ցանցային գործունեությունը, այդ թվում նաև էլփոստը, հավելվածները և կայքերը։\n\nԼրացուցիչ տեղեկությունների համար դիմեք ադմինիստրատորին։\n\nԴուք կապակցված են նաև VPN ցանցին, որը կարող է վերահսկել ձեր ցանցային գործունեությունը։"</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Դուք կապակցված եք <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածին, որը կարող է վերահսկել ձեր ցանցի գործունեությունը, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Դուք կապակցված եք <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածին, որը կարող է վերահսկել անձնական ցանցում կատարած ձեր գործողությունները, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Դուք կապակցված եք <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածին, որը կարող է վերահսկել անձնական ցանցում կատարած ձեր գործողությունները, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Աշխատանքային պրոֆիլի կառավարիչն է՝ <xliff:g id="ORGANIZATION">%1$s</xliff:g>: Այն կապակցված է <xliff:g id="APPLICATION">%2$s</xliff:g> հավելվածին, որը կարող է վերահսկել աշխատանքային ցանցում կատարած գործունեությունը, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:\n\nԼրացուցիչ տեղեկությունների համար դիմեք ադմինիստրատորին:"</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Ձեր աշխատանքային պրոֆիլի կառավարիչն է <xliff:g id="ORGANIZATION">%1$s</xliff:g> կազմակերպությունը։ Այն կապակցված է <xliff:g id="APPLICATION">%2$s</xliff:g> հավելվածին, որը կարող է վերահսկել ձեր ցանցային գործունեությունը, այդ թվում նաև էլփոստը, հավելվածները և կայքերը։\n\nԼրացուցիչ տեղեկությունների համար դիմեք ադմինիստրատորին:"</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Աշխատանքային պրոֆիլի կառավարիչն է՝ <xliff:g id="ORGANIZATION">%1$s</xliff:g>: Այն կապակցված է <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> հավելվածին, որը կարող է վերահսկել աշխատանքային ցանցում կատարած գործունեությունը, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:\n\nԴուք նույնպես կապակցված եք <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> հավելվածին, որը կարող է վերահսկել անձնական ցանցում կատարած ձեր գործողությունները:"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Սարքը կմնա արգելափակված՝ մինչև ձեռքով չբացեք"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Ավելի արագ ստացեք ծանուցումները"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index e7d7c5d..6ace014 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Peringatan <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mode kerja"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Cahaya Malam"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC dinonaktifkan"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC diaktifkan"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Tidak ada item baru-baru ini"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Anda sudah menghapus semua"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Info Aplikasi"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Pisahkan Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Pisahkan Vertikal"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Pisahkan Khusus"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Tutup"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Buka"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Pisahkan layar ke atas"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Pisahkan layar ke kiri"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Pisahkan layar ke kanan"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Terisi"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Putuskan sambungan VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Perangkat dikelola oleh <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> menggunakan <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> untuk mengelola perangkat Anda."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Admin dapat memantau dan mengelola setelan, akses perusahaan, aplikasi, data terkait perangkat, dan informasi lokasi perangkat."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Admin dapat memantau dan mengelola setelan, akses perusahaan, aplikasi, data terkait perangkat, dan informasi lokasi perangkat."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Pelajari lebih lanjut"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Anda tersambung ke <xliff:g id="VPN_APP">%1$s</xliff:g>, yang dapat memantau aktivitas jaringan, termasuk email, aplikasi, dan situs web."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Buka Setelan VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Admin telah mengaktifkan pencatatan log jaringan, yang memantau traffic di perangkat.\n\nUntuk informasi selengkapnya, hubungi admin."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Admin telah mengaktifkan pencatatan log jaringan, yang memantau traffic di perangkat.\n\nUntuk informasi selengkapnya, hubungi admin."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Anda memberikan izin kepada aplikasi untuk menyiapkan sambungan VPN.\n\nAplikasi ini ini dapat memantau aktivitas perangkat dan jaringan, termasuk email, aplikasi, dan situs web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Profil kerja dikelola oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministrator dapat memantau aktivitas jaringan, termasuk email, aplikasi, dan situs web.\n\nUntuk informasi selengkapnya, hubungi administrator.\n\nAnda juga tersambung ke VPN yang dapat memantau aktivitas jaringan."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Profil kerja dikelola oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdmin dapat memantau aktivitas jaringan, termasuk email, aplikasi, dan situs web.\n\nUntuk informasi selengkapnya, hubungi admin.\n\nAnda juga tersambung ke VPN, yang dapat memantau aktivitas jaringan."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Anda tersambung ke <xliff:g id="APPLICATION">%1$s</xliff:g>, yang dapat memantau aktivitas jaringan, termasuk email, aplikasi, dan situs web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Anda tersambung ke <xliff:g id="APPLICATION">%1$s</xliff:g>, yang dapat memantau aktivitas jaringan pribadi, termasuk email, aplikasi, dan situs web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Anda tersambung ke <xliff:g id="APPLICATION">%1$s</xliff:g>, yang dapat memantau aktivitas jaringan pribadi, termasuk email, aplikasi, dan situs web.."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Profil kerja dikelola oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g> dan tersambung ke <xliff:g id="APPLICATION">%2$s</xliff:g>, yang dapat memantau aktivitas jaringan kerja, termasuk email, aplikasi, dan situs web.\n\nUntuk informasi selengkapnya, hubungi administrator."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Profil kerja dikelola oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil ini terhubung ke <xliff:g id="APPLICATION">%2$s</xliff:g>, yang dapat memantau aktivitas jaringan kerja, termasuk email, aplikasi, dan situs web.\n\nUntuk informasi selengkapnya, hubungi admin."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Profil kerja dikelola oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g> dan tersambung ke <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, yang dapat memantau aktivitas jaringan kerja, termasuk email, aplikasi, dan situs web.\n\nAnda juga tersambung ke <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, yang dapat memantau aktivitas jaringan pribadi."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Perangkat akan tetap terkunci hingga Anda membukanya secara manual"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Dapatkan pemberitahuan lebih cepat"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 3807554..8538305 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> viðvörun"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Vinnustilling"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Næturljós"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"Slökkt á NFC"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"Kveikt á NFC"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Engin nýleg atriði"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Þú hefur hreinsað allt"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Forritsupplýsingar"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Lárétt skipting"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Lóðrétt skipting"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Sérsniðin skipting"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Hunsa"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Opna"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Skipta skjá að ofanverðu"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Skipta skjá til vinstri"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Skipta skjá til hægri"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Fullhlaðin"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Aftengja VPN-net"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Þessu tæki er stýrt af <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> notar <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> til að stýra tækinu þínu."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Kerfisstjóri getur fylgst með og stjórnað stillingum, fyrirtækjaaðgangi, forritum, gögnum tengdum tækinu og staðsetningu tækisins."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Kerfisstjóri getur fylgst með og stjórnað stillingum, fyrirtækjaaðgangi, forritum, gögnum tengdum tækinu og staðsetningu tækisins."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Frekari upplýsingar"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Þú ert með tengingu við <xliff:g id="VPN_APP">%1$s</xliff:g>, sem getur fylgst með netnotkun þinni, þ. á m. tölvupósti, forritum og vefsvæðum."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Opna VPN-stillingar"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Kerfisstjóri hefur kveikt á eftirliti netkerfa, sem fylgist með netumferð á tækinu þínu.\n\nHafðu samband við kerfisstjóra til að fá frekari upplýsingar."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Kerfisstjóri hefur kveikt á eftirliti netkerfa, sem fylgist með netumferð á tækinu þínu.\n\nHafðu samband við kerfisstjóra til að fá frekari upplýsingar."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Þú veittir forriti heimild til að koma á VPN-tengingu.\n\nÞetta forrit getur fylgst með virkni þinni í tækinu og á netinu, þar á meðal tölvupósti, forritum og vefsvæðum."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Vinnusniðinu þínu er stjórnað af <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nKerfisstjórinn þinn getur fylgst með netnotkun þinni, þar á meðal tölvupósti, forritum og vefsvæðum.\n\nHafðu samband við kerfisstjórann til að fá frekari upplýsingar.\n\nÞú ert einnig með tengingu við VPN-net sem getur fylgst með netnotkun þinni."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> stýrir vinnusniðinu þínu.\n\nKerfisstjórinn getur fylgst með virkni þinni á netinu, þ.m.t. tölvupósti, forritum og vefsvæðum.\n\nHafðu samband við kerfisstjórann til að fá frekari upplýsingar.\n\nÞú ert einnig með VPN-tengingu, sem getur fylgst með virkni þinni á netinu."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Þú ert með tengingu við <xliff:g id="APPLICATION">%1$s</xliff:g>, sem getur fylgst með netnotkun þinni, þ. á m. tölvupósti, forritum og vefsvæðum."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Þú ert með tengingu við <xliff:g id="APPLICATION">%1$s</xliff:g>, sem getur fylgst með persónulegri netnotkun þinni, þ. á m. tölvupósti, forritum og vefsvæðum."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Þú ert með tengingu við <xliff:g id="APPLICATION">%1$s</xliff:g>, sem getur fylgst með persónulegri netnotkun þinni, þ. á m. tölvupósti, forritum og vefsvæðum."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Vinnusniðinu þínu er stjórnað af <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Það er tengt <xliff:g id="APPLICATION">%2$s</xliff:g>, sem getur fylgst með vinnutengdri netnotkun þinni, þar á meðal tölvupósti, forritum og vefsvæðum.\n\nHafðu samband við kerfisstjórann til að fá frekari upplýsingar."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> stýrir vinnusniðinu þínu. Það er tengt við <xliff:g id="APPLICATION">%2$s</xliff:g>, sem getur fylgst með netvirkni þinni í vinnunni, þar á meðal tölvupósti, forritum og vefsvæðum.\n\nHafðu samband við kerfisstjóra til að fá frekari upplýsingar."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Vinnusniðinu þínu er stjórnað af <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Það er tengt <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, sem getur fylgst með vinnutengdri netnotkun þinni, þar á meðal tölvupósti, forritum og vefsvæðum.\n\nÞú ert einnig með tengingu við <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, sem getur fylgst með persónulegri netnotkun þinni."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Tækið verður læst þar til þú opnar það handvirkt"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Fáðu tilkynningar hraðar"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 967f353..3273784 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Avviso <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modalità Lavoro"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luminosità notturna"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC non attiva"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC attiva"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nessun elemento recente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Hai cancellato tutto"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informazioni sull\'applicazione"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisione in orizzontale"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisione in verticale"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisione personalizzata"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Ignora"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Apri"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Schermo diviso in alto"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Schermo diviso a sinistra"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Schermo diviso a destra"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carica"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Scollega VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Il dispositivo è gestito dall\'app <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> utilizza l\'app <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> per gestire il dispositivo."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"L\'amministratore può monitorare e gestire impostazioni, accesso aziendale, app, dati associati al dispositivo e informazioni sulla posizione del dispositivo."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"L\'amministratore può monitorare e gestire impostazioni, accesso aziendale, app, dati associati al dispositivo e informazioni sulla posizione del dispositivo."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Ulteriori informazioni"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Sei connesso a <xliff:g id="VPN_APP">%1$s</xliff:g>, che consente di monitorare le attività di rete, inclusi siti web, email e app."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Apri impostazioni VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"L\'amministratore ha attivato i log di rete, che monitorano il traffico sul dispositivo.\n\nContatta l\'amministratore per ulteriori informazioni."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"L\'amministratore ha attivato i log di rete, che consentono di monitorare il traffico sul dispositivo.\n\nPer ulteriori informazioni, contatta l\'amministratore."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Hai autorizzato l\'app a configurare una connessione VPN.\n\nQuesta app può monitorare il tuo dispositivo e l\'attività di rete, inclusi email, app e siti web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Il tuo profilo di lavoro è gestito da <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nL\'amministratore può monitorare l\'attività di rete, inclusi email, app e siti web.\n\nPer ulteriori informazioni, contatta l\'amministratore.\n\nSei connesso anche a VPN, da cui è possibile monitorare la tua attività di rete."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Il tuo profilo di lavoro è gestito da <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nL\'amministratore può monitorare la tua attività di rete, inclusi siti web, email e app.\n\nPer ulteriori informazioni, contatta l\'amministratore.\n\nSei inoltre connesso a una VPN, da cui è possibile monitorare la tua attività di rete."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Sei connesso a <xliff:g id="APPLICATION">%1$s</xliff:g>, da cui è possibile monitorare la tua attività di rete, inclusi email, app e siti web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Sei connesso a <xliff:g id="APPLICATION">%1$s</xliff:g>, da cui è possibile monitorare la tua attività di rete personale, inclusi email, app e siti web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Sei collegato a <xliff:g id="APPLICATION">%1$s</xliff:g>, che consente di monitorare la tua attività di rete personale, inclusi siti web, email e app."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Il tuo profilo di lavoro è gestito da <xliff:g id="ORGANIZATION">%1$s</xliff:g>. È connesso a <xliff:g id="APPLICATION">%2$s</xliff:g>, da cui è possibile monitorare la tua attività di rete lavorativa, inclusi email, app e siti web.\n\nPer ulteriori informazioni, contatta il tuo amministratore."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Il tuo profilo di lavoro è gestito da <xliff:g id="ORGANIZATION">%1$s</xliff:g>. È connesso a <xliff:g id="APPLICATION">%2$s</xliff:g>, da cui è possibile monitorare la tua attività di rete lavorativa, inclusi siti web, email e app.\n\nPer ulteriori informazioni, contatta l\'amministratore."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Il tuo profilo di lavoro è gestito da <xliff:g id="ORGANIZATION">%1$s</xliff:g>. È connesso a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, da cui è possibile monitorare la tua attività di rete lavorativa, inclusi email, app e siti web.\n\nSei connesso anche a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, da cui è possibile monitorare la tua attività di rete personale."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Il dispositivo resterà bloccato fino allo sblocco manuale"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Ricevi notifiche più velocemente"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 7dac321..c33c536 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -325,6 +325,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"אזהרה - <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"מצב עבודה"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"תאורת לילה"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC מושבת"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC מופעל"</string>
<string name="recents_empty_message" msgid="808480104164008572">"אין פריטים אחרונים"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"מחקת הכול"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"מידע על האפליקציה"</string>
@@ -338,6 +341,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"פיצול אופקי"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"פיצול אנכי"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"פיצול מותאם אישית"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"סגור"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"פתח"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"פיצול מסך למעלה"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"פיצול מסך לשמאל"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"פיצול מסך לימין"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"טעון"</string>
@@ -418,20 +426,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"נתק את ה-VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"המכשיר שלך מנוהל על ידי <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> משתמש באפליקציה <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> כדי לנהל את מכשירך."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"מנהל המערכת יכול לבצע מעקב ולנהל הגדרות, גישה ארגונית, אפליקציות ונתונים המשויכים למכשירך, ולמידע על מיקום המכשיר."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"מנהל המערכת יכול לנטר ולנהל הגדרות, גישה ארגונית, אפליקציות, נתונים המשויכים למכשיר ומידע על מיקום המכשיר."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"למידע נוסף"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"אתה מחובר לאפליקציה <xliff:g id="VPN_APP">%1$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת, כולל הודעות אימייל, אפליקציות ואתרים."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"פתח את הגדרות ה-VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"מנהל המערכת הפעיל את תכונת רישום התנועה ברשת, אשר מנטרת את תנועת הנתונים במכשירך.\n\nלמידע נוסף, צור קשר עם מנהל המערכת."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"מנהל המערכת הפעיל את תכונת רישום התנועה ברשת, שמנטרת את תנועת הנתונים במכשיר.\n\nלמידע נוסף, צור קשר עם מנהל המערכת."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"נתת לאפליקציה כלשהי הרשאה להגדיר חיבור VPN.\n\nהאפליקציה הזו יכולה לעקוב אחר הפעילות שלך ברשת ובמכשיר, כולל הודעות אימייל, אפליקציות ואתרים."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"פרופיל העבודה שלך מנוהל על ידי <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nמנהל המערכת שלך יכול לעקוב אחר הפעילות שלך ברשת, כולל הודעות אימייל, אפליקציות ואתרים.\n\nלמידע נוסף, פנה למנהל המערכת שלך.\n\nאתה מחובר גם לרשת VPN, שיכולה לעקוב אחר הפעילות שלך ברשת."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"פרופיל העבודה שלך מנוהל על-ידי <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\n מנהל המערכת שלך יכול לעקוב אחרי הפעילות שלך ברשת, כולל פעילות באימייל, באפליקציות ובאתרים.\n\n למידע נוסף, צור קשר עם מנהל המערכת.\n\nבנוסף, אתה מחובר ל-VPN, שגם באמצעותו ניתן לעקוב אחרי הפעילות שלך ברשת."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"אתה מחובר לאפליקציה <xliff:g id="APPLICATION">%1$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת, כולל הודעות אימייל, אפליקציות ואתרים."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"אתה מחובר לאפליקציה <xliff:g id="APPLICATION">%1$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת הפרטית, כולל הודעות אימייל, אפליקציות ואתרים."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"אתה מחובר לאפליקציה <xliff:g id="APPLICATION">%1$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת הפרטית, כולל הודעות אימייל, אפליקציות ואתרים."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"פרופיל העבודה שלך מנוהל על ידי <xliff:g id="ORGANIZATION">%1$s</xliff:g>. הוא מחובר לאפליקציה <xliff:g id="APPLICATION">%2$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת העסקית, כולל הודעות אימייל, אפליקציות ואתרים.\n\nלמידע נוסף, פנה למנהל המערכת שלך."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"פרופיל העבודה שלך מנוהל על-ידי <xliff:g id="ORGANIZATION">%1$s</xliff:g>. הוא מחובר ל-<xliff:g id="APPLICATION">%2$s</xliff:g>, אפליקציה שיכולה לעקוב אחרי הפעילות שלך ברשת, כולל פעילות באימייל, באפליקציות ובאתרים.\n\nלמידע נוסף, צור קשר עם מנהל המערכת."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"פרופיל העבודה שלך מנוהל על ידי <xliff:g id="ORGANIZATION">%1$s</xliff:g>. הוא מחובר לאפליקציה <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת העסקית, כולל הודעות אימייל, אפליקציות ואתרים.\n\nאתה מחובר גם לאפליקציה <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת הפרטית."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"המכשיר יישאר נעול עד שתבטל את נעילתו באופן ידני"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"קבל התראות מהר יותר"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index f01b82d..97d5834 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -323,6 +323,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"警告: 上限は<xliff:g id="DATA_LIMIT">%s</xliff:g>です"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work モード"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"読書灯"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"最近のタスクはありません"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"すべてのタスクを消去しました"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"アプリ情報"</string>
@@ -336,6 +342,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"横に分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"縦に分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"分割(カスタム)"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"閉じる"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"開く"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"画面を上に分割"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"画面を左に分割"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"画面を右に分割"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"充電が完了しました"</string>
@@ -416,20 +427,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPNを切断"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"この端末は <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>で管理されています。"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> は <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>を使用してこの端末を管理しています。"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"管理者は、この端末に関連付けられた設定、コーポレート アクセス、アプリ、データと、端末の位置情報を監視および管理できます。"</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"詳細"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"「<xliff:g id="VPN_APP">%1$s</xliff:g>」に接続しています。このアプリはあなたのネットワーク アクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN 設定を開く"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"管理者がネットワーク ログを有効にしているため、この端末のトラフィックは監視されています。\n\n詳しくは、管理者にお問い合わせください。"</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"アプリにVPN接続の設定を許可しました。\n\nこのアプリはあなたの端末やネットワークアクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"この仕事用プロファイルは<xliff:g id="ORGANIZATION">%1$s</xliff:g>によって管理されています。\n\n管理者はあなたのネットワークアクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。\n\n詳しくは管理者にお問い合わせください。\n\nVPNにも接続しているため、VPNもネットワークアクティビティを監視できます。"</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"<xliff:g id="APPLICATION">%1$s</xliff:g>に接続しています。このアプリはあなたのネットワークアクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g>に接続しています。このアプリはあなたの個人のネットワークアクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"「<xliff:g id="APPLICATION">%1$s</xliff:g>」に接続しています。このアプリはあなたの個人のネットワーク アクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"この仕事用プロファイルは<xliff:g id="ORGANIZATION">%1$s</xliff:g>によって管理され、<xliff:g id="APPLICATION">%2$s</xliff:g>に接続しています。このアプリはあなたの仕事のネットワークアクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。\n\n詳しくは管理者にお問い合わせください。"</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"この仕事用プロファイルは<xliff:g id="ORGANIZATION">%1$s</xliff:g>によって管理され、<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>に接続しています。このアプリはあなたの仕事のネットワークアクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。\n\n<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>にも接続しているため、個人のネットワークアクティビティも監視できます。"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"手動でロックを解除するまでロックされたままとなります"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"通知をすばやく確認できます"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index ca8978d..9e65155 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> გაფრთხილება"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"სამსახურის რეჟიმი"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ღამის განათება"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC გათიშულია"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC ჩართულია"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ბოლოს გამოყენებული ერთეულები არ არის"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ყველაფერი გასუფთავდა"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"აპლიკაციის შესახებ"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ჰორიზონტალური გაყოფა"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ვერტიკალური გაყოფა"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ინდივიდუალური გაყობა"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"დახურვა"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"გახსნა"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ეკრანის გაყოფა ზემოთ"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"ეკრანის გაყოფა მარცხნივ"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"ეკრანის გაყოფა მარჯვნივ"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"დატენილია"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN-ის გათიშვა"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"თქვენს მოწყობილობას მართავს <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> იყენებს <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>-ს თქვენი მოწყობილობის სამართავად."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"თქვენს ადმინისტრატორს შეუძლია მოწყობილობასთან დაკავშირებული პარამეტრების, კორპორატიული წვდომის, აპებისა და მონაცემების, მათ შორის, თქვენი მოწყობილობის მდებარეობის ინფორმაციის, მონიტორინგი და მართვა."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"თქვენს ადმინისტრატორს შეუძლია მოწყობილობასთან დაკავშირებული პარამეტრების, კორპორაციული წვდომის, აპებისა და მონაცემების (მათ შორის, თქვენი მოწყობილობის მდებარეობის ინფორმაციის) მონიტორინგი და მართვა."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"შეიტყვეთ მეტი"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"თქვენ დაუკავშირდით <xliff:g id="VPN_APP">%1$s</xliff:g>-ს, რომელსაც თქვენი ქსელის აქტივობის, მათ შორის, ელფოსტის, აპებისა და ვებსაიტების, მონიტორინგი შეუძლია."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN-ის პარამეტრების გახსნა"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"თქვენმა ადმინისტრატორმა ქსელის ჟურნალირება ჩართო, რომელიც თქვენი მოწყობილობის ტრაფიკის მონიტორინგს ახორციელებს.\n\nდამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"თქვენმა ადმინისტრატორმა ქსელის ჟურნალირება ჩართო, რომელიც თქვენი მოწყობილობის ტრაფიკის მონიტორინგს ახორციელებს.\n\nდამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"თქვენ მიეცით ნებართვა აპს, დააყენოს VPN კავშირი.\n\nამ აპს შეუძლია თქვენი მოწყობილობის და ქსელის აქტივობის, მათ შორის, ელფოსტის, აპებისა და ვებსაიტების მონიტორინგი."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"თქვენს სამუშაო პროფილს მართავს <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nთქვენს ადმინისტრატორს შეუძლია თქვენი ქსელის აქტივობის მონიტორინგი, მათ შორის, ელფოსტის, აპებისა და ვებ-საიტების.\n\nდამატებითი ინფორმაციისთვის, დაუკავშირდით თქვენს ადმინისტრატორს.\n\nთქვენ ასევე დაკავშირებული ხართ VPN-თან, რომელსაც შეუძლია თქვენი ქსელის აქტივობის მონიტორინგი."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"თქვენს სამსახურის პროფილს მართავს <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nთქვენს ადმინისტრატორს შეუძლია თქვენი ქსელის აქტივობის (მათ შორის, ელფოსტის, აპებისა და ვებსაიტების) მონიტორინგი.\n\nდამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს.\n\nგარდა ამისა, თქვენ დაკავშირებული ხართ VPN-თან, რომელსაც ასევე შეუძლია თქვენი ქსელის აქტივობის მონიტორინგი."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"თქვენ დაუკავშირდით <xliff:g id="APPLICATION">%1$s</xliff:g>-ს, რომელსაც შეუძლია თქვენი ქსელის აქტივობის, მათ შორის, ელფოსტის, აპებისა და ვებსაიტების მონიტორინგი."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"თქვენ დაუკავშირდით <xliff:g id="APPLICATION">%1$s</xliff:g>-ს, რომელსაც შეუძლია თქვენი პირადი ქსელის აქტივობის, მათ შორის, ელფოსტის, აპებისა და ვებსაიტების მონიტორინგი."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"თქვენ დაუკავშირდით <xliff:g id="APPLICATION">%1$s</xliff:g>-ს, რომელსაც თქვენი პირადი ქსელის აქტივობის მონიტორინგი შეუძლია, მათ შორის, ელფოსტის, აპებისა და ვებსაიტების."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"თქვენი სამუშაო პროფილი <xliff:g id="ORGANIZATION">%1$s</xliff:g>-ის მიერ იმართება. ის დაკავშირებულია <xliff:g id="APPLICATION">%2$s</xliff:g>-თან, რომელსაც შეუძლია თქვენი სამსახურის ქსელის აქტივობის, მათ შორის, ელფოსტის, აპებისა და ვებსაიტების მონიტორინგი.\n\nდამატებითი ინფორმაციისთვის მიმართეთ თქვენს ადმინისტრატორს."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"თქვენს სამსახურის პროფილს მართავს <xliff:g id="ORGANIZATION">%1$s</xliff:g>. ის დაკავშირებულია <xliff:g id="APPLICATION">%2$s</xliff:g>-თან, რომელსაც თქვენი ქსელის აქტივობის (მათ შორის, ელფოსტის, აპებისა და ვებსაიტების) მონიტორინგი შეუძლია.\n\nდამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"თქვენი სამუშაო პროფილი <xliff:g id="ORGANIZATION">%1$s</xliff:g>-ის მიერ იმართება. ის დაკავშირებულია <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>-თან, რომელსაც შეუძლია თქვენი სამსახურის ქსელის აქტივობის, მათ შორის, ელფოსტის, აპებისა და ვებსაიტების მონიტორინგი.\n\nასევე, დაკავშირებული ხართ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>-თან, რომელსაც შეუძლია თქვენი პირადი ქსელის აქტივობის მონიტორინგი."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"მოწყობილობის დარჩება ჩაკეტილი, სანამ ხელით არ გახსნით"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"შეტყობინებების უფრო სწრაფად მიღება"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 9faa5dc..be32860 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> туралы ескерту"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Жұмыс режимі"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Түнгі жарық"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC өшірулі"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC қосулы"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Жақындағы элементтер жоқ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Сіз барлығын өшірдіңіз"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Қолданба туралы ақпарат"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Бөлінген көлденең"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Бөлінген тік"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Бөлінген теңшелетін"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Қабылдамау"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Ашу"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Экранды жоғарыға қарай бөлу"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Экранды солға қарай бөлу"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Экранды оңға қарай бөлу"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Зарядталды"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN желісін ажырату"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Құрылғыңызды <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> басқарады."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> құрылғыны <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> қолданбасымен басқарады."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Әкімші параметрлерді, корпоративтік кіру құқығын, қолданбаларды, құрылғыға қатысты деректерді, құрылғының орналасқан жер ақпаратын бақылай және басқара алады."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Әкімші параметрлерді, корпоративтік кіру рұқсаттарын, қолданбаларды, құрылғыға қатысты деректерді, құрылғының орналасқан жер ақпаратын бақылай және басқара алады."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Толығырақ"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Желідегі әрекеттерді, соның ішінде электрондық хабарларды, қолданбаларды және вебсайттарды бақылайтын <xliff:g id="VPN_APP">%1$s</xliff:g> қолданбасына қосылдыңыз."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN параметрлерін ашу"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Әкімші құрылғыдағы трафикті қадағалау үшін желі журналын жүргізуді қосып қойған.\n\nТолығырақ ақпарат алу үшін әкімшімен хабарласыңыз."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Әкімші құрылғыдағы трафикті қадағалау үшін желі журналын жүргізуді қосып қойған.\n\nТолығырақ ақпарат алу үшін әкімшімен хабарласыңыз."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Қолданбаға VPN байланысын орнату рұқсатын бердіңіз.\n\nБұл қолданба құрылғыңызды және желідегі белсенділігіңізді, соның ішінде электрондық пошталарды, қолданбаларды және веб-сайттарды бақылай алады."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Жұмыс профиліңізді <xliff:g id="ORGANIZATION">%1$s</xliff:g> басқарады.\n\nӘкімші желідегі белсенділігіңізді, соның ішінде электрондық пошталарды, қолданбаларды және веб-сайттарды бақылай алады.\n\nҚосымша ақпарат алу үшін әкімшіге хабарласыңыз.\n\nСондай-ақ сіз желідегі белсенділігіңізді бақылай алатын VPN желісіне қосылғансыз."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Жұмыс профиліңізді <xliff:g id="ORGANIZATION">%1$s</xliff:g> басқарады.\n\nӘкімші желідегі белсенділігіңізді, соның ішінде электрондық пошталарды, қолданбаларды және вебсайттарды бақылай алады.\n\nҚосымша ақпарат алу үшін әкімшіге хабарласыңыз.\n\nСондай-ақ сіз желідегі белсенділігіңізді бақылай алатын VPN желісіне қосылғансыз."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Сіз желідегі белсенділігіңізді, соның ішінде электрондық пошталарды, қолданбаларды және веб-сайттарды бақылай алатын <xliff:g id="APPLICATION">%1$s</xliff:g> қолданбасына қосылғансыз."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Сіз жеке желідегі белсенділігіңізді, соның ішінде электрондық пошталарды, қолданбаларды және веб-сайттарды бақылай алатын <xliff:g id="APPLICATION">%1$s</xliff:g> қолданбасына қосылғансыз."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Жеке желідегі әрекеттеріңізді, соның ішінде электрондық пошта хабарларын, қолданбаларды және вебсайттарды бақылай алатын <xliff:g id="APPLICATION">%1$s</xliff:g> қолданбасына қосылғансыз."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Жұмыс профиліңізді <xliff:g id="ORGANIZATION">%1$s</xliff:g> басқарады. Ол жұмыс кезінде желідегі белсенділігіңізді, соның ішінде электрондық пошталарды, қолданбаларды және веб-сайттарды бақылай алатын <xliff:g id="APPLICATION">%2$s</xliff:g> қолданбасына қосылған.\n\nҚосымша ақпарат алу үшін әкімшіге хабарласыңыз."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Жұмыс профиліңізді <xliff:g id="ORGANIZATION">%1$s</xliff:g> басқарады. Ол жұмыс барысындағы желідегі белсенділігіңізді, соның ішінде электрондық хабарларды, қолданбаларды және вебсайттарды бақылай алатын <xliff:g id="APPLICATION">%2$s</xliff:g> қолданбасына қосылған.\n\nҚосымша ақпарат алу үшін әкімшіге хабарласыңыз."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Жұмыс профиліңізді <xliff:g id="ORGANIZATION">%1$s</xliff:g> басқарады. Ол желідегі белсенділігіңізді, соның ішінде электрондық пошталарды, қолданбаларды және веб-сайттарды бақылай алатын <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> қолданбасына қосылған.\n\nСондай-ақ сіз желідегі жеке белсенділігіңізді бақылай алатын <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> қолданбасына қосылғансыз."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Қолмен бекітпесін ашқанша құрылғы бекітілген күйде қалады"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Хабарландыруларды тезірек алу"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 562727b..3d87668 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ការព្រមាន"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"របៀបការងារ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ពន្លឺពេលយប់"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"មិនមានធាតុថ្មីៗទេ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"អ្នកបានជម្រះអ្វីៗទាំងអស់"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ព័ត៌មានកម្មវិធី"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"បំបែកផ្តេក"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"បំបែកបញ្ឈរ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"បំបែកផ្ទាល់ខ្លួន"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"បដិសេធ"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"បើក"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"បំបែកអេក្រង់ទៅខាងលើ"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"បំបែកអេក្រង់ទៅខាងឆ្វេង"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"បំបែកអេក្រង់ទៅខាងស្តាំ"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"បានបញ្ចូលថ្ម"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"ផ្ដាច់ VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"ឧបករណ៍របស់អ្នកគ្រប់គ្រងដោយ <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> ។"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> ប្រើប្រាស់ <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> ដើម្បីគ្រប់គ្រងឧបករណ៍របស់អ្នក។"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"អ្នកគ្រប់គ្រងរបស់អ្នកអាចតាមដាន និងគ្រប់គ្រងការកំណត់ ការចូលលក្ខណៈក្រុមហ៊ុន កម្មវិធី ទិន្នន័យពាក់ព័ន្ធនឹងឧបករណ៍របស់អ្នក និងព័ត៌មានទីតាំងឧបករណ៍របស់អ្នក។"</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"ស្វែងយល់បន្ថែម"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"អ្នកបានភ្ជាប់ទៅ <xliff:g id="VPN_APP">%1$s</xliff:g> ដែលអាចតាមដានសកម្មភាពក្នុងបណ្តាញរបស់អ្នក រួមទាំងអ៊ីមែល កម្មវិធី និងគេហទំព័រផងដែរ។"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"បើកការកំណត់ VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"អ្នកគ្រប់គ្រងរបស់អ្នកបានបើកការធ្វើកំណត់ហេតុបណ្តាញ ដែលវានឹងតាមដានចរាចរណ៍បណ្តាញនៅលើឧបករណ៍របស់អ្នក។\n\nសម្រាប់ព័ត៌មានបន្ថែម សូមទាក់ទងអ្នកគ្រប់គ្រងរបស់អ្នក។"</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"អ្នកបានអនុញ្ញាតឲ្យកម្មវិធីដំឡើងការតភ្ជាប់ VPN។\n\nកម្មវិធីនេះអាចឃ្លាំមើលឧបករណ៍ និងសកម្មភាពបណ្តាញរបស់អ្នក រាប់បញ្ចូលទាំងអ៊ីមែល កម្មវិធី និងគេហទំព័រ។"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"ប្រវត្តិការងាររបស់អ្នកត្រូវបានគ្រប់គ្រងដោយ <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nអ្នកគ្រប់គ្រងរបស់អ្នកមានលទ្ធភាពអាចឃ្លាំមើលសកម្មភាពបណ្តាញរបស់អ្នក រួមបញ្ចូលទាំងអ៊ីមែល កម្មវិធី គេហទំព័រ។\n\nសម្រាប់ព័ត៌មានបន្ថែម សូមទាក់ទងអ្នកគ្រប់គ្រងរបស់អ្នក។\n\nអ្នកក៏ត្រូវបានភ្ជាប់ជាមួួយ VPN ផងដែរ ដែលវាអាចឃ្លាំមើលសកម្មភាពបណ្តាញរបស់អ្នក។"</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"អ្នកត្រូវបានតភ្ជាប់ទៅ <xliff:g id="APPLICATION">%1$s</xliff:g> ដែលអាចឃ្លាំមើលសកម្មភាពបណ្តាញរបស់អ្នក រាប់បញ្ចូលទាំងអ៊ីមែល កម្មវិធី និងគេហទំព័រ។"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"អ្នកត្រូវបានតភ្ជាប់ទៅ <xliff:g id="APPLICATION">%1$s</xliff:g> ដែលអាចឃ្លាំមើលសកម្មភាពបណ្តាញរបស់អ្នក រាប់បញ្ចូលទាំងអ៊ីមែល កម្មវិធី និងគេហទំព័រ។"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"អ្នកត្រូវបានភ្ជាប់ទៅ <xliff:g id="APPLICATION">%1$s</xliff:g> ដែលអាចឃ្លាំមើលសកម្មភាពបណ្តាញរបស់អ្នក រាប់បញ្ចូលទាំងអ៊ីមែល កម្មវិធី និងគេហទំព័រ។"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"ប្រវត្តិរូបការងាររបស់អ្នកត្រូវបានគ្រប់គ្រងដោយ <xliff:g id="ORGANIZATION">%1$s</xliff:g>។ វាត្រូវបានតភ្ជាប់ទៅនឹង <xliff:g id="APPLICATION">%2$s</xliff:g> ដែលអាចឃ្លាំមើលសកម្មភាពបណ្តាញរបស់អ្នក រាប់បញ្ចូលទាំងអ៊ីមែល កម្មវិធី គេហទំព័រ។\n\nសម្រាប់ព័ត៌មានបន្ថែម សូមទាក់ទងអ្នកគ្រប់គ្រប់របស់អ្នក។"</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"ប្រវត្តិរូបការងាររបស់អ្នកត្រូវបានគ្រប់គ្រងដោយ <xliff:g id="ORGANIZATION">%1$s</xliff:g>។ វាត្រូវបានតភ្ជាប់ទៅនឹង <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ដែលអាចឃ្លាំមើលសកម្មភាពបណ្តាញរបស់អ្នក រាប់បញ្ចូលទាំងអ៊ីមែល កម្មវិធី គេហទំព័រ។\n\nអ្នកក៏ត្រូវបានតភ្ជាប់ផងដែរទៅនឹង <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ដែលអាចឃ្លាំមើលសកម្មភាពបណ្តាញផ្ទាល់ខ្លួនរបស់អ្នក។"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"ឧបករណ៍នឹងចាក់សោរហូតដល់អ្នកដោះសោដោយដៃ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ទទួលបានការជូនដំណឹងកាន់តែលឿន"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index a0e2808..f13b8fde 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ಎಚ್ಚರಿಕೆ"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"ಕೆಲಸದ ಮೋಡ್"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ನೈಟ್ ಲೈಟ್"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC ನಿಷ್ಕ್ರಿಯಗೊಂಡಿದೆ"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC ಸಕ್ರಿಯಗೊಂಡಿದೆ"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಐಟಂಗಳಿಲ್ಲ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ನೀವು ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿರುವಿರಿ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ಅಪ್ಲಿಕೇಶನ್ ಮಾಹಿತಿ"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ಅಡ್ಡಲಾಗಿ ವಿಭಜಿಸಿದ"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ಲಂಬವಾಗಿ ವಿಭಜಿಸಿದ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ಕಸ್ಟಮ್ ವಿಭಜಿಸಿದ"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"ವಜಾಗೊಳಿಸಿ"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"ತೆರೆಯಿರಿ"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ಮೇಲ್ಭಾಗಕ್ಕೆ ಪರದೆಯನ್ನು ವಿಭಜಿಸಿ"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"ಎಡಕ್ಕೆ ಪರದೆಯನ್ನು ವಿಭಜಿಸಿ"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"ಬಲಕ್ಕೆ ಪರದೆಯನ್ನು ವಿಭಜಿಸಿ"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN ಸಂಪರ್ಕಕಡಿತಗೊಳಿಸಿ"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"ನಿಮ್ಮ ಸಾಧನವನ್ನು <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> ನಿಂದ ನಿರ್ವಹಿಸಲಾಗಿದೆ."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ನಿರ್ವಹಿಸಲು <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> ಅನ್ನು ಬಳಸುತ್ತದೆ."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ಸೆಟ್ಟಿಂಗ್ಗಳು, ಕಾರ್ಪೊರೇಟ್ ಪ್ರವೇಶ, ಅಪ್ಲಿಕೇಶನ್ಗಳು, ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಡೇಟಾ ಮತ್ತು ನಿಮ್ಮ ಸಾಧನದ ಸ್ಥಳ ಮಾಹಿತಿಯನ್ನು ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದು ಮತ್ತು ನಿರ್ವಹಿಸಬಹುದು."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"ನಿರ್ವಾಹಕರು ಸೆಟ್ಟಿಂಗ್ಗಳು, ಕಾರ್ಪೊರೇಟ್ ಪ್ರವೇಶ, ಅಪ್ಲಿಕೇಶನ್ಗಳು, ನಿಮ್ಮ ಸಾಧನದ ಡೇಟಾ ಮತ್ತು ಸ್ಥಳ ಮಾಹಿತಿಯ ನಿಗಾವಣೆ ಮತ್ತು ನಿರ್ವಹಣೆ ಮಾಡಬಹುದು."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"ನೀವು ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದಾದ, <xliff:g id="VPN_APP">%1$s</xliff:g> ಗೆ ನೀವು ಸಂಪರ್ಕಗೊಂಡಿರುವಿರಿ."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ತೆರೆಯಿರಿ"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಟ್ರಾಫಿಕ್ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಲು ನೆಟ್ವರ್ಕ್ ಲಾಗಿನ್ ಮಾಡುವಿಕೆಯನ್ನು ಆನ್ ಮಾಡಿದ್ದಾರೆ.\n\nಹೆಚ್ಚಿನ ಮಾಹಿತಿಗೆ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ನೆಟ್ವರ್ಕ್ ಲಾಗಿಂಗ್ ಆನ್ ಮಾಡಿದ್ದಾರೆ. ಇದು ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಟ್ರಾಫಿಕ್ ಮೇಲೆ ನಿಗಾ ಇರಿಸುತ್ತದೆ.\n\nಹೆಚ್ಚಿನ ಮಾಹಿತಿಗೆ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"ನೀವು VPN ಸಂಪರ್ಕ ಹೊಂದಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿ ನೀಡಿರುವಿರಿ.\n\nಈ ಅಪ್ಲಿಕೇಶನ್ ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದು."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"ನಿಮ್ಮ ಪ್ರೊಫೈಲ್ ಅನ್ನು <xliff:g id="ORGANIZATION">%1$s</xliff:g> ಮೂಲಕ ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ.\n\nನಿಮ್ಮ ನಿರ್ವಾಹಕರು ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡುವ ಸಾಮರ್ಥ್ಯವನ್ನು ಹೊಂದಿದ್ದಾರೆ.\n\nಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ.\n\nನಿಮ್ಮ ವೈಯಕ್ತಿಕ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯನ್ನು ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದಾದ VPN ಗೆ ಕೂಡಾ ನೀವು ಸಂಪರ್ಕಗೊಂಡಿರುವಿರಿ."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಅನ್ನು <xliff:g id="ORGANIZATION">%1$s</xliff:g> ನಿರ್ವಹಿಸುತ್ತಿದೆ.\n\nಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳೂ ಸೇರಿದಂತೆ ನಿಮ್ಮ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲೆ ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ನಿಗಾ ಇರಿಸಬಲ್ಲರು.\n\nಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ.\n\nಅಲ್ಲದೇ, ನಿಮ್ಮ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ನಿಗಾ ವಹಿಸುವ VPN ಗೂ ಸಹ ನೀವು ಸಂಪರ್ಕಗೊಂಡಿರುವಿರಿ."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"ನೀವು ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದಾದ <xliff:g id="APPLICATION">%1$s</xliff:g> ಗೆ ಸಂಪರ್ಕಗೊಂಡಿರುವಿರಿ."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"ನೀವು ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳು ಸೇರಿದಂತೆ ನಿಮ್ಮ ವೈಯಕ್ತಿಕ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದಾದ <xliff:g id="APPLICATION">%1$s</xliff:g> ಗೆ ಸಂಪರ್ಕಗೊಂಡಿರುವಿರಿ."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"ನೀವು ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು, ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ವೈಯಕ್ತಿಕ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದಾದ <xliff:g id="APPLICATION">%1$s</xliff:g> ಗೆ ಸಂಪರ್ಕಗೊಂಡಿರುವಿರಿ."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಅನ್ನು <xliff:g id="ORGANIZATION">%1$s</xliff:g> ಮೂಲಕ ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ. ಇದು ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು, ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ಕೆಲಸದ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದಾದ <xliff:g id="APPLICATION">%2$s</xliff:g> ಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ.\n\nಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಅನ್ನು <xliff:g id="ORGANIZATION">%1$s</xliff:g> ನಿರ್ವಹಿಸುತ್ತಿದೆ. ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳೂ ಸೇರಿದಂತೆ ನಿಮ್ಮ ಕೆಲಸದ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲೆ ನಿಗಾ ಇರಿಸಬಲ್ಲ <xliff:g id="APPLICATION">%2$s</xliff:g> ಗೆ ಇದು ಸಂಪರ್ಕ ಹೊಂದಿದೆ.\n\nಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಅನ್ನು <xliff:g id="ORGANIZATION">%1$s</xliff:g> ಮೂಲಕ ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ. ಇದು ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು, ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ಕೆಲಸದ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದಾದ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ಗೆ ಸಂಪರ್ಕಿತಗೊಂಡಿದೆ.\n\nನೀವು ಕೂಡಾ ವೈಯಕ್ತಿಕ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದಾದ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ಗೆ ಸಂಪರ್ಕಿತಗೊಂಡಿರುವಿರಿ."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"ನೀವಾಗಿಯೇ ಅನ್ಲಾಕ್ ಮಾಡುವವರೆಗೆ ಸಾಧನವು ಲಾಕ್ ಆಗಿಯೇ ಇರುತ್ತದೆ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ವೇಗವಾಗಿ ಅಧಿಸೂಚನೆಗಳನ್ನು ಪಡೆದುಕೊಳ್ಳಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index de4e6ed..fc60f7a 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -323,6 +323,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> 경고"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"작업 모드"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"야간 조명"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"최근 항목이 없습니다."</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"모든 항목을 삭제했습니다."</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"애플리케이션 정보"</string>
@@ -336,6 +342,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"수평 분할"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"수직 분할"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"맞춤 분할"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"닫기"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"열기"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"위쪽으로 화면 분할"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"왼쪽으로 화면 분할"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"오른쪽으로 화면 분할"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"충전됨"</string>
@@ -416,20 +427,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN 연결 해제"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>에서 관리하는 기기입니다."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>이(가) <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>을(를) 사용하여 내 기기를 관리합니다."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"관리자는 설정, 기업 액세스, 앱, 기기 관련 데이터 및 기기의 위치 정보를 모니터링하고 관리할 수 있습니다."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"자세히 알아보기"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"<xliff:g id="VPN_APP">%1$s</xliff:g>에 연결되었습니다. 이 앱은 이메일, 앱, 웹사이트와 같은 내 네트워크 활동을 모니터링할 수 있습니다."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"공개 VPN 설정"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"관리자가 기기 트래픽을 모니터링하는 네트워크 로깅을 사용 설정했습니다.\n\n자세한 정보는 관리자에게 문의하세요."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN 연결을 설정할 수 있는 권한을 앱에 부여했습니다.\n\n이 앱에서 이메일, 앱, 웹사이트와 같은 내 네트워크 활동 및 기기를 모니터링할 수 있습니다."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"직장 프로필은 <xliff:g id="ORGANIZATION">%1$s</xliff:g>에서 관리합니다.\n\n관리자는 이메일, 앱, 웹사이트와 같은 네트워크 활동을 모니터링할 수 있습니다.\n\n자세한 내용은 관리자에게 문의하세요.\n\n또한 VPN에 연결되어 있으며 여기에서 내 네트워크 활동을 모니터링할 수 있습니다."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"<xliff:g id="APPLICATION">%1$s</xliff:g>에 연결되었습니다. 이 앱은 이메일, 앱, 웹사이트와 같은 내 네트워크 활동을 모니터링할 수 있습니다."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g>에 연결되었습니다. 이 앱은 이메일, 앱, 웹사이트와 같은 내 네트워크 활동을 모니터링할 수 있습니다."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g>에 연결되었습니다. 이 앱은 이메일, 앱, 웹사이트와 같은 내 개인 네트워크 활동을 모니터링할 수 있습니다."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"직장 프로필은 <xliff:g id="ORGANIZATION">%1$s</xliff:g>에서 관리합니다. 이는 <xliff:g id="APPLICATION">%2$s</xliff:g>에 연결되어 있으며 여기에서 이메일, 앱, 웹사이트와 같은 직장 네트워크 활동을 모니터링할 수 있습니다.\n\n자세한 내용은 관리자에게 문의하세요."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"직장 프로필은 <xliff:g id="ORGANIZATION">%1$s</xliff:g>에서 관리합니다. 이는 <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>에 연결되어 있으며 여기에서 이메일, 앱, 웹사이트와 같은 직장 네트워크 활동을 모니터링할 수 있습니다.\n\n또한 <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>에 연결되어 있으며, 여기에서 내 개인 네트워크 활동을 모니터링할 수 있습니다."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"수동으로 잠금 해제할 때까지 기기가 잠금 상태로 유지됩니다."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"알림을 더욱 빠르게 받기"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 89790ee..90ae2c6 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> эскертүү"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Иштөө режими"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Түнкү жарык"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC өчүрүлгөн"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC иштетилген"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Акыркы колдонмолор жок"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Баарын тазаладыңыз"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Колдонмо жөнүндө маалымат"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Туурасынан бөлүү"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Тигинен бөлүү"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Ыңгайлаштырылган бөлүү"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Этибарга албоо"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Ачуу"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Экранды өйдө жакка бөлүү"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Экранды сол жакка бөлүү"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Экранды оң жакка бөлүү"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Кубатталды"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN\'ди ажыратуу"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Түзмөгүңүз <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> тарабынан башкарылат."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"Түзмөгүңүздү башкаруу үчүн <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> уюму <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> колдонмосун колдонот."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Администраторуңуз жөндөөлөрдү, корпоративдик кирүү мүмкүнчүлүгүн, колдонмолорду, уруксаттарды жана ушул түзмөкө байланыштуу дайындарды, ошондой эле түзмөгүңүздүн жайгашкан жери тууралуу маалыматты көзөмөлдөп жана башкара алат."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Администраторуңуз жөндөөлөрдү, корпоративдик кирүү мүмкүнчүлүгүн, колдонмолорду, уруксаттарды жана ушул түзмөкө байланыштуу дайындарды, ошондой эле түзмөгүңүздүн жайгашкан жери тууралуу маалыматты көзөмөлдөп жана башкара алат."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Кеңири маалымат"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Электрондук почта, колдонмолор жана вебсайттар сыяктуу тармактагы аракеттериңизди тескей турган <xliff:g id="VPN_APP">%1$s</xliff:g> колдонмосуна туташып турасыз."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN жөндөөлөрүн ачуу"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Администраторуңуз тармактын таржымалын алууну иштетти, андыктан түзмөгүңүздөгү трафик көзөмөлгө алынды.\n\nКеңири маалымат алуу үчүн администраторуңузга кайрылыңыз."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Администраторуңуз тармактын таржымалын алууну иштетти, андыктан түзмөгүңүздөгү трафик көзөмөлгө алынды.\n\nКеңири маалымат алуу үчүн администраторуңузга кайрылыңыз."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Колдонмого VPN туташуусун орнотууга уруксат бердиңиз.\n\nБул колдонмо түзмөгүңүздү жана электрондук почталар, колдонмолор жана вебсайттар сыяктуу тармактагы аракеттериңизди көзөмөлдөй алат."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Жумуш профилиңизди <xliff:g id="ORGANIZATION">%1$s</xliff:g> башкарат.\n\nАдминистраторуңуз электрондук почталар, колдонмолор жана вебсайттар сыяктуу тармактагы аракеттериңизди тескей алат.\n\nКөбүрөөк маалымат алуу үчүн, администраторуңузга кайрылыңыз.\n\nМындан тышкары, тармактагы аракеттериңизди тескей турган VPN\'ге да туташып турасыз."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Жумуш профилиңизди <xliff:g id="ORGANIZATION">%1$s</xliff:g> башкарат.\n\nАдминистраторуңуздун тармактагы аракетиңизди, анын ичинде электрондук почталар, колдонмолор жана вебсайттарды көзөмөлдөө мүмкүнчүлүгү бар.\n\nКөбүрөөк маалымат үчүн, администраторуңузга кайрылыңыз.\n\nСиз тармактагы жеке аракетиңизди көзөмөлдөй турган VPN\'ге да туташкансыз."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Электрондук почта, колдонмолор жана вебсайттар сыяктуу тармактык аракеттерди көзөмөлдөй турган <xliff:g id="APPLICATION">%1$s</xliff:g> колдонмосуна туташып турасыз."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Электрондук почта, колдонмолор жана вебсайттар сыяктуу тармактагы жеке аракеттериңизди көзөмөлдөй турган <xliff:g id="APPLICATION">%1$s</xliff:g> колдонмосуна туташып турасыз."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Электрондук почта, колдонмолор жана вебсайттар сыяктуу тармактагы жеке аракеттериңизди тескей турган <xliff:g id="APPLICATION">%1$s</xliff:g> колдонмосуна туташып турасыз."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Жумуш профилиңизди <xliff:g id="ORGANIZATION">%1$s</xliff:g> башкарат. Ал электрондук почта, колдонмолор жана вебсайттар сыяктуу жумуш тармагыңыздагы аракеттерди көзөмөлдөй турган <xliff:g id="APPLICATION">%2$s</xliff:g> менен туташкан.\n\nКөбүрөөк маалымат алуу үчүн администраторуңузга кайрылыңыз."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Жумуш профилиңизди <xliff:g id="ORGANIZATION">%1$s</xliff:g> башкарат. Ал электрондук почта, колдонмолор жана вебсайттар сыяктуу жумуш тармагыңыздагы аракеттерди көзөмөлдөй турган <xliff:g id="APPLICATION">%2$s</xliff:g> менен туташкан.\n\nКөбүрөөк маалымат алуу үчүн администраторуңузга кайрылыңыз."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Жумуш профилиңизди <xliff:g id="ORGANIZATION">%1$s</xliff:g> башкарат. Ал электрондук почта, колдонмолор жана вебсайттар сыяктуу жумуш тармагыңыздагы аракеттерди көзөмөлдөй турган <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> менен туташкан.\n\nМындан тышкары, тармактагы жеке аракеттериңизди көзөмөлдөгөн <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> колдонмосуна туташып турасыз."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Түзмөктүн кулпусу кол менен ачылмайынча кулпуланган бойдон алат"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Эскертмелерди тезирээк алуу"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index da8a467..c639fa3 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"ຄຳເຕືອນ <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"ໂໝດການເຮັດວຽກ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ແສງກາງຄືນ"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC is disabled"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC is enabled"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ບໍ່ມີລາຍການຫຼ້າສຸດ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ທ່ານລຶບລ້າງທຸກຢ່າງແລ້ວ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ຂໍ້ມູນແອັບພລິເຄຊັນ"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ການແຍກລວງຂວາງ"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ການແຍກລວງຕັ້ງ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ການແຍກກຳນົດເອງ"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"ປິດໄວ້"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"ເປີດ"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Split screen to the top"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Split screen to the left"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Split screen to the right"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"ສາກເຕັມແລ້ວ."</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"ຕັດການເຊື່ອມຕໍ່ VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"ອຸປະກອນຂອງທ່ານແມ່ນຈັດການໂດຍ <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> ໃຊ້ <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> ເພື່ອຈັດການອຸປະກອນຂອງທ່ານ."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"ຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານສາມາດຕິດຕາມ ແລະ ຈັດການການຕັ້ງຄ່າ, ການເຂົ້າເຖິງອົງກອນ, ແອັບ, ຂໍ້ມູນທີ່ເຊື່ອມໂຍງກັບອຸປະກອນຂອງທ່ານແລະ ຂໍ້ມູນສະຖານທີ່ຂອງອຸປະກອນທ່ານໄດ້."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"ຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານສາມາດຕິດຕາມ ແລະ ຈັດການການຕັ້ງຄ່າ, ການເຂົ້າເຖິງອົງກອນ, ແອັບ, ຂໍ້ມູນທີ່ເຊື່ອມໂຍງກັບອຸປະກອນຂອງທ່ານແລະ ຂໍ້ມູນສະຖານທີ່ຂອງອຸປະກອນທ່ານໄດ້."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"ສຶກສາເພີ່ມເຕີມ"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"ທ່ານເຊື່ອມຕໍ່ກັບ <xliff:g id="VPN_APP">%1$s</xliff:g> ແລ້ວ, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍ, ຮວມທັງອີເມວ, ແອັບ ແລະ ເວັບໄຊຕ່າງໆໄດ້."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"ເປີດການຕັ້ງຄ່າ VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"ຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານໄດ້ເປີດໃຊ້ການບັນທຶກເຄືອຂ່າຍໄວ້, ເຊິ່ງຈະກວດສອບທຣາບຟິກໃນອຸປະກອນຂອງທ່ານ.\n\nສຳລັບຂໍ້ມູນເພີ່ມເຕີມໃຫ້ຕິດຕໍ່ຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information, contact your admin."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"ທ່ານໄດ້ອະນຸຍາດໃຫ້ແອັບຕັ້ງການເຊື່ອມຕໍ່ VPN.\n\nແອັບນີ້ສາມາດຕິດຕາມການເຄື່ອນໄຫວຂອງອຸປະກອນ ແລະເຄືອຂ່າຍຂອງທ່ານ ເຊິ່ງລວມທັງອີເມວ, ແອັບ ແລະເວັບໄຊທ໌."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"ໂປຣໄຟລ໌ວຽກຂອງທ່ານຖືກຄຸ້ມຄອງໂດຍ <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nຜູ້ຄວບຄຸມຂອງທ່ານສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍຂອງທ່ານໄດ້ ລວມທັງອີເມວ, ແອັບ, ແລະເວັບໄຊທ໌.\n\nສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ຕິດຕໍ່ຜູ້ຄວບຄຸມຂອງທ່ານ.\n\nທ່ານຍັງເຊື່ອມຕໍ່ກັບ VPN, ເຊິ່ງສາມາດຕິດຕາມກິດຈະກຳເຄືອຂ່າຍຂອງທ່ານ."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nYour admin is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your admin.\n\nYou\'re also connected to a VPN, which can monitor your network activity."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"ທ່ານເຊື່ອມຕໍ່ກັບ <xliff:g id="APPLICATION">%1$s</xliff:g>, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍຂອງທ່ານ ລວມທັງອີເມວ, ແອັບ ແລະເວັບໄຊທ໌."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"ທ່ານເຊື່ອມຕໍ່ກັບ <xliff:g id="APPLICATION">%1$s</xliff:g>, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍສ່ວນຕົວຂອງທ່ານ ລວມທັງອີເມວ, ແອັບ ແລະເວັບໄຊທ໌."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"ທ່ານເຊື່ອມຕໍ່ກັບ <xliff:g id="APPLICATION">%1$s</xliff:g> ແລ້ວ, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍສ່ວນຕົວຂອງທ່ານ ຮວມທັງອີເມວ, ແອັບ ແລະເວັບໄຊໄດ້."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກຂອງທ່ານຖືກຄວບຄຸມໂດຍ <xliff:g id="ORGANIZATION">%1$s</xliff:g>. ມັນຖືກເຊື່ອມຕໍ່ກັບ <xliff:g id="APPLICATION">%2$s</xliff:g>, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍບ່ອນເຮັດວຽກຂອງທ່ານ ລວມທັງອີເມວ, ແອັບ ແລະເວັບໄຊທ໌.\n\nສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ໃຫ້ຕິດຕໍ່ຜູ້ບໍລິຫານຂອງທ່ານ."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. It is connected to <xliff:g id="APPLICATION">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nFor more information, contact your admin."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກຂອງທ່ານຖືກຈັດການໂດຍ <xliff:g id="ORGANIZATION">%1$s</xliff:g>. ມັນເຊື່ອມຕໍ່ກັບ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍບ່ອນເຮັດວຽກຂອງທ່ານ ລວມທັງອີເມວ, ແອັບ ແລະເວັບໄຊທ໌.\n\nທ່ານເຊື່ອມຕໍ່ກັບ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍສ່ວນຕົວຂອງທ່ານ."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ຮັບເອົາການແຈ້ງເຕືອນໄວຂຶ້ນ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index b2ead25..a40bcb6 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -325,6 +325,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> įspėjimas"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Darbo režimas"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nakties šviesa"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"ALR"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"ALR išjungtas"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"ALR įjungtas"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nėra jokių naujausių elementų"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Viską išvalėte"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Programos informacija"</string>
@@ -338,6 +341,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horizontalus skaidymas"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikalus skaidymas"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Tinkintas skaidymas"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Atsisakyti"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Atidaryti"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Skaidyti ekraną į viršų"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Skaidyti ekraną į kairę"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Skaidyti ekraną į dešinę"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Įkrautas"</string>
@@ -418,20 +426,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Atjungti VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Įrenginį tvarko „<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>“."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"„<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>“ naudoja „<xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>“ įrenginiui tvarkyti."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administr. gali stebėti ir tvark. nustat., įmonės prieigos par., progr., su įreng. susietus duomenis ir įreng. vietovės inform."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Administrat. gali stebėti ir tvark. nustat., įmonės prieigos par., progr., su įreng. susietus duomenis ir įreng. vietovės inform."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Sužinoti daugiau"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Esate prisijungę prie programos „<xliff:g id="VPN_APP">%1$s</xliff:g>“, kuri gali stebėti tinklo veiklą, įskaitant el. laiškus, programas ir svetaines."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Atidaryti VPN nustatymus"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administratorius įjungė tinklo duomenų įrašymą į žurnalą. Įjungus šią funkciją stebimas srautas jūsų įrenginyje.\n\nJei reikia daugiau informacijos, susisiekite su administratoriumi."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Administratorius įjungė tinklo duomenų įrašymą į žurnalą. Įjungus šią funkciją stebimas srautas jūsų įrenginyje.\n\nJei reikia daugiau informacijos, susisiekite su administratoriumi."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Suteikėte programai leidimą nustatyti VPN ryšį.\n\nŠi programa gali stebėti įrenginio ir tinklo veiklą, įskaitant el. laiškus, programas ir svetaines."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Jūsų darbo profilį tvarko „<xliff:g id="ORGANIZATION">%1$s</xliff:g>“.\n\nAdministratorius gali stebėti tinklo veiklą, įskaitant el. laiškus, programas ir svetaines.\n\nDaugiau informacijos galite gauti susisiekę su administratoriumi.\n\nBe to, esate prisijungę prie VPN, kuris gali stebėti tinklo veiklą."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Jūsų darbo profilį tvarko „<xliff:g id="ORGANIZATION">%1$s</xliff:g>“.\n\nJūsų administratorius gali stebėti jūsų tinklo veiklą, įskaitant el. laiškus, programas ir svetaines.\n\nJei reikia daugiau informacijos, susisiekite su administratoriumi.\n\nTaip pat esate prisijungę prie VPN, kuris gali stebėti jūsų tinklo veiklą."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Esate prisijungę prie programos „<xliff:g id="APPLICATION">%1$s</xliff:g>“, kuri gali stebėti tinklo veiklą, įskaitant el. laiškus, programas ir svetaines."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Esate prisijungę prie programos „<xliff:g id="APPLICATION">%1$s</xliff:g>“, kuri gali stebėti asmeninio profilio tinklo veiklą, įskaitant el. laiškus, programas ir svetaines."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Esate prisijungę prie programos „<xliff:g id="APPLICATION">%1$s</xliff:g>“, kuri gali stebėti asmeninio tinklo veiklą, įskaitant el. laiškus, programas ir svetaines."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Darbo profilį tvarko „<xliff:g id="ORGANIZATION">%1$s</xliff:g>“. Jis susietas su programa „<xliff:g id="APPLICATION">%2$s</xliff:g>“, kuri gali stebėti darbo profilio tinklo veiklą, įskaitant el. laiškus, programas ir svetaines.\n\nDaugiau informacijos galite gauti susisiekę su administratoriumi."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Jūsų darbo profilį tvarko „<xliff:g id="ORGANIZATION">%1$s</xliff:g>“. Jis susietas su programa „<xliff:g id="APPLICATION">%2$s</xliff:g>“, kuri negali stebėti jūsų tinklo veiklos, įskaitant el. laiškus, programas ir svetaines.\n\nJei reikia daugiau informacijos, susisiekite su administratoriumi."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Darbo profilį tvarko „<xliff:g id="ORGANIZATION">%1$s</xliff:g>“. Jis susietas su programa „<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>“, kuri gali stebėti darbo profilio tinklo veiklą, įskaitant el. laiškus, programas ir svetaines.\n\nTaip pat esate prisijungę prie programos „<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>“, kuri gali stebėti asmeninio profilio tinklo veiklą."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Įrenginys liks užrakintas, kol neatrakinsite jo neautomatiniu būdu"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Greičiau gaukite pranešimus"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 375b75e..8ce3890 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -323,6 +323,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> brīdinājums"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Darba režīms"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nakts režīms"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Nav nesenu vienumu"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Visi uzdevumi ir notīrīti"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informācija par lietojumprogrammu"</string>
@@ -336,6 +342,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horizontāls dalījums"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikāls dalījums"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Pielāgots dalījums"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Noraidīt"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Atvērt"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Sadalīt ekrānu augšdaļā"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Sadalīt ekrānu kreisajā pusē"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Sadalīt ekrānu labajā pusē"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Akumulators uzlādēts"</string>
@@ -416,20 +427,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Atvienot VPN tīklu"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Jūsu ierīci pārvalda <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> izmanto lietotni <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> jūsu ierīces pārvaldībai."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administrators var pārraudzīt un pārvaldīt iestatījumus, korporatīvo piekļuvi, lietotnes, ierīces datus un informāciju par ierīces atrašanās vietu."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Uzzināt vairāk"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Ir izveidots savienojums ar lietotni <xliff:g id="VPN_APP">%1$s</xliff:g>, kas var pārraudzīt jūsu darbības tīklā, tostarp e-pasta ziņojumus, lietotnes un vietnes."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Atvērt VPN iestatījumus"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administrators ir ieslēdzis tīkla reģistrēšanu, kuru izmanto, lai pārraudzītu datplūsmu jūsu ierīcē.\n\nLai iegūtu plašāku informāciju, sazinieties ar administratoru."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Jūs piešķīrāt lietotnei atļauju izveidot savienojumu ar VPN tīklu.\n\nŠī lietotne var pārraudzīt jūsu ierīcē un tīklā veiktās darbības, tostarp e-pasta ziņojumus, lietotnes un vietnes."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Jūsu darba profilu pārvalda <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nJūsu administrators var pārraudzīt jūsu tīklā veiktās darbības, tostarp e-pasta ziņojumus, lietotnes un vietnes.\n\nLai iegūtu plašāku informāciju, sazinieties ar administratoru.\n\nIerīcē ir arī izveidots savienojums ar VPN tīklu, kurā var tikt pārraudzītas jūsu tīklā veiktās darbības."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Ir izveidots savienojums ar lietotni <xliff:g id="APPLICATION">%1$s</xliff:g>, kura var pārraudzīt jūsu tīklā veiktās darbības, tostarp e-pasta ziņojumus, lietotnes un vietnes."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Ir izveidots savienojums ar lietotni <xliff:g id="APPLICATION">%1$s</xliff:g>, kura var pārraudzīt jūsu tīklā veiktās privātās darbības, tostarp e-pasta ziņojumus, lietotnes un vietnes."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Ir izveidots savienojums ar lietotni <xliff:g id="APPLICATION">%1$s</xliff:g>, kas var pārraudzīt jūsu tīklā veiktās privātās darbības, tostarp e-pasta ziņojumus, lietotnes un vietnes."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Jūsu darba profilu pārvalda <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Tas ir saistīts ar lietojumprogrammu <xliff:g id="APPLICATION">%2$s</xliff:g>, kura var pārraudzīt jūsu tīklā veiktās darbības, tostarp e-pasta ziņojumus, lietotnes un vietnes.\n\nLai iegūtu plašāku informāciju, sazinieties ar administratoru."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Jūsu darba profilu pārvalda <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Tas ir saistīts ar lietojumprogrammu <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, kura var pārraudzīt jūsu tīklā veiktās darbības, tostarp e-pasta ziņojumus, lietotnes un vietnes.\n\nIr piesaistīta arī lietojumprogramma <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, kas var pārraudzīt jūsu tīklā veiktās privātās darbības."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Ierīce būs bloķēta, līdz to manuāli atbloķēsiet."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Saņemiet paziņojumus ātrāk"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 7fccbe6..fa9489d 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Предупредување за <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Режим на работа"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Ноќно светло"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC е оневозможено"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC е овозможено"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Нема неодамнешни ставки"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Исчистивте сѐ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Информации за апликацијата"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Раздели хоризонтално"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Раздели вертикално"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Раздели прилагодено"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Отфрлете"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Отворете"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Поделен екран во горниот дел"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Поделен екран на левата страна"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Поделен екран на десната страна"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Наполнета"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Исклучи ВПН"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> управува со уредов."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> ја користи <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> за да управува со вашиот уред."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Админ. може да следи и да управува со: поставки, корпоративен пристап, апликации, податоци за уредот и информации за локација."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Администраторот може да следи и да управува со: поставки, корпоративен пристап, апликации, податоци за уредот и информации за локација."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Дознајте повеќе"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Поврзани сте на <xliff:g id="VPN_APP">%1$s</xliff:g>, којашто може да ја следи вашата активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-сајтовите."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Отворете „Поставки за VPN“"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Вашиот администратор вклучил евиденција на мрежата, што подразбира следење на сообраќајот на вашиот уред.\n\nЗа повеќе информации, контактирајте со администраторот."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Вашиот администратор вклучил евиденција на мрежата, што подразбира следење на сообраќајот на вашиот уред.\n\nЗа повеќе информации, контактирајте со администраторот."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Дозволивте апликацијата да постави поврзување преку ВПН.\n\nАпликацијата може да го следи уредот и активноста на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> управува со вашиот работен профил.\n\nАдминистратор е во можност да ја следи вашата активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите.\n\nЗа повеќе информации, контактирајте со администраторот.\n\nИсто така, поврзани сте на ВПН, којашто може да ја следи вашата активност на мрежата."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> управува со вашиот работен профил.\n\nАдминистратор е во можност да ја следи вашата активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите.\n\nЗа повеќе информации, контактирајте со администраторот.\n\nYИсто така, поврзани сте на VPN којашто може да ја следи вашата активност на мрежата."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"ВПН"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Поврзани сте на <xliff:g id="APPLICATION">%1$s</xliff:g>, којашто може да ја следи вашата активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Поврзани сте на <xliff:g id="APPLICATION">%1$s</xliff:g>, којашто може да ја следи вашата лична активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Поврзани сте на <xliff:g id="APPLICATION">%1$s</xliff:g>, којашто може да ја следи вашата лична активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> управува со вашиот работен профил. Истиот е поврзан на <xliff:g id="APPLICATION">%2$s</xliff:g>, којашто може да ја следи вашата работна активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите.\n\nЗа повеќе информации, контактирајте со администраторот."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> управува со вашиот работен профил. Истиот е поврзан на <xliff:g id="APPLICATION">%2$s</xliff:g>, којашто може да ја следи вашата работна активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите.\n\nЗа повеќе информации, контактирајте со администраторот."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> управува со вашиот работен профил. Истиот е поврзан на <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, којашто може да ја следи вашата работна активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите.\n\nВие исто така сте поврзани на <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, којашто може да ја следи вашата лична активност на мрежата."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Уредот ќе остане заклучен додека рачно не го отклучите"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Добивајте известувања побрзо"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 4e64a74..313bf9c 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> മുന്നറിയിപ്പ്"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"പ്രവർത്തന മോഡ്"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"നൈറ്റ് ലൈറ്റ്"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC പ്രവർത്തനരഹിതമാക്കി"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC പ്രവർത്തനക്ഷമമാക്കി"</string>
<string name="recents_empty_message" msgid="808480104164008572">"സമീപകാല ഇനങ്ങൾ ഒന്നുമില്ല"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"നിങ്ങൾ എല്ലാം മായ്ച്ചിരിക്കുന്നു"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ആപ്പ് വിവരം"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"തിരശ്ചീനമായി വേർതിരിക്കുക"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ലംബമായി വേർതിരിക്കുക"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ഇഷ്ടാനുസൃതമായി വേർതിരിക്കുക"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"ഒഴിവാക്കുക"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"തുറക്കുക"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"സ്ക്രീൻ മുകളിലേക്ക് പിളർത്തുക"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"സ്ക്രീൻ ഇടതുവശത്തേക്ക് പിളർത്തുക"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"സ്ക്രീൻ വലതുവശത്തേക്ക് പിളർത്തുക"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"ചാർജായി"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN വിച്ഛേദിക്കുക"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"നിങ്ങളുടെ ഉപകരണം മാനേജുചെയ്യുന്നത് <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> ആണ്."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"നിങ്ങളുടെ ഉപകരണം മാനേജുചെയ്യാൻ <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> ഉപയോഗിക്കുന്നത് <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> ആണ്."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"ഈ ഉപകരണവുമായി ബന്ധപ്പെട്ട ക്രമീകരണം, കോർപ്പറേറ്റ് ആക്സസ്സ്, ആപ്സ്, വിവരങ്ങൾ എന്നിവയും ഉപകരണത്തിന്റെ ലൊക്കേഷൻ വിവരവും നിരീക്ഷിക്കാനും മാനേജുചെയ്യാനും അഡ്മിനിസ്ട്രേറ്റർക്ക് കഴിയും."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"ക്രമീകരണങ്ങൾ, കോർപ്പറേറ്റ് ആക്സസ്സ്, ആപ്സ്, ഉപകരണത്തിന്റെ ഡാറ്റ, ലൊക്കേഷൻ എന്നിവ നിരീക്ഷിക്കാനും നിയന്ത്രിക്കാനും അഡ്മിന് കഴിയും."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" 5"</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"കൂടുതലറിയുക"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"നിങ്ങൾ <xliff:g id="VPN_APP">%1$s</xliff:g> ആപ്പിലേക്ക് കണക്റ്റുചെയ്തിരിക്കുന്നു, ഇമെയിലുകൾ, ആപ്സ്, വെബ്സൈറ്റുകൾ എന്നിവ ഉൾപ്പെടെ നിങ്ങളുടെ നെറ്റ്വർക്ക് ആക്റ്റിവിറ്റി നിരീക്ഷിക്കാൻ ഈ ആപ്പിന് കഴിയും."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" 5"</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN ക്രമീകരണം തുറക്കുക"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"നിങ്ങളുടെ അഡ്മിൻ, ഉപകരണത്തിലെ ട്രാഫിക്ക് നിരീക്ഷിക്കുന്ന നെറ്റ്വർക്ക് ലോഗിംഗ് ഓണാക്കിയിട്ടുണ്ട്.\n\nകൂടുതൽ വിവരങ്ങൾക്ക് നിങ്ങളുടെ അഡ്മിനെ ബന്ധപ്പെടുക."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"നിങ്ങളുടെ ഉപകരണത്തിലെ ട്രാഫിക്ക് നിരീക്ഷിക്കുന്ന നെറ്റ്വർക്ക് ലോഗിംഗ് അഡ്മിൻ ഓണാക്കിയിട്ടുണ്ട്.\n\nകൂടുതൽ വിവരങ്ങൾക്ക് അഡ്മിനുമായി ബന്ധപ്പെടുക."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN കണക്ഷൻ സജ്ജീകരിക്കാൻ നിങ്ങൾ ഒരു ആപ്പിന് അനുമതി നൽകി.\n\nഈ ആപ്പിന് നിങ്ങളുടെ ഇമെയിലുകളും ആപ്സും വെബ്സൈറ്റുകളും ഉൾപ്പെടെ, ഉപകരണവും നെറ്റ്വർക്ക് പ്രവർത്തനവും നിരീക്ഷിക്കാൻ കഴിയും."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ നിയന്ത്രിക്കുന്നത് <xliff:g id="ORGANIZATION">%1$s</xliff:g> ആണ്.\n\nനിങ്ങളുടെ അഡ്മിനിസ്ട്രേറ്റർക്ക് ഇമെയിലുകളും അപ്ലിക്കേഷനുകളും സുരക്ഷിത വെബ്സൈറ്റുകളും ഉൾപ്പെടെയുള്ള നെറ്റ്വർക്ക് പ്രവർത്തനം നിരീക്ഷിക്കാൻ കഴിയും.\n\nകൂടുതൽ വിവരങ്ങൾക്ക്, അഡ്മിനിസ്ട്രേറ്ററെ ബന്ധപ്പെടുക.\n\nനിങ്ങളുടെ നെറ്റ്വർക്ക് പ്രവർത്തനം നിരീക്ഷിക്കാനാകുന്ന ഒരു VPN-ലേക്കും നിങ്ങൾ കണക്റ്റുചെയ്തിരിക്കുന്നു."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ നിയന്ത്രിക്കുന്നത് <xliff:g id="ORGANIZATION">%1$s</xliff:g> ആണ്.\n\nഇമെയിലുകൾ, ആപ്സ്, വെബ്സൈറ്റുകൾ എന്നിവയടങ്ങുന്ന നിങ്ങളുടെ നെറ്റ്വർക്ക് ആക്റ്റിവിറ്റി നിരീക്ഷിക്കാൻ അഡ്മിന് കഴിയും.\n\nകൂടുതൽ വിവരങ്ങൾക്ക് അഡ്മിനുമായി ബന്ധപ്പെടുക.\n\nനെറ്റ്വർക്ക് ആക്റ്റിവിറ്റി നിരീക്ഷിക്കാൻ സാധിക്കുന്ന ഒരു VPN-ലേക്ക് കൂടി നിങ്ങൾ കണക്റ്റ് ചെയ്യപ്പെട്ടിരിക്കുന്നു."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"നിങ്ങൾ <xliff:g id="APPLICATION">%1$s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തിരിക്കുന്നു, അതിന് ഇമെയിലുകൾ, ആപ്സ്, വെബ്സൈറ്റുകൾ എന്നിവ ഉൾപ്പെടെ നിങ്ങളുടെ നെറ്റ്വർക്ക് പ്രവർത്തനം നിരീക്ഷിക്കാനാകും."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"നിങ്ങൾ <xliff:g id="APPLICATION">%1$s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തിരിക്കുന്നു, അതിന് ഇമെയിലുകൾ, ആപ്സ്, വെബ്സൈറ്റുകൾ എന്നിവ ഉൾപ്പെടെ നിങ്ങളുടെ സ്വകാര്യ നെറ്റ്വർക്ക് പ്രവർത്തനം നിരീക്ഷിക്കാനാകും."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"നിങ്ങൾ <xliff:g id="APPLICATION">%1$s</xliff:g> ആപ്പിലേക്ക് കണക്റ്റുചെയ്തിരിക്കുന്നു, ഇമെയിലുകൾ, ആപ്സ്, വെബ്സൈറ്റുകൾ എന്നിവ ഉൾപ്പെടെ നെറ്റ്വർക്ക് ആക്റ്റിവിറ്റി നിരീക്ഷിക്കാൻ ഈ ആപ്പിന് കഴിയും."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ നിയന്ത്രിക്കുന്നത് <xliff:g id="ORGANIZATION">%1$s</xliff:g> ആണ്. നിങ്ങൾ <xliff:g id="APPLICATION">%2$s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തിരിക്കുന്നു, അതിന് ഇമെയിലുകൾ, ആപ്സ്, വെബ്സൈറ്റുകൾ എന്നിവ ഉൾപ്പെടെ നിങ്ങളുടെ ഔദ്യോഗിക നെറ്റ്വർക്ക് പ്രവർത്തനം നിരീക്ഷിക്കാനാകും.\n\nകൂടുതൽ വിവരങ്ങൾക്ക്, നിങ്ങളുടെ അഡ്മിനിസ്ട്രേറ്ററെ ബന്ധപ്പെടുക."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ നിയന്ത്രിക്കുന്നത് <xliff:g id="ORGANIZATION">%1$s</xliff:g> ആണ്. ഇമെയിൽ, ആപ്പുകൾ, വെബ്സൈറ്റുകൾ എന്നിവയടങ്ങുന്ന നിങ്ങളുടെ നെറ്റ്വർക്ക് ആക്റ്റിവിറ്റി നിരീക്ഷിക്കാൻ സാധിക്കുന്ന <xliff:g id="APPLICATION">%2$s</xliff:g> എന്നതിലേക്ക് ഇത് കണക്റ്റ് ചെയ്യപ്പെട്ടിരിക്കുന്നു.\n\nകൂടുതൽ വിവരങ്ങൾക്ക് അഡ്മിനുമായി ബന്ധപ്പെടുക."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ നിയന്ത്രിക്കുന്നത് <xliff:g id="ORGANIZATION">%1$s</xliff:g> ആണ്. നിങ്ങൾ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തിരിക്കുന്നു, അതിന് ഇമെയിലുകൾ, ആപ്സ്, വെബ്സൈറ്റുകൾ എന്നിവ ഉൾപ്പെടെ നിങ്ങളുടെ ഔദ്യോഗിക നെറ്റ്വർക്ക് പ്രവർത്തനം നിരീക്ഷിക്കാനാകും.\n\nനിങ്ങൾ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> എന്നതിലേക്കും കണക്റ്റുചെയ്തിരിക്കുന്നു, അതിന് നിങ്ങളുടെ സ്വകാര്യ നെറ്റ്വർക്ക് പ്രവർത്തനം നിരീക്ഷിക്കാനാകും."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"നിങ്ങൾ സ്വമേധയാ അൺലോക്കുചെയ്യുന്നതുവരെ ഉപകരണം ലോക്കുചെയ്തതായി തുടരും"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"അറിയിപ്പുകൾ വേഗത്തിൽ സ്വീകരിക്കുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 3c12896..873f5a5 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -319,6 +319,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> анхааруулга"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Ажлын горим"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Шөнийн гэрэл"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC-г цуцалсан"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC-г идэвхжүүлсэн"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Сүүлийн үеийн зүйл байхгүй"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Та бүгдийг нь устгасан"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Аппликешны мэдээлэл"</string>
@@ -332,6 +335,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Хэвтээ чиглэлд хуваах"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Босоо чиглэлд хуваах"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Хүссэн хэлбэрээр хуваах"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Хаах"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Нээх"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Дэлгэцийг дээд хэсэгт хуваах"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Дэлгэцийг зүүн хэсэгт хуваах"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Дэлгэцийг баруун хэсэгт хуваах"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Цэнэглэгдсэн"</string>
@@ -412,20 +420,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN таслах"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Таны төхөөрөмжийг <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> удирддаг."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> таны төхөөрөмжийг удирдахын тулд <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>-г ашигладаг."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Таны админ тохиргоо, байгууллагын хандалт, апп, төхөөрөмжтэй холбоотой өгөгдөл болон төхөөрөмжийн байршлын мэдээллийг хянах, удирдах боломжтой."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Таны админ тохиргоо, байгууллагын хандалт, апп, төхөөрөмжтэй холбоотой өгөгдөл болон таны төхөөрөмжийн байршлын мэдээллийг хянах, удирдах боломжтой."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Дэлгэрэнгүй үзэх"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Таны имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN тохиргоог нээх"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Таны админ төхөөрөмжийн ачааллыг хянадаг сүлжээний логийг асаасан байна.\n\nДэлгэрэнгүй мэдээлэл авахын тулд админтайгаа холбогдоно уу."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Таны админ төхөөрөмжийн ачааллыг хянадаг сүлжээний логийг асаасан байна.\n\nДэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Та апп-д VPN холболт хийхийг зөвшөөрсөн байна.\n\nЭнэхүү апп нь таны имэйл, апп, вэбсайт зэрэг төхөөрөмж болон сүлжээний үйл ажиллагааг хянах боломжтой."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг.\n\nАдмин нь таны имэйл,апп болон вэбсайт зэрэг сүлжээний үйл ажиллагааг хянадаг. \n\n Дэлгэрэнгүй мэдээлэл авахыг хүсвэл админтайгаа холбогдоно уу. \n\nМөн та VPN-д холбогдсон бөгөөд ингэснээр өөрийн сүлжээний үйл ажиллагааг хянах боломжтой байна."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> таны ажлын профайлыг удирддаг.\n\nТаны админ имэйл, апп болон вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой.\n\nДэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу.\n\nТа сүлжээний үйл ажиллагааг хянах боломжтой VPN-д холбогдсон байна."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вэбсайт зэрэг сүлжээний үйл ажиллагааг хянах боломжтой."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вэбсайт зэрэг сүлжээний хувийн үйл ажиллагааг хянах боломжтой."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Та имэйл, апп, вэб хуудас зэрэг хувийн сүлжээнийхээ үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон байна."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь <xliff:g id="APPLICATION">%2$s</xliff:g>-тэй холбогдсон бөгөөд таны имэйл, апп, вэбсайт зэрэг сүлжээний үйл ажиллагааг хянах боломжтой.\n\nДэлгэрэнгүй мэдээлэл авахыг хүсвэл админтайгаа холбогдоно уу."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> таны ажлын профайлыг удирддаг. Энэ нь таны имэйл, апп, вэб хуудас зэрэг ажлын сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%2$s</xliff:g>-тэй холбогдсон.\n\nДэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>-тай холбогдсон бөгөөд таны имэйл, апп, вэбсайт зэрэг ажлын сүлжээний үйл ажиллагааг хянах боломжтой.\n\nМөн та <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны сүлжээний хувийн үйл ажиллагааг хянаж чадна."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Таныг гараар онгойлгох хүртэл төхөөрөмж түгжээтэй байх болно"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Мэдэгдлийг хурдан авах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 7fc127a..1beae47 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> चेतावणी"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"कार्य मोड"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"रात्रीचा प्रकाश"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC अक्षम केले आहे"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC सक्षम केले आहे"</string>
<string name="recents_empty_message" msgid="808480104164008572">"अलीकडील कोणतेही आयटम नाहीत"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"आपण सर्वकाही साफ केले"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"अनुप्रयोग माहिती"</string>
@@ -334,6 +337,16 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"क्षैतिज विभाजित करा"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"अनुलंब विभाजित करा"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"सानुकूल विभाजित करा"</string>
+ <!-- no translation found for recents_accessibility_dismissed (2354459747918667050) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_open (1651449827614876864) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_top (9056056469282256287) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_left (8987144699630620019) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_right (275069779299592867) -->
+ <skip />
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"चार्ज झाली"</string>
@@ -414,20 +427,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN डिस्कनेक्ट करा"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"आपले डिव्हाइस <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> ने व्यवस्थापित केले आहे."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"आपले डिव्हाइस व्यवस्थापित करण्यासाठी <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> वापरते."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"आपला प्रशासक सेटिंग्ज, कॉर्पोरेट प्रवेश, अॅप्स, आपल्या डिव्हाइसशी संबद्ध डेटा आणि आपल्या डिव्हाइसच्या स्थान माहितीचे परीक्षण करू आणि ते व्यवस्थापित करू शकतो."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"आपला प्रशासक सेटिंग्ज, कॉर्पोरेट प्रवेश, अॅप्स, आपल्या डिव्हाइशी संबंधित डेटा आणि डिव्हाइसच्या स्थान माहितीचे निरीक्षण आणि व्यवस्थापन करू शकतो."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"अधिक जाणून घ्या"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"आपण <xliff:g id="VPN_APP">%1$s</xliff:g> शी कनेक्ट केले आहे, जो ईमेल, अॅप्स आणि वेबसाइटसह आपल्या नेटवर्क क्रियाकलापाचे परीक्षण करू शकतो."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN सेटिंग्ज उघडा"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"आपल्या प्रशासकाने नेटवर्क लॉगिंग चालू केले आहे, जे आपल्या डिव्हाइस वरील रहदारीचे परीक्षण करते.\n\nअधिक माहितीसाठी आपल्या प्रशासकाशी संपर्क साधा."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"आपल्या प्रशासकाने नेटवर्क लॉगिंग चालू केले आहे, जे आपल्या डिव्हाइसवरील रहदारीचे निरीक्षण करते.\n\nअधिक माहितीसाठी आपल्या प्रशासकाशी संपर्क साधा."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"आपण VPN कनेक्शन सेट करण्यासाठी अॅपला परवानगी दिली.\n\nहा अॅप ईमेल, अॅप्स आणि वेबसाइटसह, आपल्या डिव्हाइस आणि नेटवर्क क्रियाकलापाचे परीक्षण करू शकतो."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"आपले कार्य प्रोफाईल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारे व्यवस्थापित केले आहे.\n\nआपला प्रशासक ईमेल, अॅप्स आणि वेबसाइटसह आपल्या नेटवर्क क्रियाकलापाचे परीक्षण करण्यास सक्षम आहे.\n\nअधिक माहितीसाठी, आपल्या प्रशासकाशी संपर्क साधा.\n\nआपण एका VPN शी देखील कनेक्ट केले आहे, जो आपल्या नेटवर्क क्रियाकलापाचे परीक्षण करू शकतो."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"आपले कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारे व्यवस्थापित केले जाते.\n\nआपला प्रशासक ईमेल, अॅप्स आणि वेबसाइटच्या समावेशासह आपल्या नेटवर्क क्रियाकलापाचे निरीक्षण करण्यास सक्षम आहे.\n\nअधिक माहितीसाठी आपल्या प्रशासकाशी संपर्क साधा.\n\nआपण VPN शी देखील कनेक्ट आहात, जे आपल्या नेटवर्क क्रियाकलापाचे निरीक्षण करू शकते."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"आपण <xliff:g id="APPLICATION">%1$s</xliff:g> शी कनेक्ट केले आहे, जो ईमेल, अॅप्स आणि वेबसाइटसह आपल्या नेटवर्क क्रियाकलापाचे परीक्षण करू शकतो."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"आपण <xliff:g id="APPLICATION">%1$s</xliff:g> शी कनेक्ट केले आहे, जो ईमेल, अॅप्स आणि वेबसाइटसह आपल्या वैयक्तिक नेटवर्क क्रियाकलापाचे परीक्षण करू शकतो."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"आपण <xliff:g id="APPLICATION">%1$s</xliff:g> शी कनेक्ट केले आहे, जो ईमेल, अॅप्स आणि वेबसाइटसह आपल्या वैयक्तिक नेटवर्क क्रियाकलापाचे परीक्षण करू शकतो."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"आपले कार्य प्रोफाईल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारे व्यवस्थापित केले आहे. ते <xliff:g id="APPLICATION">%2$s</xliff:g> शी कनेक्ट केले आहे, जो ईमेल, अॅप्स आणि वेबसाइटसह आपल्या कार्य नेटवर्क क्रियाकलापाचे परीक्षण करू शकतो.\n\nअधिक माहितीसाठी, आपल्या प्रशासकाशी संपर्क साधा."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"आपले कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारे व्यवस्थापित केले जाते. ते <xliff:g id="APPLICATION">%2$s</xliff:g> शी कनेक्ट केलेले आहे, जे ईमेल, अॅप्स आणि वेबसाइटच्या समावेशासह आपल्या कार्य नेटवर्क क्रियाकलापाचे निरीक्षण करते. \n\nअधिक माहितीसाठी आपल्या प्रशासकाशी संपर्क साधा."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"आपले कार्य प्रोफाईल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारे व्यवस्थापित केले आहे. ते <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> शी कनेक्ट केले आहे, जे ईमेल, अॅप्स आणि वेबसाइटसह आपल्या कार्य नेटवर्क क्रियाकलापाचे परीक्षण करू शकते.\n\nआपण <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> शी देखील कनेक्ट केले आहे, जे आपल्या वैयक्तिक नेटवर्क क्रियाकलापाचे परीक्षण करू शकते."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"आपण व्यक्तिचलितपणे अनलॉक करेपर्यंत डिव्हाइस लॉक केलेले राहील"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"सूचना अधिक जलद मिळवा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 2069959..21873fe1 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Amaran <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Mod kerja"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Cahaya Malam"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Tiada item terbaharu"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Anda telah mengetepikan semua item"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Maklumat Aplikasi"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Mendatar Terpisah"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Menegak Terpisah"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Tersuai Terpisah"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Ketepikan"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Buka"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Pisahkan skrin ke atas"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Pisahkan skrin ke kiri"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Pisahkan skrin ke kanan"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Sudah dicas"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Putuskan sambungan VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Peranti anda diurus oleh <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> menggunakan <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> untuk mengurus peranti anda."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Pentadbir anda boleh memantau dan mengurus tetapan, akses korporat, apl, data yang dikaitkan dengan peranti dan maklumat lokasi peranti anda."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Ketahui lebih lanjut"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Anda disambungkan ke <xliff:g id="VPN_APP">%1$s</xliff:g>, yang boleh memantau aktiviti rangkaian anda, termasuk e-mel, apl dan tapak web."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Buka Tetapan VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Pentadbir anda telah menghidupkan pengelogan rangkaian yang memantau trafik pada peranti anda.\n\nUntuk mendapatkan maklumat lanjut, hubungi pentadbir anda."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Anda memberikan kebenaran kepada apl untuk menyediakan sambungan VPN.\n\nApl ini boleh memantau aktiviti peranti dan rangkaian anda, termasuk e-mel, apl dan tapak web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Profil kerja anda diurus oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nPentadbir anda boleh memantau aktiviti rangkaian anda, termasuk e-mel, apl dan tapak web.\n\nUntuk mendapatkan maklumat lanjut, hubungi pentadbir anda.\n\nAnda turut disambungkan ke VPN, yang boleh memantau aktiviti rangkaian anda."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Anda disambungkan ke <xliff:g id="APPLICATION">%1$s</xliff:g>, yang boleh memantau aktiviti rangkaian anda, termasuk e-mel, apl dan tapak web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Anda disambungkan ke <xliff:g id="APPLICATION">%1$s</xliff:g>, yang boleh memantau aktiviti rangkaian peribadi anda, termasuk e-mel, apl dan tapak web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Anda disambungkan ke <xliff:g id="APPLICATION">%1$s</xliff:g>, yang boleh memantau aktiviti rangkaian peribadi anda, termasuk e-mel, apl dan tapak web."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Profil kerja anda diurus oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil ini disambungkan ke <xliff:g id="APPLICATION">%2$s</xliff:g>, yang boleh memantau aktiviti rangkaian kerja anda, termasuk e-mel, apl dan tapak web.\n\nUntuk mendapatkan maklumat lanjut, sila hubungi pentadbir anda."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Profil kerja anda diurus oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil disambungkan ke <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, yang boleh memantau aktiviti rangkaian kerja anda, termasuk e-mel, apl dan tapak web.\n\nAnda turut disambungkan ke <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, yang boleh memantau aktiviti rangkaian peribadi anda."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Peranti akan kekal terkunci sehingga anda membuka kunci secara manual"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Dapatkan pemberitahuan lebih cepat"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index db6edad..b920279 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> သတိပေးချက်"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"အလုပ် မုဒ်"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ညအလင်းရောင်"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC ကို ပိတ်ထားသည်"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC ကို ဖွင့်ထားသည်"</string>
<string name="recents_empty_message" msgid="808480104164008572">"မကြာမီကဖွင့်ထားသည်များ မရှိပါ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"သင်အားလုံးကို ရှင်းလင်းပြီးပါပြီ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"အပလီကေးရှင်းအင်ဖို"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ရေပြင်ညီ ပိုင်းမည်"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ဒေါင်လိုက်ပိုင်းမည်"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"စိတ်ကြိုက် ပိုင်းမည်"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"ပယ်ရန်"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"ဖွင့်ရန်"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"မျက်နှာပြင်ကို အပေါ်သို့ ခွဲရန်"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"မျက်နှာပြင်ကို ဘယ်ဘက်သို့ ခွဲရန်"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"မျက်နှာပြင်ကို ညာဘက်သို့ ခွဲရန်"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"အားသွင်းပြီး"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN ကို အဆက်ဖြတ်ရန်"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"သင့်စက်ပစ္စည်းကို <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> က စီမံခန့်ခွဲထားပါသည်။"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> သည် သင့်စက်ပစ္စည်းကို စီမံခန့်ခွဲရန် <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> ကို အသုံးပြုပါသည်။"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"စီမံသူသည် ဆက်တင်၊ ကော်ပိုရိတ် သုံးခွင့်၊ အက်ပ်၊ စက်ပစ္စည်းနှင့် ဆက်စပ်ဒေတာနှင့် ၎င်း၏တည်နေရာအချက်အလက်ကိုစောင့်ကြည့်စီမံနိုင်ပါသည်။"</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"စီမံသူသည် ဆက်တင်၊ ကော်ပိုရိတ်သုံးခွင့်၊ အက်ပ်၊ စက်ပစ္စည်းနှင့်ဆိုင်သောဒေတာနှင့် ၎င်း၏တည်နေရာအချက်အလက်ကိုစောင့်ကြည့်စီမံနိုင်ပါသည်။"</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"ပိုမိုလေ့လာရန်"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"အီးမေးလ်၊ အက်ပ်နှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်ကွန်ရက်လုပ်ဆောင်ချက်ကို စောင့်ကြည့်နိုင်သည့် <xliff:g id="VPN_APP">%1$s</xliff:g> သို့ သင်သည် ချိတ်ဆက်ထားပါသည်။"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Open VPN ဆက်တင်များ"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"သင့်စီမံခန့်ခွဲသူသည် သင့်စက်ပစ္စည်းပေါ်ရှိ ဒေတာအသွားအလာကို စောင့်ကြည့်နိုင်သည့် ကွန်ရက်မှတ်တမ်းတင်ခြင်းကို ဖွင့်ထားပါသည်။\n\nထပ်မံလေ့လာရန်အတွက် သင့်စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။"</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"သင့်စီမံခန့်ခွဲသူသည် စက်ပစ္စည်းပေါ်ရှိ ဒေတာအသွားအလာကို စောင့်ကြည့်နိုင်သည့် ကွန်ရက်အတွက် မှတ်တမ်းတင်ခြင်းကို ဖွင့်ထားပါသည်။\n\nနောက်ထပ် အချက်အလက်များအတွက် သင့်စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။"</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN ချိတ်ဆက်မှုပြုလုပ်ရန် အက်ပ်ကို သင်ခွင့်ပြုလိုက်သည်။ \n\n ဤအက်ပ်သည် အီးမေးလ်များ၊ အက်ပ်များနှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်ကွန်ရက်လုပ်ဆောင်ချက်များကို စောင့်ကြည့်နိုင်သည်။"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"သင့်အလုပ်ပရိုဖိုင်ကို <xliff:g id="ORGANIZATION">%1$s</xliff:g>မှ စီမံခန့်ခွဲပါသည်။ \n\nသင့်စီမံခန့်ခွဲသူသည် အီးမေးလ်များ၊ အက်ပ်များ၊ နှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်ကွန်ရက်လုပ်ဆောက်ချက်ကို စောင့်ကြည့်နိုင်သည်။ \n\nအချက်အလက်များ ပိုမိုရယူရန် သင်၏ စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။ \n\nသင်သည် VPN တစ်ခုသို့ပါ ချိတ်ဆက်ထားပြီး ၎င်းကပါ သင်၏ ကွန်ရက်လုပ်ဆောင်ချက်ကို စောင့်ကြည့်နိုင်ပါသည်။"</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"သင့်အလုပ်ပရိုဖိုင်ကို <xliff:g id="ORGANIZATION">%1$s</xliff:g> က စီမံခန့်ခွဲထားပါသည်။\n\nသင့်စီမံခန့်ခွဲသူသည် အီးမေးလ်၊ အက်ပ်နှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်ကွန်ရက်လုပ်ဆောင်ချက်ကို စောင့်ကြည့်နိုင်ပါသည်။\n\nနောက်ထပ် အချက်အလက်များအတွက် သင့်စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။\n\nသင်သည် သင့်ကွန်ရက်လုပ်ဆောင်ချက်ကို စောင့်ကြည့်နိုင်သည့် VPN သို့လည်း ချိတ်ဆက်ထားပါသေးသည်။"</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"သင်သည် <xliff:g id="APPLICATION">%1$s</xliff:g> သို့ ချိတ်ဆက်ထားသည်၊ ၎င်းသည် အီးမေးများ၊ အက်ပ်များနှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်ကွန်ရက်လုပ်ဆောင်ချက်များကို စောင့်ကြည့်နိုင်သည်။"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"သင်သည် <xliff:g id="APPLICATION">%1$s</xliff:g> သို့ ချိတ်ဆက်ထားသည်။ ၎င်းသည် အီးမေးလ်များ၊ အက်ပ်များနှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်ကွန်ရက်လုပ်ဆောင်ချက်ကို စောင့်ကြည့်နိုင်သည်။"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"သင်သည် အီးမေးလ်၊ အက်ပ်နှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်ကိုယ်ရေးကိုယ်တာ ကွန်ရက်အသုံးပြုမှုကို စောင့်ကြည့်နိုင်သည့် <xliff:g id="APPLICATION">%1$s</xliff:g> သို့ ချိတ်ဆက်ထားပါသည်။"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"သင့်အလုပ်ပရိုဖိုင်ကို <xliff:g id="ORGANIZATION">%1$s</xliff:g> မှစီမံခန့်ခွဲသည်။ ၎င်းကို <xliff:g id="APPLICATION">%2$s</xliff:g> သို့ ချိတ်ဆက်ထားသည်၊ ၎င်းသည် အီးမေးလ်များ၊ အက်ပ်များနှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်အလုပ်ကွန်ရက်လုပ်ဆောင်ချက်ကို စောင့်ကြည့်နိုင်သည်။ \n\nအချက်အလက်များ ပိုမိုရယူရန် သင်၏ စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။"</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"သင့်အလုပ်ပရိုဖိုင်ကို <xliff:g id="ORGANIZATION">%1$s</xliff:g> က စီမံခန့်ခွဲထားပါသည်။ ၎င်းသည် အီးမေးလ်၊ အက်ပ်နှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်ကွန်ရက်လုပ်ဆောင်ချက်များကို စောင့်ကြည့်နိုင်သည့် <xliff:g id="APPLICATION">%2$s</xliff:g> သို့ ချိတ်ဆက်ထားပါသည်။\n\nနောက်ထပ် အချက်အလက်များအတွက် သင့်စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။"</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"သင့်အလုပ် ပရိုဖိုင်ကို <xliff:g id="ORGANIZATION">%1$s</xliff:g> မှစီမံခန့်ခွဲသည်။ ၎င်းကို <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> သို့ ချိတ်ဆက်ထားသည်၊ ၎င်းသည် အီးမေးလ်များ၊ အက်ပ်များနှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်အလုပ်ကွန်ရက်လုပ်ဆောင်ချက်ကို စောင့်ကြည့်နိုင်သည်။ \n\n သင်သည်<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ကိုလည်း ချိတ်ဆက်ထားသည်၊ ၎င်းသည် သင့်ကိုယ်ပိုင်ကွန်ရက်လုပ်ဆောင်ချက်များကို စောင့်ကြည့်နိုင်သည်။"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"သင်က လက်ဖြင့် သော့မဖွင့်မချင်း ကိရိယာမှာ သော့ပိတ်လျက် ရှိနေမည်"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"အကြောင်းကြားချက်များ မြန်မြန်ရရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index e5b94bd..bb08967 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Advarsel for <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Arbeidsmodus"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nattlys"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC er slått av"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC er slått på"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Ingen nylige elementer"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Du har fjernet alt"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Appinformasjon"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Del horisontalt"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Del vertikalt"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Del tilpasset"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Avvis"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Åpne"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Delt skjerm øverst"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Delt skjerm til venstre"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Delt skjerm til høyre"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Oppladet"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Koble fra VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Enheten din administreres av <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> bruker <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> til å administrere enheten din."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administratoren din kan overvåke og administrere innstillinger, bedriftstilgang, apper, data som er tilknyttet denne enheten, og enhetens posisjonsinformasjon."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Administratoren din kan overvåke og administrere innstillinger, bedriftstilgang, apper, data som er tilknyttet denne enheten, og enhetens posisjonsinformasjon."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Finn ut mer"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Enheten er koblet til <xliff:g id="VPN_APP">%1$s</xliff:g>, som kan overvåke nettverksaktiviteten din, inkludert e-post, apper og nettsteder."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Åpne VPN-innstillingene"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administratoren din har slått på loggføring av nettverk, som overvåker trafikken på enheten din.\n\nKontakt administratoren for mer informasjon."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Administratoren din har slått på loggføring av nettverk, som overvåker trafikken på enheten din.\n\nKontakt administratoren for mer informasjon."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Du ga en app tillatelse til å konfigurere en VPN-tilkobling.\n\nDenne appen kan overvåke enheten og nettverksaktiviteten din, inkludert e-post, apper og nettsteder."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Work-profilen din administreres av <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministratoren din kan overvåke nettverksaktiviteten din, inkludert e-post, apper og nettsteder.\n\nFor mer informasjon, ta kontakt med administratoren.\n\nDu er også koblet til et VPN, som kan overvåke nettverksaktiviteten din."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Jobbprofilen din administreres av <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministratoren din kan overvåke nettverksaktiviteten din, inkludert e-post, apper og nettsteder.\n\nKontakt administratoren for mer informasjon.\n\nDu er også tilkoblet en VPN som kan overvåke nettverksaktiviteten din."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Enheten er koblet til <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan overvåke nettverksaktiviteten din, inkludert e-post, apper og nettsteder."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Enheten er koblet til <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan overvåke den personlige nettverksaktiviteten din, inkludert e-post, apper og nettsteder."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Enheten er koblet til <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan overvåke den personlige nettverksaktiviteten din, inkludert e-post, apper og nettsteder."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Work-profilen din administreres av <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Den er koblet til <xliff:g id="APPLICATION">%2$s</xliff:g>, som kan overvåke nettverksaktiviteten din på jobben, inkludert e-post, apper og nettsteder.\n\nFor å få mer informasjon, ta kontakt med administratoren."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Jobbprofilen din administreres av <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Den er koblet til <xliff:g id="APPLICATION">%2$s</xliff:g>, som kan overvåke nettverksaktiviteten din, inkludert e-poster, apper og nettsteder.\n\nKontakt administratoren din for mer informasjon."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Work-profilen din administreres av <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Den er koblet til <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, som kan overvåke nettverksaktiviteten din på jobben, inkludert e-post, apper og nettsteder.\n\nDu er også koblet til <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, som kan overvåke den personlige nettverksaktiviteten din."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Enheten forblir låst til du låser den opp manuelt"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Motta varsler raskere"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 893f638..da856a6 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> चेतावनी दिँदै"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"कार्य मोड"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"रात्रिको प्रकाश"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC लाई असक्षम पारिएको छ"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC लाई सक्षम पारिएको छ"</string>
<string name="recents_empty_message" msgid="808480104164008572">"हालका कुनै पनि वस्तुहरू छैनन्"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"तपाईँले सबै कुरा खाली गर्नुभएको छ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"अनुप्रयोग जानकारी"</string>
@@ -334,6 +337,16 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"तेर्सो रूपमा विभाजन गर्नुहोस्"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ठाडो रूपमा विभाजन गर्नुहोस्"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"आफू अनुकूल विभाजन गर्नुहोस्"</string>
+ <!-- no translation found for recents_accessibility_dismissed (2354459747918667050) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_open (1651449827614876864) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_top (9056056469282256287) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_left (8987144699630620019) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_right (275069779299592867) -->
+ <skip />
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"चार्ज भयो"</string>
@@ -414,20 +427,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"विच्छेद VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"तपाईंको यन्त्र <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> द्वारा व्यवस्थापन गरिएको छ।"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> ले तपाईंको यन्त्रको व्यवस्थापन गर्न <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> को प्रयोग गर्दछ।"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"तपाईंको प्रशासकले सेटिङ, संस्थागत पहुँच, अनुप्रयोग, तपाईंको यन्त्रसँग सम्बन्धित डेटा र तपाईंको यन्त्रको स्थान सम्बन्धी जानकारीको अनुगमन तथा व्यवस्थापन गर्न सक्नुहुन्छ।"</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"तपाईँको प्रशासकले सेटिङहरू, संस्थागत पहुँच, अनुप्रयोग, तपाईँको यन्त्रसँग सम्बन्धित डेटा र तपाईँको यन्त्रको स्थान सम्बन्धी जानकारीको निगरानी तथा व्यवस्थापन गर्न सक्नुहुन्छ।"</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"थप जान्नुहोस्"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"तपाईं <xliff:g id="VPN_APP">%1$s</xliff:g> मा जोडिनुभएको छ जसले इमेल, अनुप्रयोग र वेबसाइटहरू लगायत तपाईंको नेटवर्क सम्बन्धी गतिविधिको अनुगमन गर्न सक्छ।"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN सम्बन्धी सेटिङहरू खोल्नुहोस्"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"तपाईंको प्रशासकले तपाईंको यन्त्रमा ट्राफिकको अनुगमन गर्ने नेटवर्कको लगिङलाई सक्रिय गर्नुभएको छ।\n\nथप जानकरीका लागि आफ्नो प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"तपाईँको प्रशासकले तपाईँको यन्त्रमा ट्राफिकको निगरानी गर्ने नेटवर्कको लगिङलाई सक्रिय पार्नुभएको छ।\n\nथप जानकारीका लागि आफ्नो प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"तपाईँले VPN जडान गर्न अनुप्रयोगलाई अनुमति दिनुभयो।\n\nयो अनुप्रयोगले तपाईँका यन्त्र र नेटवर्क गतिविधि लगायत इमेल, अनुप्रयोग र वेबसाइटहरू अनुगमन गर्न सक्छ।"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"तपाईँको कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g>द्वारा व्यवस्थापन गरिन्छ।.\n\nतपाईँको प्रशासक इमेल, अनुप्रयोगहरू, र सुरक्षित वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधि अनुगमन गर्न सक्षम छ।\n\nथप जानकारीको लागि, आफ्नो प्रशासकसँग सम्पर्क राख्नुहोस्।\n\nतपाईँ VPN सँग पनि जडित हुनुहुन्छ, जसले तपाईँको नेटवर्क गतिविधि अनुगमन गर्न सक्छ।"</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"तपाईँको कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> ले व्यवस्थापन गर्दछ।\n\nतपाईँको प्रशासकले तपाईँको इमेल, अनुप्रयोग र वेबसाइट सहित नेटवर्कमा तपाईँको गतिविधिको निगरानी गर्न सक्नुहुन्छ। \n\nथप जानकारीका लागि आफ्नो प्रशासकलाई सम्पर्क गर्नुहोस्।\n\n तपाईँ एउटा VPN मा जडित हुनुहुन्छ। यस VPN ले नेटवर्कमा तपाईँको गतिविधिको निगरानी गर्न सक्छ।"</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"तपाईँ <xliff:g id="APPLICATION">%1$s</xliff:g> सँग जडित हुनुहुन्छ जसले इ-मेल, अनुप्रयोगहरू र वेबसाइट लगायतका तपाईँका नेटवर्क गतिविधिको अनुगमन गर्न सक्छ।"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"तपाईँ <xliff:g id="APPLICATION">%1$s</xliff:g> सँग जडित हुनुहुन्छ जसले इ-मेल, अनुप्रयोगहरू र वेबसाइट लगायतका तपाईँको निजी नेटवर्क गतिविधिका अनुगमन गर्न सक्छ।"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"तपाईं <xliff:g id="APPLICATION">%1$s</xliff:g> मा जोडिनुभएको छ जसले इमेल, अनुप्रयोग र वेबसाइटहरू लगायतको तपाईंको व्यक्तिगत नेटवर्क सम्बन्धी गतिविधिको अनुगमन गर्न सक्छ।"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"तपाईँको कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थापन गरिन्छ। यो <xliff:g id="APPLICATION">%2$s</xliff:g> सँग जोडिएको छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधि अनुगमन गर्न सक्छ।\n\nथप जानकारीको लागि, आफ्नो प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"तपाईँको कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> ले व्यवस्थापन गर्दछ। तपाईँको कार्य प्रोफाइल <xliff:g id="APPLICATION">%2$s</xliff:g> मा जोडिएको छ। यो अनुप्रयोगले तपाईँको इमेल, अन्य अनुप्रयोग र वेबसाइटहरू सहित नेटवर्कमा तपाईँको गतिविधिको निगरानी गर्न सक्छ।\n\nथप जानकारीका लागि आफ्नो प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"तपाईँको कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थापन गरिन्छ। यो <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> सँग जोडिएको छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधि अनुगमन गर्न सक्छ।\n\nतपाईँ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> सँग पनि जडित हुनुहुन्छ, जसले तपाईँको व्यक्तिगत नेटवर्क गतिविधि अनुगमन गर्न सक्छ।"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"तपाईँले नखोले सम्म उपकरण बन्द रहनेछ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"छिटो सूचनाहरू प्राप्त गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index df1f97b..87c0860 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Waarschuwing voor <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Werkmodus"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nachtverlichting"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC is uitgeschakeld"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC is ingeschakeld"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Geen recente items"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Je hebt alles gewist"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"App-informatie"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horizontaal splitsen"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Verticaal splitsen"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Aangepast splitsen"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Sluiten"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Openen"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Scherm bovenaan gesplitst"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Scherm links gesplitst"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Scherm rechts gesplitst"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Opgeladen"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Verbinding met VPN verbreken"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Je apparaat wordt beheerd door <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> gebruikt <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> om je apparaat te beheren."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Je beheerder kan instellingen, zakelijke toegang, apps, aan je apparaat gekoppelde gegevens en de locatiegegevens van je apparaat controleren en beheren."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Je beheerder kan instellingen, zakelijke toegang, apps, aan je apparaat gekoppelde gegevens en de locatiegegevens van je apparaat controleren en beheren."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Meer informatie"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Je bent verbonden met <xliff:g id="VPN_APP">%1$s</xliff:g>, waarmee je netwerkactiviteit (waaronder e-mails, apps en websites) kan worden gecontroleerd."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN-instellingen openen"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Je beheerder heeft netwerkregistratie ingeschakeld, waarmee verkeer op je apparaat wordt bijgehouden.\n\nNeem voor meer informatie contact op met je beheerder."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Je beheerder heeft netwerkregistratie ingeschakeld, waarmee verkeer op je apparaat wordt bijgehouden.\n\nNeem contact op met je beheerder voor meer informatie."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Je hebt een app toestemming gegeven voor het instellen van een VPN-verbinding.\n\nMet deze app kan je apparaat- en netwerkactiviteit worden gecontroleerd, inclusief e-mails, apps en websites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nJe beheerder kan je netwerkactiviteit controleren, inclusief e-mails, apps en websites.\n\nNeem contact op met je beheerder voor meer informatie.\n\nU bent ook verbonden met een VPN waarmee je netwerkactiviteit kan worden gecontroleerd."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nJe beheerder kan je netwerkactiviteit controleren, inclusief e-mails, apps en websites.\n\nNeem contact op met je beheerder voor meer informatie.\n\nJe bent ook verbonden met een VPN, waarmee je netwerkactiviteit kan worden gecontroleerd."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"U bent verbonden met <xliff:g id="APPLICATION">%1$s</xliff:g>, waarmee je netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"U bent verbonden met <xliff:g id="APPLICATION">%1$s</xliff:g>, waarmee je persoonlijke netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Je bent verbonden met <xliff:g id="APPLICATION">%1$s</xliff:g>, waarmee je persoonlijke netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Deze is verbonden met <xliff:g id="APPLICATION">%2$s</xliff:g>, waarmee je werkgerelateerde netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites.\n\nNeem contact op met je beheerder voor meer informatie."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Het is gekoppeld aan <xliff:g id="APPLICATION">%2$s</xliff:g>, waarmee je werkgerelateerde netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites.\n\nNeem contact op met je beheerder voor meer informatie."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Deze is verbonden met <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, waarmee je werkgerelateerde netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites.\n\nU bent ook verbonden met <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, waarmee je persoonlijke netwerkactiviteit kan worden gecontroleerd."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Het apparaat blijft vergrendeld totdat u het handmatig ontgrendelt"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Sneller meldingen ontvangen"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 763ee1f..f4e551d2 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ਚਿਤਾਵਨੀ"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"ਕੰਮ ਮੋਡ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"ਰਾਤਰੀ ਲਾਈਟ"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC ਨੂੰ ਅਯੋਗ ਬਣਾਇਆ ਗਿਆ"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC ਨੂੰ ਯੋਗ ਬਣਾਇਆ ਗਿਆ"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮਾਂ ਨਹੀਂ"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ਤੁਸੀਂ ਸਭ ਕੁਝ ਸਾਫ਼ ਕਰ ਦਿੱਤਾ ਹੈ"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ਐਪਲੀਕੇਸ਼ਨ ਜਾਣਕਾਰੀ"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ਹੌਰੀਜ਼ੌਂਟਲ ਸਪਲਿਟ"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ਵਰਟੀਕਲ ਸਪਲਿਟ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ਕਸਟਮ ਸਪਲਿਟ"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"ਖਾਰਜ ਕਰੋ"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"ਖੋਲ੍ਹੋ"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ਸਕ੍ਰੀਨ ਨੂੰ ਉੱਪਰ ਵੱਲ ਖੰਡਿਤ ਕਰੋ"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"ਸਕ੍ਰੀਨ ਨੂੰ ਖੱਬੇ ਪਾਸੇ ਖੰਡਿਤ ਕਰੋ"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"ਸਕ੍ਰੀਨ ਨੂੰ ਸੱਜੇ ਪਾਸੇ ਖੰਡਿਤ ਕਰੋ"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"ਚਾਰਜ ਹੋਇਆ"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN ਨੂੰ ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"ਤੁਹਾਡੀ ਡੀਵਾਈਸ <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤੀ ਜਾਂਦੀ ਹੈ।"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> ਤੁਹਾਡੀ ਡੀਵਾਈਸ ਦੇ ਪ੍ਰਬੰਧਨ ਲਈ <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ।"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"ਤੁਹਾਡਾ ਪ੍ਰਸ਼ਾਸਕ ਤੁਹਾਡੀ ਡੀਵਾਈਸ ਅਤੇ ਉਸਦੀ ਟਿਕਾਣਾ ਜਾਣਕਾਰੀ ਸਬੰਧੀ ਸੈਟਿੰਗਾਂ, ਕਾਰਪੋਰੇਟ ਪਹੁੰਚ, ਐਪਾਂ, ਡੈਟੇ ਦੀ ਨਿਗਰਾਨੀ ਅਤੇ ਪ੍ਰਬੰਧਨ ਕਰ ਸਕਦਾ ਹੈ।"</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"ਤੁਹਾਡਾ ਪ੍ਰਸ਼ਾਸਕ ਤੁਹਾਡੀ ਡੀਵਾਈਸ ਦੀਆਂ ਸੈਟਿੰਗਾਂ, ਕਾਰਪੋਰੇਟ ਪਹੁੰਚ, ਐਪਾਂ, ਡੈਟੇ ਅਤੇ ਟਿਕਾਣਾ ਜਾਣਕਾਰੀ ਦੀ ਨਿਗਰਾਨੀ ਅਤੇ ਪ੍ਰਬੰਧਨ ਕਰ ਸਕਦਾ ਹੈ।"</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"ਹੋਰ ਜਾਣੋ"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"ਤੁਸੀਂ <xliff:g id="VPN_APP">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੋ, ਜੋ ਈਮੇਲਾਂ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਨੇ ਨੈੱਟਵਰਕ ਲੌਗਿੰਗ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਹੋਇਆ ਹੈ, ਜੋ ਕਿ ਤੁਹਾਡੀ ਡੀਵਾਈਸ \'ਤੇ ਟਰੈਫਿਕ ਦੀ ਨਿਗਰਾਨੀ ਕਰਦਾ ਹੈ।\n\nਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਨੇ ਨੈੱਟਵਰਕ ਲੌਗਿੰਗ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਹੋਇਆ ਹੈ, ਜਿਸ ਨਾਲ ਤੁਹਾਡੀ ਡੀਵਾਈਸ \'ਤੇ ਟ੍ਰੈਫਿਕ ਦੀ ਨਿਗਰਾਨੀ ਹੁੰਦੀ ਹੈ।\n\nਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"ਤੁਸੀਂ ਇੱਕ ਐਪ ਨੂੰ ਇੱਕ VPN ਕਨੈਕਸ਼ਨ ਸੈਟ ਅਪ ਕਰਨ ਦੀ ਅਨੁਮਤੀ ਦਿੱਤੀ ਹੈ।\n\nਇਹ ਐਪ ਤੁਹਾਡੀ ਡੀਵਾਈਸ ਅਤੇ ਨੈੱਟਵਰਕ ਗਤੀਵਿਧੀ ਦਾ ਨਿਰੀਖਣ ਕਰ ਸਕਦਾ ਹੈ, ਈਮੇਲਾਂ, ਐਪਸ ਅਤੇ ਸੁਰੱਖਿਅਤ ਵੈਬਸਾਈਟਾਂ ਸਮੇਤ।"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"ਤੁਹਾਡੀ ਕਾਰਜ ਪ੍ਰੋਫ਼ਾਈਲ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ਦੁਆਰਾ ਵਿਵਸਥਿਤ ਕੀਤੀ ਜਾਂਦੀ ਹੈ।\n\nਤੁਹਾਡਾ ਪ੍ਰਸ਼ਾਸਕ ਈਮੇਲ, ਐਪਸ, ਅਤੇ ਵੈੱਬਪੰਨੇ ਸੰਤੇ ਤੁਹਾਡੀ ਨੈੱਟਵਰਕ ਗਤੀਵਿਧੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰਨ ਦੇ ਸਮਰੱਥ ਹੈ।\n\nਵਧੇਰੇ ਜਾਣਕਾਰੀ ਲਈ, ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।\n\nਤੁਸੀਂ VPN ਨਾਲ ਵੀ ਕਨੈਕਟ ਹੋ, ਜੋ ਤੁਹਾਡੀ ਨੈੱਟਵਰਕ ਗਤੀਵਿਧੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦਾ ਹੈ।"</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"ਤੁਹਾਡੇ ਕਾਰਜ ਪ੍ਰੋਫ਼ਾਈਲ ਦਾ ਪ੍ਰਬੰਧਨ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ਵੱਲੋਂ ਪ੍ਰਬੰਧਨ ਕੀਤਾ ਗਿਆ ਹੈ।\n\nਤੁਹਾਡਾ ਪ੍ਰਸ਼ਾਸਕ ਈਮੇਲ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰਨ ਵਿੱਚ ਸਮਰੱਥ ਹੈ।\n\nਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।\n\nਤੁਸੀਂ ਇੱਕ VPN ਨਾਲ ਵੀ ਕਨੈਕਟ ਹੋਂ, ਜੋ ਤੁਹਾਡੀ ਨੈਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦਾ ਹੈ।"</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"ਤੁਸੀਂ <xliff:g id="APPLICATION">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੋ, ਜੋ ਈਮੇਲ, ਐਪਸ ਅਤੇ ਵੈਬਸਫ਼ਿਆਂ ਸਮੇਤ ਤੁਹਾਡੀ ਨੈੱਟਵਰਕ ਗਤੀਵਿਧੀ ਦਾ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ।"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"ਤੁਸੀਂ <xliff:g id="APPLICATION">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੋ, ਜੋ ਈਮੇਲ, ਐਪਸ ਅਤੇ ਵੈਬਸਫ਼ਿਆਂ ਸਮੇਤ ਤੁਹਾਡੀ ਨੈੱਟਵਰਕ ਗਤੀਵਿਧੀ ਦਾ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ।"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"ਤੁਸੀਂ <xliff:g id="APPLICATION">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੋ, ਜੋ ਈਮੇਲਾਂ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਨਿੱਜੀ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"ਤੁਹਾਡੀ ਕਾਰਜ ਪ੍ਰੋਫ਼ਾਈਲ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ਦੁਆਰਾ ਵਿਵਸਥਿਤ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਇਹ <xliff:g id="APPLICATION">%2$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੈ, ਜੋ ਈਮੇਲ, ਐਪਸ ਅਤੇ ਵੈਬਸਫ਼ਿਆਂ ਸਮੇਤ ਤੁਹਾਡੀ ਨੈੱਟਵਰਕ ਗਤੀਵਿਧੀ ਦਾ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ।\n\nਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, ਆਪਣੇ ਪ੍ਰਬੰਧਕ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।"</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"ਤੁਹਾਡੇ ਕਾਰਜ ਪ੍ਰੋਫ਼ਾਈਲ ਦਾ ਪ੍ਰਬੰਧਨ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ਵੱਲੋਂ ਕੀਤਾ ਗਿਆ ਹੈ। ਇਹ <xliff:g id="APPLICATION">%2$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੈ, ਜੋ ਈਮੇਲਾਂ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਕਾਰਜ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।\n\nਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"ਤੁਹਾਡੀ ਕਾਰਜ ਪ੍ਰੋਫ਼ਾਈਲ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ਦੁਆਰਾ ਵਿਵਸਥਿਤ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਇਹ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੈ, ਜੋ ਈਮੇਲ, ਐਪਸ ਅਤੇ ਵੈਬਸਫ਼ਿਆਂ ਸਮੇਤ ਤੁਹਾਡੀ ਨੈੱਟਵਰਕ ਗਤੀਵਿਧੀ ਦਾ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ।\n\nਤੁਸੀਂ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ਨਾਲ ਵੀ ਕਨੈਕਟ ਹੋ, ਜੋ ਤੁਹਾਡੀ ਨਿੱਜੀ ਨੈੱਟਵਰਕ ਗਤੀਵਿਧੀ ਦਾ ਨਿਰੀਖਣ ਕਰ ਸਕਦਾ ਹੈ।"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"ਡੀਵਾਈਸ ਲੌਕ ਰਹੇਗੀ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਮੈਨੂਅਲੀ ਅਨਲੌਕ ਨਹੀਂ ਕਰਦੇ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ਤੇਜ਼ੀ ਨਾਲ ਸੂਚਨਾਵਾਂ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 456ed81..457f644 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -325,6 +325,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Ostrzeżenie: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Tryb pracy"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Podświetlenie nocne"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Brak ostatnich elementów"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Wszystko zostało wyczyszczone"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacje o aplikacji"</string>
@@ -338,6 +344,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Podziel poziomo"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Podziel pionowo"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Podziel niestandardowo"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Zamknij"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Otwórz"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Podziel ekran u góry"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Podziel ekran z lewej"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Podziel ekran z prawej"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Naładowana"</string>
@@ -418,20 +429,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Rozłącz z VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Twoim urządzeniem zarządza <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> używa aplikacji <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> do zarządzania Twoim urządzeniem."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administrator może monitorować ustawienia, dostęp firmowy, aplikacje, dane dotyczące urządzenia i lokalizacji oraz nimi zarządzać."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Więcej informacji"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Łączysz się z aplikacją <xliff:g id="VPN_APP">%1$s</xliff:g>, która może monitorować Twoją aktywność w sieci, w tym e-maile, aplikacje i strony internetowe."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Otwórz ustawienia VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administrator włączył rejestrowanie sieciowe, które pozwala monitorować ruch na Twoim urządzeniu.\n\nAby uzyskać więcej informacji, skontaktuj się z administratorem."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Aplikacja otrzymała od Ciebie uprawnienia do konfigurowania połączenia VPN.\n\nMoże ona monitorować Twoją aktywność na urządzeniu i w sieci, w tym e-maile, aplikacje i strony internetowe."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Twoim profilem do pracy zarządza <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministrator może monitorować Twoją aktywność w sieci, w tym e-maile, aplikacje i strony internetowe.\n\nSkontaktuj się z nim, by dowiedzieć się więcej.\n\nMasz też połączenie z siecią VPN, która może monitorować Twoją aktywność w sieci."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Masz połączenie z aplikacją <xliff:g id="APPLICATION">%1$s</xliff:g>, która może monitorować Twoją aktywność w sieci, w tym e-maile, aplikacje i strony internetowe."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Masz połączenie z aplikacją <xliff:g id="APPLICATION">%1$s</xliff:g>, która może monitorować Twoją prywatną aktywność w sieci, w tym e-maile, aplikacje i strony internetowe."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Masz połączenie z aplikacją <xliff:g id="APPLICATION">%1$s</xliff:g>, która może monitorować Twoją prywatną aktywność w sieci, w tym e-maile, aplikacje i strony internetowe."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Twoim profilem do pracy zarządza <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil jest połączony z aplikacją <xliff:g id="APPLICATION">%2$s</xliff:g>, która może monitorować Twoją aktywność w sieci związaną z pracą, w tym e-maile, aplikacje i strony internetowe.\n\nAby dowiedzieć się więcej, skontaktuj się z administratorem."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Twoim profilem do pracy zarządza <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil jest połączony z aplikacją <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, która może monitorować Twoją aktywność w sieci związaną z pracą, w tym e-maile, aplikacje i strony internetowe.\n\nMasz też połączenie z aplikacją <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, która może monitorować Twoją prywatną aktywność w sieci."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Urządzenie pozostanie zablokowane, aż odblokujesz je ręcznie"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Szybszy dostęp do powiadomień"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index e2a1818..7edebe0 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Aviso de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabalho"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Modo noturno"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"A NFC está desativada"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"A NFC está ativada"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nenhum item recente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Você limpou tudo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informações do app"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisão horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisão vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisão personalizada"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Dispensar"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Abrir"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir a tela para a parte superior"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir a tela para a esquerda"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir a tela para a direita"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Desconectar VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Seu dispositivo é gerenciado por <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> usa <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> para gerenciar seu dispositivo."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"O admin. pode monitorar e gerenciar config., acesso corporativo, apps, dados associados ao dispos. e as info. de local do dispos."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"O admin. pode monitorar e gerenciar config., acesso corporativo, apps, dados associados ao dispos. e info de local do dispositivo."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Saber mais"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Você está conectado a <xliff:g id="VPN_APP">%1$s</xliff:g>, que pode monitorar sua atividade na rede, incluindo e-mails, apps e websites."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Abrir configurações de VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Seu administrador ativou o registro de rede, que monitora o tráfego no seu dispositivo.\n\nPara ver mais informações, entre em contato com o administrador."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Seu administrador ativou o registro de rede, que monitora o tráfego no seu dispositivo.\n\nPara ver mais informações, entre em contato com o administrador."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Você deu permissão para um app configurar uma conexão VPN.\n\nEsse app pode monitorar seu dispositivo e a atividade na rede, incluindo e-mails, apps e websites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nSeu administrador pode monitorar suas atividades de rede, incluindo e-mails, apps e websites.\n\nPara mais informações, entre em contato com seu administrador.\n\nVocê também está conectado a uma VPN, a qual pode monitorar suas atividades de rede."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nSeu administrador pode monitorar sua atividade de rede, incluindo e-mails, apps e websites.\n\nPara ver mais informações, entre em contato com o administrador.\n\nVocê também está conectado a uma VPN, que pode monitorar sua atividade de rede."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Você está conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorar sua atividade na rede, incluindo e-mails, apps e websites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Você está conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorar sua atividade pessoal na rede, incluindo e-mails, apps e websites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Você está conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorar sua atividade pessoal na rede, incluindo e-mails, apps e websites."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ele está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorar sua atividade profissional na rede, incluindo e-mails, apps e websites.\n\nPara ver mais informações, entre em contato com seu administrador."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ele está conectado ao app <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorar sua atividade profissional de rede, incluindo e-mails, apps e websites.\n\nPara ver mais informações, entre em contato com o administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ele está conectado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pode monitorar sua atividade profissional na rede, incluindo e-mails, apps e websites.\n\nVocê também está conectado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode monitorar sua atividade pessoal na rede."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"O dispositivo permanecerá bloqueado até que você o desbloqueie manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Receba notificações mais rápido"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index f90e1ee..d7b40ff 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Aviso de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabalho"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Luz noturna"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"O NFC está desativado"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"O NFC está ativado"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nenhum item recente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Limpou tudo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informações da aplicação"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisão horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisão vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisão personalizada"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Ignorar"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Abrir"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ecrã dividido à parte superior"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ecrã dividido à esquerda"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ecrã dividido à direita"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Desligar VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"O seu dispositivo é gerido pelo <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"A <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> utiliza o <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> para gerir o seu dispositivo."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"O administ. pode monitorizar e gerir definições, acesso empresarial, aplic. e dados associados ao dispositivo, bem como inf. de localiz. do disp."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"O administ. pode monitorizar e gerir definições, acesso empresarial, aplic. e dados associados ao dispositivo, bem como inf. de localiz. do disp."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Saiba mais"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Está ligado à rede <xliff:g id="VPN_APP">%1$s</xliff:g>, que pode monitorizar a sua atividade de rede, incluindo emails, aplicações e Websites."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Abrir as definições de VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"O seu administrador ativou os registos de rede, que monitorizam o tráfego no seu dispositivo.\n\nPara mais informações, contacte o administrador."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"O seu administrador ativou os registos de rede, que monitorizam o tráfego no seu dispositivo.\n\nPara obter mais informações, contacte o administrador."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Concedeu autorização a uma aplicação para configurar uma ligação VPN.\n\nEsta aplicação pode monitorizar a atividade do dispositivo e da rede, incluindo emails, aplicações e Websites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"O seu perfil de trabalho é gerido por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nO seu administrador pode monitorizar a atividade da rede, incluindo emails, aplicações e Websites.\n\nPara obter mais informações, contacte o administrador.\n\nAlém disso, está ligado a uma VPN, que pode monitorizar a atividade da rede."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"O seu perfil de trabalho é gerido por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nO seu administrador tem a capacidade de monitorizar a sua atividade da rede, incluindo emails, aplicações e Websites.\n\nPara obter mais informações, contacte o administrador.\n\nAlém disso, está ligado a uma VPN, que pode monitorizar a sua atividade da rede."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Está ligado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a atividade da rede, incluindo emails, aplicações e Websites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Está ligado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a atividade da rede pessoal, incluindo emails, aplicações e Websites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Está ligado ao <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a atividade da rede pessoal, incluindo emails, aplicações e Websites."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"O seu perfil de trabalho é gerido por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Está ligado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Websites.\n\nPara obter mais informações, contacte o administrador."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"O seu perfil de trabalho é gerido por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Está associado à aplicação <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorizar a sua atividade da rede de trabalho, incluindo emails, aplicações e Websites.\n\nPara obter mais informações, contacte o administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"O seu perfil de trabalho é gerido por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Está ligado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Websites.\n\nTambém está ligado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode monitorizar a atividade da rede pessoal."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"O dispositivo permanecerá bloqueado até ser desbloqueado manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Receber notificações mais rapidamente"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index e2a1818..7edebe0 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Aviso de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modo de trabalho"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Modo noturno"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"A NFC está desativada"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"A NFC está ativada"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nenhum item recente"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Você limpou tudo"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informações do app"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisão horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisão vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisão personalizada"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Dispensar"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Abrir"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir a tela para a parte superior"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir a tela para a esquerda"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir a tela para a direita"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Desconectar VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Seu dispositivo é gerenciado por <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> usa <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> para gerenciar seu dispositivo."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"O admin. pode monitorar e gerenciar config., acesso corporativo, apps, dados associados ao dispos. e as info. de local do dispos."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"O admin. pode monitorar e gerenciar config., acesso corporativo, apps, dados associados ao dispos. e info de local do dispositivo."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Saber mais"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Você está conectado a <xliff:g id="VPN_APP">%1$s</xliff:g>, que pode monitorar sua atividade na rede, incluindo e-mails, apps e websites."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Abrir configurações de VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Seu administrador ativou o registro de rede, que monitora o tráfego no seu dispositivo.\n\nPara ver mais informações, entre em contato com o administrador."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Seu administrador ativou o registro de rede, que monitora o tráfego no seu dispositivo.\n\nPara ver mais informações, entre em contato com o administrador."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Você deu permissão para um app configurar uma conexão VPN.\n\nEsse app pode monitorar seu dispositivo e a atividade na rede, incluindo e-mails, apps e websites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nSeu administrador pode monitorar suas atividades de rede, incluindo e-mails, apps e websites.\n\nPara mais informações, entre em contato com seu administrador.\n\nVocê também está conectado a uma VPN, a qual pode monitorar suas atividades de rede."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nSeu administrador pode monitorar sua atividade de rede, incluindo e-mails, apps e websites.\n\nPara ver mais informações, entre em contato com o administrador.\n\nVocê também está conectado a uma VPN, que pode monitorar sua atividade de rede."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Você está conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorar sua atividade na rede, incluindo e-mails, apps e websites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Você está conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorar sua atividade pessoal na rede, incluindo e-mails, apps e websites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Você está conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorar sua atividade pessoal na rede, incluindo e-mails, apps e websites."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ele está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorar sua atividade profissional na rede, incluindo e-mails, apps e websites.\n\nPara ver mais informações, entre em contato com seu administrador."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ele está conectado ao app <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorar sua atividade profissional de rede, incluindo e-mails, apps e websites.\n\nPara ver mais informações, entre em contato com o administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ele está conectado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pode monitorar sua atividade profissional na rede, incluindo e-mails, apps e websites.\n\nVocê também está conectado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode monitorar sua atividade pessoal na rede."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"O dispositivo permanecerá bloqueado até que você o desbloqueie manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Receba notificações mais rápido"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 8dca94e..0cfd530 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -325,6 +325,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Avertizare: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modul de lucru"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Lumină de noapte"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"Serviciul NFC este dezactivat"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"Serviciul NFC este activat"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Niciun element recent"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Ați șters tot"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informații despre aplicație"</string>
@@ -338,6 +341,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divizare pe orizontală"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divizare pe verticală"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divizare personalizată"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Respingeți"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Deschideți"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Divizați ecranul în partea de sus"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Divizați ecranul la stânga"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Divizați ecranul la dreapta"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Încărcată"</string>
@@ -418,20 +426,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Deconectați rețeaua VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Dispozitivul dvs. este gestionat de <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> folosește <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> pentru a vă gestiona dispozitivul."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administratorul dvs. poate monitoriza și gestiona setările, accesul la nivelul companiei, aplicațiile, datele asociate cu dispozitivul dvs. și informațiile despre locația dispozitivului."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Admin. dvs. poate monit. și gest. set., accesul la niv. companiei, ap., datele asoc. cu disp. dvs. și info. despre locația disp."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Aflați mai multe"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"V-ați conectat la aplicația <xliff:g id="VPN_APP">%1$s</xliff:g>, care vă poate monitoriza activitatea în rețea, inclusiv e-mailurile, aplicațiile și site-urile accesate."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Deschideți Setări VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administratorul dvs. a activat înregistrarea în jurnal pentru rețea, funcție ce monitorizează traficul de pe dispozitivul dvs.\n\nPentru mai multe informații, contactați administratorul."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Administratorul dvs. a activat înregistrarea în jurnal pentru rețea, funcție ce monitorizează traficul de pe dispozitivul dvs.\n\nPentru mai multe informații, contactați administratorul."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Ați acordat unei aplicații permisiunea de a configura o conexiune VPN.\n\nAceastă aplicație poate monitoriza activitatea de pe dispozitiv și în rețea, inclusiv e-mailurile, aplicațiile și site-urile."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Profilul de serviciu este gestionat de <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministratorul poate monitoriza activitatea în rețea, inclusiv e-mailurile, aplicațiile și site-urile.\n\nPentru mai multe informații, contactați administratorul.\n\nDe asemenea, sunteți conectat(ă) la o rețea VPN, care vă poate monitoriza activitatea în rețea."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Profilul dvs. de serviciu este gestionat de <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministratorul dvs. vă poate monitoriza activitatea în rețea, inclusiv e-mailurile, aplicațiile și site-urile.\n\nPentru mai multe informații, contactați administratorul.\n\nDe asemenea, sunteți conectat(ă) la o rețea VPN care vă poate monitoriza activitatea în rețea."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Sunteți conectat(ă) la <xliff:g id="APPLICATION">%1$s</xliff:g>, care vă poate monitoriza activitatea în rețea, inclusiv e-mailurile, aplicațiile și site-urile."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Sunteți conectat(ă) la <xliff:g id="APPLICATION">%1$s</xliff:g>, care vă poate monitoriza activitatea în rețeaua personală, inclusiv e-mailurile, aplicațiile și site-urile."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"V-ați conectat la aplicația <xliff:g id="APPLICATION">%1$s</xliff:g>, care vă poate monitoriza activitatea personală în rețea, inclusiv e-mailurile, aplicațiile și site-urile accesate."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Profilul de serviciu este gestionat de <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Este conectat la <xliff:g id="APPLICATION">%2$s</xliff:g>, care vă poate monitoriza activitatea în rețeaua de serviciu, inclusiv e-mailurile, aplicațiile și site-urile.\n\nPentru mai multe informații, contactați administratorul."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Profilul dvs. de serviciu este gestionat de <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Acesta este conectat la <xliff:g id="APPLICATION">%2$s</xliff:g>, care vă poate monitoriza activitatea în rețeaua de serviciu, inclusiv e-mailurile, aplicațiile și site-urile.\n\nPentru mai multe informații, contactați administratorul."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Profilul de serviciu este gestionat de <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Este conectat la <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, care vă poate monitoriza activitatea în rețeaua de serviciu, inclusiv e-mailurile, aplicațiile și site-urile.\n\nDe asemenea, sunteți conectat(ă) la <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, care vă poate monitoriza activitatea în rețeaua personală."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Dispozitivul va rămâne blocat până când îl deblocați manual"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Obțineți notificări mai rapid"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 9e046ed..0686ca6 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -327,6 +327,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Предупреждение: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Рабочий режим"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Ночной режим"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"Модуль NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"Модуль NFC отключен"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"Модуль NFC включен"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Недавних приложений нет"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Вы очистили всё"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Сведения о приложении"</string>
@@ -340,6 +343,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Разделить по горизонтали"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Разделить по вертикали"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Разделить по-другому"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Закрыть"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Открыть"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Разделить экран по верхнему краю"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Разделить экран по левому краю"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Разделить экран по правому краю"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Батарея заряжена"</string>
@@ -420,20 +428,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Отключить VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Этим устройством управляет приложение \"<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>\""</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"Компания \"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>\" управляет устройством с помощью приложения \"<xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>\""</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Администратор контролирует настройки, приложения, доступ к ресурсам компании, связанные с устройством данные и передачу геоданных."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Администратор контролирует настройки, приложения, доступ к ресурсам компании, связанные с устройством данные и передачу геоданных."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Подробнее…"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Запущено приложение \"<xliff:g id="VPN_APP">%1$s</xliff:g>\". Оно может отслеживать ваши действия в сети, включая работу с электронной почтой, приложениями и сайтами."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Открыть настройки VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Администратор включил ведение сетевого журнала, чтобы отслеживать трафик на вашем устройстве.\n\nДля получения подробной информации обращайтесь к администратору."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Администратор включил ведение сетевого журнала, чтобы отслеживать трафик на вашем устройстве.\n\nДля получения подробной информации обращайтесь к администратору."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Вы разрешили приложению подключаться к сети VPN.\n\nОно может отслеживать ваши действия на устройстве и в Интернете, включая работу с электронной почтой, приложениями и веб-сайтами."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Вашим корпоративным профилем управляет <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nАдминистратор может отслеживать ваши действия в Интернете, включая работу с электронной почтой, приложениями и веб-сайтами.\n\nЗа подробностями обратитесь к нему.\n\nУстройство также подключено к сети VPN, в которой возможно отслеживание ваших действий."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Вашим корпоративным профилем управляет <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nАдминистратор может отслеживать ваши действия в сети, в том числе с электронной почтой, приложениями и веб-сайтами.\n\nДля получения подробной информации обращайтесь к администратору.\n\nВы также подключены к сети VPN, в которой можно отслеживать ваши действия."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"Сеть VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Запущено приложение \"<xliff:g id="APPLICATION">%1$s</xliff:g>\", которое может отслеживать ваши действия в Интернете, включая работу с электронной почтой, приложениями и веб-сайтами."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Запущено приложение \"<xliff:g id="APPLICATION">%1$s</xliff:g>\", которое может отслеживать ваши действия в Интернете (выполняемые в личном профиле), включая работу с электронной почтой, приложениями и веб-сайтами."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Запущено приложение \"<xliff:g id="APPLICATION">%1$s</xliff:g>\", которое может отслеживать ваши действия в сети, включая работу с электронной почтой, приложениями и веб-сайтами."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Вашим рабочим профилем управляет \"<xliff:g id="ORGANIZATION">%1$s</xliff:g>\". Приложение \"<xliff:g id="APPLICATION">%2$s</xliff:g>\" может отслеживать ваши действия в Интернете, включая работу с электронной почтой, приложениями и веб-сайтами.\n\nЗа дополнительной информацией обратитесь к своему администратору."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Вашим корпоративным профилем управляет <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Приложение <xliff:g id="APPLICATION">%2$s</xliff:g> может отслеживать ваши действия в сети, включая работу с электронной почтой, приложениями и веб-сайтами.\n\nЗа подробностями обратитесь к своему администратору."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Вашим рабочим профилем управляет \"<xliff:g id="ORGANIZATION">%1$s</xliff:g>\". Приложение \"<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>\" может отслеживать ваши действия в Интернете, включая работу с электронной почтой, приложениями и веб-сайтами.\n\nПриложение \"<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>\" может отслеживать ваши действия в Интернете, выполняемые в личном профиле."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Устройство необходимо будет разблокировать вручную"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Быстрый доступ к уведомлениям"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 7728570..2c752f2 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> අවවාද කිරීම"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"වැඩ ප්රකාරය"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"රාත්රී ආලෝකය"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"මෑත අයිතම නැත"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"ඔබ සියලු දේ හිස් කර ඇත"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"යෙදුම් තොරතුරු"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"තිරස්ව වෙන් කරන්න"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"සිරස්ව වෙන් කරන්න"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"අභිමත ලෙස වෙන් කරන්න"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"අස් කරන්න"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"විවෘත"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"තිරය ඉහළට බෙදන්න"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"තිරය වමට බෙදන්න"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"තිරය දකුණට බෙදන්න"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"අරෝපිතයි"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN විසන්ධි කරන්න"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"ඔබගේ උපාංගය <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> මගින් කළමනාකරණය කෙරේ."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> ඔබගේ උපාංගය කළමනාකරණය කිරීමට <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> භාවිත කරයි."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"ඔබේ උපාංගය සමඟ සම්බන්ධිත සැකසීම්, සංස්ථාපිත ප්රවේශය, යෙදුම්, දත්ත, සහ ඔබගේ උපාංගයේ ස්ථාන තොරතුරු නිරීක්ෂණය සහ කළමනාකරණය කිරීමට ඔබගේ පරිපාලකට හැකිය."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"තව දැන ගන්න"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"ඊ-තැපැල්, යෙදුම් සහ වෙබ් අඩවි ඇතුළු ඔබේ ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි <xliff:g id="VPN_APP">%1$s</xliff:g>, වෙත ඔබ සම්බන්ධ වී ඇත."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN සැකසීම් විවෘත කරන්න"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"ඔබගේ පරිපාලක ඔබගේ උපාංගය මත තදබදය නිරීක්ෂණය කරන, ජාල ඇතුළු වීම ක්රියාත්මක කර ඇත.\n\nවැඩිදුර තොරතුරු සඳහා ඔබේ පරිපාලක අමතන්න."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"ඔබ VPN සම්බන්ධතාවක් පිහිටුවීමට යෙදුමකට අවසරයක් දී ඇත.\n\nමෙම යෙදුමට ඔබේ ඊ-තැපැල්, යෙදුම්, සහ වෙබ් අඩවි ඇතුළු, ඔබගේ උපාංග සහ ජාල ක්රියාකාරකම් නිරීක්ෂණය කිරීමට හැකිය."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"ඔබේ කාර්ය පැතිකඩ කළමනාකරණය කරන්නේ <xliff:g id="ORGANIZATION">%1$s</xliff:g> විසිනි.\n\nඔබේ පරිපාලකට ඔබේ ඊ-තැපැල්, යෙදුම්, සහ ආරක්ෂාකාරී වෙබ් අඩවි ඇතුළු, ඔබගේ ජාල ක්රියාකාරකම් නිරීක්ෂණය කිරීමට හැකියාව ඇත.\n\nවැඩිදුර තොරතුරු සඳහා, ඔබගේ පරිපාලක අමතන්න.\n\nඔබේ ජාල ක්රියාකාරකම් නිරීක්ෂණය කිරීමට හැකි VPN සම්බන්ධතාවයකටද, ඔබ සම්බන්ධව ඇත."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"ඊ-තැපැල්, යෙදුම් සහ වෙබ් අඩවි ඇතුළු ඔබේ ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION">%1$s</xliff:g> වෙත ඔබ සම්බන්ධ වී ඇත."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"ඊ-තැපැල්, යෙදුම් සහ වෙබ් අඩවි ඇතුළු ඔබේ පෞද්ගලික ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION">%1$s</xliff:g> වෙත ඔබ සම්බන්ධ වී ඇත."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"ඊ-තැපැල්, යෙදුම් සහ වෙබ් අඩවි ඇතුළු ඔබේ පෞද්ගලික ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION">%1$s</xliff:g> වෙත ඔබ සම්බන්ධ වී ඇත."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"ඔබේ කාර්යාල පැතිකඩ කළමනාකරණය කරන්නේ <xliff:g id="ORGANIZATION">%1$s</xliff:g> විසිනි. එය ඊ-තැපැල්, යෙදුම් සහ වෙබ් අඩවි ඇතුළු ඔබේ කාර්යාල ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION">%2$s</xliff:g> වෙත ඔබ සම්බන්ධ වී ඇත.\n\nවැඩිදුර විස්තර සඳහා, ඔබේ පරිපාලක අමතන්න."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"ඔබේ කාර්යාල පැතිකඩ කළමනාකරණය කරන්නේ <xliff:g id="ORGANIZATION">%1$s</xliff:g> විසිනි. එය ඊ-තැපැල්, යෙදුම් සහ වෙබ් අඩවි ඇතුළු ඔබේ කාර්යාල ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, වෙත ඔබ සම්බන්ධ වී ඇත.\n\nඔබ ඔබේ පෞද්ගලික ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> වෙතද සම්බන්ධ වී ඇත."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"ඔබ අතින් අගුළු අරින තුරු උපකරණය අගුළු වැටි තිබේ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"දැනුම්දීම් ඉක්මනින් ලබාගන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index f19b2a1..4013f41 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -327,6 +327,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Upozornenie pri <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Pracovný režim"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nočný režim"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC je deaktivované"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC je aktivované"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Žiadne nedávne položky"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vymazali ste všetko"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informácie o aplikácii"</string>
@@ -340,6 +343,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Rozdeliť vodorovné"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Rozdeliť zvislé"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Rozdeliť vlastné"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Odmietnuť"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Otvoriť"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Rozdelená obrazovka hore"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Rozdelená obrazovka naľavo"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Rozdelená obrazovka napravo"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Nabitá"</string>
@@ -420,20 +428,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Odpojiť sieť VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Vaše zariadenie spravuje aplikácia <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> spravuje vaše zariadenie pomocou aplikácie <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Správca môže sledovať a spravovať nastavenia, firemný prístup, aplikácie a údaje súvisiace s týmto zariadením vrátane informácií o polohe vášho zariadenia."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Správca môže sledovať a spravovať nastavenia, firemný prístup, aplikácie a údaje súvisiace s týmto zariadením vrátane informácií o polohe vášho zariadenia."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Ďalšie informácie"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Ste pripojený/-á k aplikácii <xliff:g id="VPN_APP">%1$s</xliff:g>, ktorá môže sledovať vašu aktivitu v sieti vrátane e-mailových správ, aplikácií a webových stránok."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Otvoriť Nastavenia pripojenia VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Váš správca aktivoval zapisovanie do denníka siete, ktoré sleduje premávku na vašom zariadení.\n\nĎalšie informácie vám poskytne správca."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Správca aktivoval zapisovanie do denníka siete, ktoré sleduje premávku na vašom zariadení.\n\nĎalšie informácie vám poskytne správca."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Určitej aplikácii ste udelili povolenie nastaviť pripojenie VPN.\n\nTáto aplikácia môže sledovať vaše zariadenie a aktivitu v sieti vrátane e-mailových správ, aplikácií a webových stránok."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Váš pracovný profil spravuje organizácia <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nSprávca môže sledovať vašu aktivitu v sieti vrátane e-mailových správ, aplikácií a webových stránok.\n\nĎalšie informácie získate od svojho správcu.\n\nSte tiež pripojený/-á k sieti VPN, ktorá môže sledovať vašu aktivitu v sieti."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Váš pracovný profil spravuje organizácia <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nSprávca môže sledovať vašu aktivitu v sieti vrátane e-mailov, aplikácií a webov.\n\nĎalšie informácie vám poskytne správca.\n\nMáte tiež aktívne pripojenie k sieti VPN, ktorá môže sledovať vašu aktivitu v rámci siete."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Ste pripojený/-á k aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g>, ktorá môže sledovať vašu aktivitu v sieti vrátane e-mailových správ, aplikácií a webových stránok."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Ste pripojený/-á k aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g>, ktorá môže sledovať vašu osobnú aktivitu v sieti vrátane e-mailových správ, aplikácií a webových stránok."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Ste pripojený/-á k aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g>, ktorá môže sledovať vašu osobnú aktivitu v sieti vrátane e-mailových správ, aplikácií a webových stránok."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Váš pracovný profil spravuje organizácia <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Je pripojený k aplikácii <xliff:g id="APPLICATION">%2$s</xliff:g>, ktorá môže sledovať vašu pracovnú aktivitu v sieti vrátane e-mailových správ, aplikácií a webových stránok.\n\nĎalšie informácie získate od svojho správcu."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Váš pracovný profil spravuje organizácia <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Je pripojený k aplikácii <xliff:g id="APPLICATION">%2$s</xliff:g>, ktorá môže sledovať vašu pracovnú aktivitu v sieti vrátane e-mailových správ, aplikácií a webov.\n\nĎalšie informácie vám poskytne správca."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Váš pracovný profil spravuje organizácia <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Je pripojený k aplikácii <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ktorá môže sledovať vašu pracovnú aktivitu v sieti vrátane e-mailových správ, aplikácií a webových stránok.\n\nSte tiež pripojený/-á k aplikácii <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, ktorá môže sledovať vašu osobnú aktivitu v sieti."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Zariadenie zostane uzamknuté, dokým ho ručne neodomknete."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Získavať upozornenia rýchlejšie"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 67eec2f..9236970 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -327,6 +327,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Opozorilo – <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Način za delo"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nočna svetloba"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Ni nedavnih elementov"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Vse te počistili"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Podatki o aplikaciji"</string>
@@ -340,6 +346,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Razdeli vodoravno"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Razdeli navpično"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Razdeli po meri"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Opusti"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Odpri"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Razdeljen zaslon na vrhu"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Razdeljen zaslon na levi"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Razdeljen zaslon na desni"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Akumulator napolnjen"</string>
@@ -420,20 +431,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Prekini povezavo z VPN-jem"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Napravo upravlja aplikacija <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> uporablja aplikacijo <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> za upravljanje naprave."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Skrbnik lahko nadzira in upravlja nastavitve, dostop za podjetje, aplikacije, z napravo povezane podatke in podatke o lokaciji."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Več o tem"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Povezani ste z aplikacijo <xliff:g id="VPN_APP">%1$s</xliff:g>, ki lahko nadzira omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Odpri nastavitve omrežja VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Skrbnik je vklopil beleženje omrežnega prometa, ki nadzoruje promet v napravi.\n\nČe želite več informacij, se obrnite na skrbnika."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Aplikaciji ste dovolili vzpostavitev povezave VPN.\n\nTa aplikacija lahko nadzira napravo in omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Delovni profil upravlja organizacija <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nSkrbnik lahko nadzira omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti.\n\nČe želite več informacij, se obrnite na skrbnika.\n\nPovezani ste tudi z omrežjem VPN, ki lahko nadzira omrežno dejavnost."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Povezani ste z aplikacijo <xliff:g id="APPLICATION">%1$s</xliff:g>, ki lahko nadzira omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Povezani ste z aplikacijo <xliff:g id="APPLICATION">%1$s</xliff:g>, ki lahko nadzira vašo osebno omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Povezani ste z aplikacijo <xliff:g id="APPLICATION">%1$s</xliff:g>, ki lahko nadzira vašo osebno omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Delovni profil upravlja organizacija <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je z aplikacijo <xliff:g id="APPLICATION">%2$s</xliff:g>, ki lahko nadzira vašo delovno omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti.\n\nČe želite več informacij, se obrnite na skrbnika."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Delovni profil upravlja organizacija <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je z aplikacijo <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ki lahko nadzira vašo delovno omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti.\n\nPovezani ste tudi z aplikacijo <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, ki lahko nadzira vašo osebno omrežno dejavnost."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Naprava bo ostala zaklenjena, dokler je ročno ne odklenete."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Hitrejše prejemanje obvestil"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index f4d83c9..40b7c51d 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Paralajmërim për kufirin prej <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Modaliteti i punës"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Drita e natës"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC është çaktivizuar"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC është aktivizuar"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Nuk ka asnjë artikull të fundit"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"I ke pastruar të gjitha"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacioni i aplikacionit"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Ndaje horizontalisht"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Ndaj vertikalisht"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Ndaj të personalizuarën"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Hiqe"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Hap"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ndaje ekranin lart"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ndaje ekranin në të majtë"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ndaje ekranin në të djathtë"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"I ngarkuar"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Shkëput VPN-në"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Pajisja jote menaxhohet nga <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> përdor <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> për të menaxhuar pajisjen tënde."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administratori yt mund të monitorojë dhe të menaxhojë cilësimet, qasjen e korporatës, aplikacionet, të dhënat në lidhje me pajisjen si dhe informacionet e vendndodhjes së pajisjes tënde."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Administratori yt mund të monitorojë dhe të menaxhojë cilësimet, qasjen e korporatës, aplikacionet, të dhënat në lidhje me pajisjen si dhe informacionet e vendndodhjes së pajisjes tënde."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Mëso më shumë"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Je i lidhur me aplikacionin <xliff:g id="VPN_APP">%1$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd në rrjet, duke përfshirë mail-et, aplikacionet dhe sajtet e uebit."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Hap cilësimet e VPN-së"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administratori yt ka aktivizuar regjistrimin e rrjetit, i cili monitoron trafikun në pajisjen tënde.\n\nPër më shumë informacione, kontakto me administratorin."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Administratori yt ka aktivizuar regjistrimin e rrjetit, i cili monitoron trafikun në pajisjen tënde.\n\nPër më shumë informacione, kontakto me administratorin."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"I dhe leje një aplikacioni që të konfigurojë një lidhje VPN.\n\nKy aplikacion mund të monitorojë pajisjen tënde dhe aktivitetin e rrjetit, përfshirë mailet, aplikacionet dhe sajtet e uebit."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Profili yt i punës menaxhohet nga <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministratori yt mund të monitorojë aktivitetin tënd të rrjetit, duke përfshirë mail-at, aplikacionet dhe faqet e internetit.\n\nPër më shumë informacion, kontakto me administratorin tënd.\n\nJe i lidhur edhe me një VPN, që mund të monitorojë aktivitetin tënd të rrjetit."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Profili yt i punës menaxhohet nga <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministratori yt mund të monitorojë aktivitetin tënd të rrjetit, duke përfshirë email-et, aplikacionet dhe sajtet e uebit.\n\nPër më shumë informacion, kontakto me administratorin tënd.\n\nJe i lidhur edhe me një VPN, që mund të monitorojë aktivitetin tënd të rrjetit."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Je i lidhur me aplikacionin <xliff:g id="APPLICATION">%1$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd në rrjet përfshirë mailet, aplikacionet dhe sajtet e uebit."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Je i lidhur me aplikacionin <xliff:g id="APPLICATION">%1$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd personal në rrjet, përfshirë mailet, aplikacionet dhe sajtet e uebit."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Je i lidhur me aplikacionin <xliff:g id="APPLICATION">%1$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd personal në rrjet, përfshirë mailet, aplikacionet dhe sajtet e uebit."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Profili yt i punës menaxhohet nga <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ai është i lidhur me <xliff:g id="APPLICATION">%2$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd të punës në rrjet, përfshirë mailet, aplikacionet dhe sajtet e uebit.\n\nPër më shumë informacione, kontakto me administratorin tënd."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Profili yt i punës menaxhohet nga <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ai është i lidhur me <xliff:g id="APPLICATION">%2$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd të punës në rrjet, përfshirë email-et, aplikacionet dhe sajtet e uebit.\n\nPër më shumë informacione, kontakto me administratorin tënd."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Profili yt i punës menaxhohet nga <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ai është i lidhur me <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd të punës në rrjet, përfshirë mailet, aplikacionet dhe sajtet e uebit.\n\nJe lidhur gjithashtu edhe me <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd personal në rrjet."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Pajisje do të qëndrojë e kyçur derisa ta shkyçësh manualisht"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Merr njoftime më shpejt"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index ffb5312..d329f1b 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Упозорење за <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Режим рада"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Ноћно светло"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC је онемогућен"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC је омогућен"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Нема недавних ставки"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Обрисали сте све"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Информације о апликацији"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Подели хоризонтално"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Подели вертикално"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Прилагођено дељење"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Одбаци"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Отвори"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Подели екран нагоре"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Подели екран налево"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Подели екран надесно"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Напуњена је"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Прекини везу са VPN-ом"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Уређајем управља <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> користи <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> за управљање уређајем."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Администратор може да надгледа подешавања, корпоративни приступ, апликације, податке повезане са уређајем и информације о локацији уређаја, као и да управља њима."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Администратор може да надгледа подешавања, корпоративни приступ, апликације, податке повезане са уређајем и информације о локацији уређаја, као и да управља њима."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Сазнајте више"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Повезани сте са апликацијом <xliff:g id="VPN_APP">%1$s</xliff:g>, која може да надгледа активности на мрежи, укључујући имејлове, апликације и веб-сајтове."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Отворите подешавања VPN-а"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Администратор је укључио евидентирање мреже, које прати саобраћај на уређају.\n\nКонтактирајте администратора за више информација."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Администратор је укључио евидентирање мреже, које прати саобраћај на уређају.\n\nКонтактирајте администратора за више информација."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Дали сте дозволу апликацији да подешава VPN везу.\n\nТа апликација може да надгледа активности на уређају и мрежи, укључујући имејлове, апликације и веб-сајтове."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Профилом за Work управља <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nАдминистратор може да надгледа активности на мрежи, укључујући имејлове, апликације и веб-сајтове.\n\nВише информација потражите од администратора.\n\nПовезани сте и на VPN, који може да надгледа активности на личној мрежи."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> управља профилом за Work.\n\nАдминистратор може да прати активности на мрежи, укључујући имејлове, апликације и веб-сајтове.\n\nКонтактирајте администратора за више информација.\n\nПовезани сте и са VPN-ом, који може да прати активности на мрежи."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Повезани сте са апликацијом <xliff:g id="APPLICATION">%1$s</xliff:g>, која може да надгледа активности на мрежи, укључујући имејлове, апликације и веб-сајтове."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Повезани сте са апликацијом <xliff:g id="APPLICATION">%1$s</xliff:g>, која може да надгледа активности на личној мрежи, укључујући имејлове, апликације и веб-сајтове."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Повезани сте са апликацијом <xliff:g id="APPLICATION">%1$s</xliff:g>, која може да надгледа активности на личној мрежи, укључујући имејлове, апликације и веб-сајтове."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Профилом за Work управља <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Повезан је са апликацијом <xliff:g id="APPLICATION">%2$s</xliff:g>, која може да надгледа активности на пословној мрежи, укључујући имејлове, апликације и веб-сајтове.\n\nВише информација потражите од администратора."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> управља профилом за Work. Он је повезан са апликацијом <xliff:g id="APPLICATION">%2$s</xliff:g>, која може да прати активности на пословној мрежи, укључујући имејлове, апликације и веб-сајтове.\n\nКонтактирајте администратора за више информација."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Профилом за Work управља <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Повезан је са апликацијом <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, која може да надгледа активности на пословној мрежи, укључујући имејлове, апликације и веб-сајтове.\n\nПовезани сте и са апликацијом <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, која може да надгледа активности на личној мрежи."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Уређај ће остати закључан док га не откључате ручно"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Брже добијајте обавештења"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 153b4c5..4b58ff9 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Varning <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Arbetsläge"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Nattljus"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Listan med de senaste åtgärderna är tom"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Du har tömt listan"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Appinformation"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Dela horisontellt"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dela vertikalt"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Dela anpassad"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Ignorera"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Öppna"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Delad skärm till överkanten"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Delad skärm åt vänster"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Delad skärm åt höger"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Laddat"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Koppla från VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Enheten hanteras av <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> hanterar enheten med hjälp av <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administratören kan övervaka och hantera inställningar, företagsåtkomst, appar, data med koppling till enheten och enhetens plats."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Läs mer"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Du är ansluten till <xliff:g id="VPN_APP">%1$s</xliff:g> som kan övervaka din aktivitet på nätverket, inklusive e-postmeddelanden, appar och webbplatser."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Öppna VPN-inställningar"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administratören har aktiverat nätverksloggning, vilken övervakar trafik på enheten.\n\nKontakta administratören om du vill veta mer."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Du har gett en app behörighet att upprätta en VPN-anslutning.\n\nAppen kan bevaka aktivitet på enheten och nätverket, inklusive e-post, appar och webbplatser."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Jobbprofilen hanteras av <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nAdministratören kan bevaka aktiviteten på nätverket, inklusive e-post, appar och webbplatser.\n\nKontakta administratören för mer information.\n\nDu är även ansluten till ett VPN-nätverk som kan bevaka aktiviteten på nätverket."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Du är ansluten till <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan bevaka aktivitet på nätverket, inklusive e-post, appar och webbplatser."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Du är ansluten till <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan bevaka din privata aktivitet på nätverket, inklusive e-post, appar och webbplatser."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Du är ansluten till <xliff:g id="APPLICATION">%1$s</xliff:g> som kan övervaka din privata aktivitet på nätverket, inklusive e-postmeddelanden, appar och webbplatser."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Jobbprofilen hanteras av <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Den är ansluten till <xliff:g id="APPLICATION">%2$s</xliff:g>, som kan hantera aktivitet på arbetsplatsens nätverk, inklusive e-post, appar och webbplatser.\n\nKontakta administratören för mer information."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Jobbprofilen hanteras av <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Den är ansluten till <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, som kan hantera aktivitet på arbetsplatsens nätverk, inklusive e-post, appar och webbplatser.\n\nDu är även ansluten till <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, som kan hantera privat aktivitet på nätverket."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Enheten förblir låst tills du låser upp den manuellt"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Få aviseringar snabbare"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 9793e12..d8a4d42 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Onyo <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Hali ya kazi"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Mwanga wa Usiku"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC imezimwa"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC imewashwa"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Hakuna vipengee vya hivi karibuni"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Umeondoa vipengee vyote"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Maelezo ya Programu"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Gawanya Mlalo"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Gawanya Wima"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Maalum Iliyogawanywa"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Ondoa"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Fungua"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Gawa skrini kuelekea juu"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Gawa skrini upande wa kushoto"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Gawa skrini upande wa kulia"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Betri imejaa"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Ondoa VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Kifaa chako kinadhibitiwa na <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> inatumia <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> kudhibiti kifaa chako."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Msimamizi wako anaweza kufuatilia na kudhibiti mipangilio, ufikiaji wa mashirika, programu, data inayohusiana na kifaa chako na maelezo ya eneo la kifaa chako."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Msimamizi wako anaweza kufuatilia na kudhibiti mipangilio, ufikiaji wa mashirika, programu, data inayohusiana na kifaa chako na maelezo ya eneo la kifaa chako."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Pata maelezo zaidi"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Umeunganishwa kwenye <xliff:g id="VPN_APP">%1$s</xliff:g>, ambayo inaweza kufuatilia shughuli za mtandao wako, ikiwa ni pamoja na barua pepe, programu na tovuti."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Fungua Mipangilio ya VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Msimamizi wako amewasha kumbukumbu ya kuingia mtandaoni ambayo huchunguza trafiki kwenye kifaa chako.\n\nIli kupata maelezo zaidi, wasiliana na msimamizi wako."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Msimamizi wako amewasha kumbukumbu ya kuingia mtandaoni ambayo hufuatilia trafiki kwenye kifaa chako.\n\nKwa maelezo zaidi, wasiliana na msimamizi wako."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Uliruhusu programu iweke muunganisho wa VPN.\n\nProgramu hii inaweza kufuatilia shughuli za kifaa na mtandao wako, ikiwa ni pamoja na barua pepe, programu na tovuti."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Wasifu wako wa kazini unasimamiwa na <xliff:g id="ORGANIZATION">%1$s</xliff:g>. \n\nMsimamizi wako ana uwezo wa kufuatilia shughuli ya mtandao wako ikiwa ni pamoja na barua pepe, programu, na tovuti. \n\nKwa maelezo zaidi, wasiliana na msimamizi wako.\n\nUmeunganishwa pia kwenye VPN, ambayo inaweza kufuatilia shughuli za mtandao wako."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Wasifu wako wa kazini unadhibitiwa na <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nMsimamizi wako anaweza kufuatilia shughuli za mtandaoni, ikiwa ni pamoja na barua pepe, programu na tovuti.\n\nKwa maelezo zaidi, wasiliana na msimamizi wako.\n\nUmeunganishwa pia kwenye VPN, ambayo inaweza kufuatilia shughuli zako mtandaoni."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Umeunganishwa kwenye <xliff:g id="APPLICATION">%1$s</xliff:g>, ambayo inaweza kufuatilia shughuli za mtandao wako, ikiwa ni pamoja na barua pepe, programu na tovuti."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Umeunganishwa kwenye <xliff:g id="APPLICATION">%1$s</xliff:g>, ambayo inaweza kufuatilia shughuli za mtandao wako, ikiwa ni pamoja na barua pepe, programu na tovuti."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Umeunganishwa kwenye <xliff:g id="APPLICATION">%1$s</xliff:g>, ambayo inaweza kufuatilia shughuli za mtandao wako, ikiwa ni pamoja na barua pepe, programu na tovuti."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Wasifu wako wa kazini unasimamiwa na <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Wasifu huu umeunganishwa kwenye <xliff:g id="APPLICATION">%2$s</xliff:g>, ambayo inaweza kufuatilia shughuli za mtandao wako, ikiwa ni pamoja na barua pepe, programu, na tovuti. \n\nKwa maelezo zaidi, wasiliana na msimamizi wako."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Wasifu wako wa kazini unadhibitiwa na <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Umeunganishwa kwenye <xliff:g id="APPLICATION">%2$s</xliff:g>, ambayo inaweza kufuatilia shughuli zako za mtandao, ikiwa ni pamoja na barua pepe, programu na tovuti.\n\nKwa maelezo zaidi, wasiliana na msimamizi wako."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Wasifu wako wa kazini unasimamiwa na <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Wasifu huu umeunganishwa kwenye <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ambayo inaweza kufuatilia mtandao wako wa kazini, ikiwa ni pamoja na barua pepe, programu na tovuti. \n\n Wewe pia umeunganishwa kwenye <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, ambayo inaweza kufuatilia shughuli za mtandao wako kibinafsi."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Kifaa kitaendelea kuwa katika hali ya kufungwa hadi utakapokifungua mwenyewe"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Pata arifa kwa haraka"</string>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index fd147cd..63b9d75 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -34,7 +34,7 @@
<bool name="config_keyguardUserSwitcher">true</bool>
<!-- Nav bar button default ordering/layout -->
- <string name="config_navBarLayout" translatable="false">space;back,home,recent;menu_ime</string>
+ <string name="config_navBarLayout" translatable="false">left;back,home,recent;right</string>
<!-- Animation duration when using long press on recents to dock -->
<integer name="long_press_dock_anim_duration">290</integer>
diff --git a/packages/SystemUI/res/values-sw900dp/config.xml b/packages/SystemUI/res/values-sw900dp/config.xml
index d8f9ef4..221b013 100644
--- a/packages/SystemUI/res/values-sw900dp/config.xml
+++ b/packages/SystemUI/res/values-sw900dp/config.xml
@@ -19,6 +19,6 @@
<resources>
<!-- Nav bar button default ordering/layout -->
- <string name="config_navBarLayout" translatable="false">back,home;space;menu_ime,recent</string>
+ <string name="config_navBarLayout" translatable="false">back,home,left;space;right,recent</string>
</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 7ef6872..5b17c54 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> எச்சரிக்கை"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"பணிப் பயன்முறை"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"இரவு ஒளி"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"சமீபத்திய பணிகள் இல்லை"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"எல்லாவற்றையும் அழித்துவிட்டீர்கள்"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"பயன்பாட்டு தகவல்"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"கிடைமட்டமாகப் பிரி"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"செங்குத்தாகப் பிரி"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"தனிவிருப்பத்தில் பிரி"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"நிராகரி"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"திற"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"திரையை மேல்புறமாகப் பிரி"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"திரையை இடப்புறமாகப் பிரி"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"திரையை வலப்புறமாகப் பிரி"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"சார்ஜ் செய்யப்பட்டது"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPNஐத் துண்டி"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"உங்கள் சாதனத்தை நிர்வகிப்பது: <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"உங்கள் சாதனத்தை நிர்வகிக்க, <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> பயன்பாட்டை <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> பயன்படுத்தும்."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"உங்கள் நிர்வாகியால் அமைப்புகள், நிறுவன அணுகல், பயன்பாடுகள், சாதனத்துடன் தொடர்புடைய தரவு, சாதன இருப்பிடத் தகவல் ஆகியவற்றைக் கண்காணிக்கவும் நிர்வகிக்கவும் முடியும்."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"மேலும் அறிக"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"<xliff:g id="VPN_APP">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN அமைப்புகளைத் திற"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"உங்கள் நிர்வாகி நெட்வொர்க் பதிவெடுத்தலை இயக்கியுள்ளார், இது சாதனத்தில் ட்ராஃபிக்கைக் கண்காணிக்கும்.\n\nமேலும் தகவலுக்கு, உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN இணைப்பை அமைக்க, பயன்பாட்டிற்கு அனுமதி வழங்கியுள்ளீர்கள்.\n\nஇந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட, உங்கள் சாதனத்தையும் நெட்வொர்க் செயல்பாட்டையும் கண்காணிக்க முடியும்."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"உங்கள் பணி சுயவிவரத்தை நிர்வகிப்பது: <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nமின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டை நிர்வாகியால் கண்காணிக்க முடியும்.\n\nகூடுதல் தகவலுக்கு, நிர்வாகியைத் தொடர்புகொள்ளவும்.\n\nஉங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய VPN இலும் இணைக்கப்பட்டுள்ளீர்கள்."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால், மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால், மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"உங்கள் பணி சுயவிவரத்தை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது. <xliff:g id="APPLICATION">%2$s</xliff:g> உடன் இணைக்கப்பட்டதால், மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட உங்கள் பணியிட நெட்வொர்க் செயல்பாட்டை அதனால் கண்காணிக்க முடியும்.\n\nகூடுதல் தகவலுக்கு, நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"உங்கள் பணி சுயவிவரத்தை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது. <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளதால், மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட உங்கள் பணியிட நெட்வொர்க் செயல்பாட்டை அதனால் கண்காணிக்க முடியும்.\n\nமேலும் <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளதால், உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டையும் அதனால் கண்காணிக்க முடியும்."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"நீங்கள் கைமுறையாகத் திறக்கும் வரை, சாதனம் பூட்டப்பட்டிருக்கும்"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"விரைவாக அறிவிப்புகளைப் பெறுதல்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index f55d95e..701dc2d 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> హెచ్చరిక"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"పని మోడ్"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"రాత్రి కాంతి"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC నిలిపివేయబడింది"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC ప్రారంభించబడింది"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ఇటీవలి అంశాలు ఏవీ లేవు"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"మీరు అన్నింటినీ తీసివేసారు"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"అనువర్తన సమాచారం"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"సమతలంగా విభజించు"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"లంబంగా విభజించు"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"అనుకూలంగా విభజించు"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"తీసివేయి"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"తెరువు"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"స్క్రీన్ని ఎగువకు విభజించు"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"స్క్రీన్ని ఎడమ వైపుకి విభజించు"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"స్క్రీన్ని కుడి వైపుకి విభజించు"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"ఛార్జ్ చేయబడింది"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPNను డిస్కనెక్ట్ చేయి"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"మీ పరికరం <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> ద్వారా నిర్వహించబడుతోంది."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> మీ పరికరాన్ని నిర్వహించడానికి <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>ని ఉపయోగిస్తుంది."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"మీ నిర్వాహకులు సెట్టింగ్లు, కార్పొరేట్ ప్రాప్యత, అనువర్తనాలు, మీ పరికరం అనుబంధిత డేటా మరియు స్థాన సమాచారం పర్యవేక్షించగలరు మరియు నిర్వహించగలరు."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"సెట్టింగ్లు, కార్పొరేట్ ప్రాప్యత, అనువర్తనాలు, మీ పరికరంతో అనుబంధించబడిన డేటా మరియు మీ పరికరం యొక్క స్థాన సమాచారాన్ని మీ నిర్వాహకులు పర్యవేక్షించగలరు మరియు నిర్వహించగలరు."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"మరింత తెలుసుకోండి"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"మీరు <xliff:g id="VPN_APP">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN సెట్టింగ్లను తెరవండి"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"మీ నిర్వాహకులు మీ పరికరంలో ట్రాఫిక్ను పర్యవేక్షించే నెట్వర్క్ లాగింగ్ను ఆన్ చేసారు.\n\nమరింత సమాచారం కోసం మీ నిర్వాహకులను సంప్రదించండి."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"మీ నిర్వాహకులు మీ పరికరంలోని ట్రాఫిక్ని పర్యవేక్షించగల నెట్వర్క్ లాగింగ్ని ఆన్ చేసారు.\n\nమరింత సమాచారం కావాలంటే, మీ నిర్వాహకులను సంప్రదించండి."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"మీరు VPN కనెక్షన్ సెటప్ చేయడానికి ఒక అనువర్తనానికి అనుమతి ఇచ్చారు.\n\nఈ అనువర్తనం ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ పరికరం మరియు నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"మీ కార్యాలయ ప్రొఫైల్ను <xliff:g id="ORGANIZATION">%1$s</xliff:g> నిర్వహిస్తోంది.\n\nమీ నిర్వాహకుడు ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలరు.\n\nమరింత సమాచారం కోసం, మీ నిర్వాహకుడిని సంప్రదించండి.\n\nమీరు VPNకి కూడా కనెక్ట్ చేయబడ్డారు, ఇది మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> ద్వారా మీ కార్యాలయ ప్రొఫైల్ నిర్వహించబడుతోంది.\n\nఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల సామర్థ్యం మీ నిర్వాహకులకు ఉంది.\n\nమరింత సమాచారం కావాలంటే, మీ నిర్వాహకులను సంప్రదించండి.\n\nమీరు VPNకి కూడా కనెక్ట్ అయ్యారు, ఇది మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"మీరు <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"మీరు <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"మీరు <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"మీ కార్యాలయ ప్రొఫైల్ను <xliff:g id="ORGANIZATION">%1$s</xliff:g> నిర్వహిస్తోంది. అలాగే, మీ కార్యాలయ ప్రొఫైల్ <xliff:g id="APPLICATION">%2$s</xliff:g>కి కనెక్ట్ చేయబడింది, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ కార్యాలయ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు.\n\nమరింత సమాచారం కోసం, మీ నిర్వాహకుడిని సంప్రదించండి."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> ద్వారా మీ కార్యాలయ ప్రొఫైల్ నిర్వహించబడుతోంది. ఇది <xliff:g id="APPLICATION">%2$s</xliff:g>కు కనెక్ట్ చేయబడింది, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ కార్యాలయ నెట్వర్క్ కార్యాచరణను నిర్వహించగలదు.\n\nమరింత సమాచారం కావాలంటే, మీ నిర్వాహకులను సంప్రదించండి."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"మీ కార్యాలయ ప్రొఫైల్ను <xliff:g id="ORGANIZATION">%1$s</xliff:g> నిర్వహిస్తోంది. అలాగే, మీ కార్యాలయ ప్రొఫైల్ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>కి కనెక్ట్ చేయబడింది, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ కార్యాలయ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు.\n\nమీరు <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>కి కూడా కనెక్ట్ చేయబడ్డారు, ఇది మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"మీరు మాన్యువల్గా అన్లాక్ చేస్తే మినహా పరికరం లాక్ చేయబడి ఉంటుంది"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"నోటిఫికేషన్లను వేగంగా పొందండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 0e18876..a051e3a 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"คำเตือน <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"โหมดการทำงาน"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"แสงตอนกลางคืน"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC ถูกปิดใช้งาน"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"เปิดใช้งาน NFC แล้ว"</string>
<string name="recents_empty_message" msgid="808480104164008572">"ไม่มีรายการล่าสุด"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"คุณได้ล้างทุกอย่างแล้ว"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ข้อมูลแอปพลิเคชัน"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"แยกในแนวนอน"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"แยกในแนวตั้ง"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"แยกแบบกำหนดเอง"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"ปิด"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"เปิด"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"แยกหน้าจอไปด้านบน"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"แยกหน้าจอไปทางซ้าย"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"แยกหน้าจอไปทางขวา"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"ชาร์จแล้ว"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"ยกเลิกการเชื่อมต่อ VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"อุปกรณ์ของคุณได้รับการจัดการโดย <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> ใช้ <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> เพื่อจัดการอุปกรณ์"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"ผู้ดูแลระบบของคุณสามารถตรวจสอบและจัดการการตั้งค่า การเข้าถึงของบริษัท แอป และข้อมูลที่เชื่อมโยงกับอุปกรณ์ และข้อมูลตำแหน่งของอุปกรณ์ด้วย"</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"ผู้ดูแลระบบสามารถตรวจสอบและจัดการการตั้งค่า การเข้าถึงของบริษัท แอป ข้อมูลที่เชื่อมโยงกับอุปกรณ์ของคุณ และข้อมูลตำแหน่งของอุปกรณ์"</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"เรียนรู้เพิ่มเติม"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"คุณเชื่อมต่อกับ <xliff:g id="VPN_APP">%1$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายของคุณ รวมถึงอีเมล แอป และเว็บไซต์ได้"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"เปิดการตั้งค่า VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"ผู้ดูแลระบบของคุณเปิดการทำบันทึกเครือข่าย ซึ่งจะติดตามดูการรับส่งข้อมูลบนอุปกรณ์ \n\nหากต้องการข้อมูลเพิ่มเติม ให้ติดต่อผู้ดูแลระบบของคุณ"</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"ผู้ดูแลระบบได้เปิดการทำบันทึกเครือข่าย ซึ่งจะติดตามดูการรับส่งข้อมูลบนอุปกรณ์ของคุณ\n\nโปรดติดต่อผู้ดูแลระบบสำหรับข้อมูลเพิ่มเติม"</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"คุณได้ให้สิทธิ์แอปในการตั้งค่าการเชื่อมต่อ VPN\n\nแอปนี้จะสามารถตรวจสอบอุปกรณ์และกิจกรรมในเครือข่าย รวมถึงอีเมล แอป และเว็บไซต์ได้"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"โปรไฟล์งานได้รับการจัดการโดย <xliff:g id="ORGANIZATION">%1$s</xliff:g>\n\nผู้ดูแลระบบของคุณสามารถตรวจสอบกิจกรรมในเครือข่ายรวมถึงอีเมล แอป และเว็บไซต์ได้\n\nสำหรับข้อมูลเพิ่มเติม โปรดติดต่อผู้ดูแลระบบ\n\nนอกจากนี้คุณยังมีการเชื่อมต่อ VPN ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายของคุณ"</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"โปรไฟล์งานของคุณได้รับการจัดการโดย <xliff:g id="ORGANIZATION">%1$s</xliff:g>\n\nผู้ดูแลระบบสามารถตรวจสอบกิจกรรมในเครือข่ายของคุณ ซึ่งรวมถึงอีเมล แอป และเว็บไซต์ต่างๆ\n\nโปรดติดต่อผู้ดูแลระบบสำหรับข้อมูลเพิ่มเติม\n\nนอกจากนี้คุณยังเชื่อมต่อกับ VPN ซึ่งตรวจสอบกิจกรรมในเครือข่ายของคุณได้"</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"คุณเชื่อมต่อกับ <xliff:g id="APPLICATION">%1$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายของคุณ รวมถึงอีเมล แอป และเว็บไซต์ได้"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"คุณเชื่อมต่อกับ <xliff:g id="APPLICATION">%1$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายส่วนตัวของคุณ รวมถึงอีเมล แอป และเว็บไซต์ได้"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"คุณเชื่อมต่อกับ <xliff:g id="APPLICATION">%1$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายส่วนตัวของคุณ รวมถึงอีเมล แอป และเว็บไซต์ได้"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"โปรไฟล์งานได้รับการจัดการโดย <xliff:g id="ORGANIZATION">%1$s</xliff:g> โดยมีการเชื่อมต่อกับ <xliff:g id="APPLICATION">%2$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่าย รวมถึงอีเมล แอป และเว็บไซต์ได้\n\nสำหรับข้อมูลเพิ่มเติม โปรดติดต่อผู้ดูแลระบบ"</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"โปรไฟล์งานของคุณได้รับการจัดการโดย <xliff:g id="ORGANIZATION">%1$s</xliff:g> โปรไฟล์ดังกล่าวเชื่อมโยงกับ <xliff:g id="APPLICATION">%2$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายงานของคุณ ซึ่งรวมถึงอีเมล แอป และเว็บไซต์ต่างๆ\n\nโปรดติดต่อผู้ดูแลระบบสำหรับข้อมูลเพิ่มเติม"</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"โปรไฟล์งานได้รับการจัดการโดย <xliff:g id="ORGANIZATION">%1$s</xliff:g> โดยมีการเชื่อมต่อกับ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่าย รวมถึงอีเมล แอป และเว็บไซต์ได้\n\nนอกจากนี้ คุณยังเชื่อมต่อกับ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายส่วนตัวได้"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"อุปกรณ์จะล็อกจนกว่าคุณจะปลดล็อกด้วยตนเอง"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"รับการแจ้งเตือนเร็วขึ้น"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 0e01e9c..7857811 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Babala sa <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Work mode"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Night Light"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Walang mga kamakailang item"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Na-clear mo ang lahat"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Impormasyon ng Application"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Custom"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"I-dismiss"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Bukas"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"I-split ang screen pataas"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"I-split ang screen pakaliwa"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"I-split ang screen pakanan"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Nasingil na"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Idiskonekta ang VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Pinamamahalaan ng <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> ang iyong device."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"Ginagamit ng <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> ang <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> upang pamahalaan ang iyong device."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Masusubaybayan at mapapamahalaan ng admin mo ang mga setting, pangkorporasyong access, app, data sa device at impormasyon ng lokasyon ng device mo."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Matuto pa"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Kumonekta ka sa <xliff:g id="VPN_APP">%1$s</xliff:g>, na maaaring sumubaybay sa iyong aktibidad sa network, kasama ang mga email, app at website."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Buksan ang Mga Setting ng VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Na-on ng iyong admin ang pag-log sa network, na sumusubaybay sa trapiko sa device mo.\n\nPara sa higit pang impormasyon, makipag-ugnayan sa iyong admin."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Nagbigay ka ng pahintulot sa app upang mag-set up ng VPN na koneksyon.\n\nMaaaring subaybayan ng app na ito ang iyong aktibidad sa device at network, kabilang ang mga email, app at website."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Pinapamahalaan ang iyong profile ng <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nMay kakayahan ang iyong administrator sa pagsubaybay ng iyong aktibidad sa network kabilang ang mga email, app at website.\n\nPara sa higit pang impormasyon, makipag-ugnayan sa iyong administrator.\n\nNakakonekta ka rin sa isang VPN, na maaaring sumubaybay sa iyong aktibidad sa network."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Nakakonekta ka sa <xliff:g id="APPLICATION">%1$s</xliff:g>, na maaaring sumubaybay sa iyong aktibidad sa network kabilang ang mga email, app at website."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Nakakonekta ka sa <xliff:g id="APPLICATION">%1$s</xliff:g>, na maaaring sumubaybay sa iyong personal na aktibidad sa network, kabilang ang mga email, app at website."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Nakakonekta ka sa <xliff:g id="APPLICATION">%1$s</xliff:g>, na maaaring sumubaybay sa aktibidad sa iyong personal na network, kabilang ang mga email, app at website."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Ang iyong profile sa trabaho ay pinapamahalaan ng <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Nakakonekta ito sa <xliff:g id="APPLICATION">%2$s</xliff:g>, na maaaring sumubaybay sa iyong aktibidad sa network, kabilang ang mga email, app at website.\n\nPara sa higit pang impormasyon, makipag-ugnayan sa iyong administrator."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Ang iyong profile sa trabaho ay pinapamahalaan ng <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Nakakonekta ito sa <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, na maaaring sumubaybay sa iyong aktibidad sa network, kabilang ang mga email, app at website.\n\nNakakonekta ka rin sa <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, na maaaring sumubaybay sa iyong personal na aktibidad sa network."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Mananatiling naka-lock ang device hanggang sa manu-mano mong i-unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Kunin ang notification nang mas mabilis"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 9987f94..d4ef51d 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> uyarısı"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Çalışma modu"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Gece Işığı"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Yeni öğe yok"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Her şeyi sildiniz"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Uygulama Bilgileri"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Yatay Ayırma"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dikey Ayırma"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Özel Ayırma"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Kapat"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Aç"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ekranı yukarıya doğru böl"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ekranı sola doğru böl"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ekranı sağa doğru böl"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Ödeme alındı"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN bağlantısını kes"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Cihazınız <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> tarafından yönetiliyor."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>, cihazınızı yönetmek için <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> kullanıyor."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Yöneticiniz ayarları, kurumsal erişimi, uygulamaları, cihazınızla ilişkilendirilen verileri ve cihazınızın konum bilgilerini takip edip yönetebilir."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Daha fazla bilgi"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"E-postalarınız, uygulamalarınız ve web siteleriniz de dahil olmak üzere ağ etkinliğinizi takip edebilen <xliff:g id="VPN_APP">%1$s</xliff:g> ağına bağlısınız."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN Ayarlarını aç"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Yöneticiniz,cihazınızdaki trafiği izleyen ağ günlük kaydını açtı.\n\nDaha fazla bilgi için yöneticinizle iletişim kurun."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN bağlantısı kurması için bir uygulamaya izin verdiniz.\n\nBu uygulama, cihazınızın yanı sıra e-postalarınız, uygulamalarınız ve web siteleriniz dahil olmak üzere ağ etkinliğinizi izleyebilir."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Cihazınız <xliff:g id="ORGANIZATION">%1$s</xliff:g> tarafından yönetiliyor.\n\nYöneticiniz; e-postalarınız, uygulamalarınız ve web siteleriniz dahil olmak üzere ağ etkinliğinizi izleyebilir.\n\nDaha fazla bilgi edinmek için yöneticinizle iletişim kurun.\n\nAyrıca ağ etkinliğinizi izleyebilen bir VPN\'ye bağlısınız."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"E-postalarınız, uygulamalarınız ve web siteleriniz dahil olmak üzere ağ etkinliğinizi izleyebilen <xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasına bağlısınız."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"E-postalarınız, uygulamalarınız ve web siteleriniz dahil olmak üzere kişisel ağ etkinliğinizi izleyebilen <xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasına bağlısınız."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"E-postalarınız, uygulamalarınız ve web siteleriniz dahil olmak üzere kişisel ağ etkinliğinizi izleyebilen <xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasına bağlısınız."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"İş profiliniz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tarafından yönetiliyor. E-postalarınız, uygulamalarınız ve web siteleriniz dahil olmak üzere ağ etkinliğinizi izleyebilen <xliff:g id="APPLICATION">%2$s</xliff:g> uygulamasına bağlı.\n\nDaha fazla bilgi için yöneticinizle iletişim kurun."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"İş profiliniz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tarafından yönetiliyor. E-postalarınız, uygulamalarınız ve web siteleriniz dahil olmak üzere ağ etkinliğinizi izleyebilen <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> uygulamasına bağlı.\n\n Ayrıca kişisel ağ etkinliğinizi izleyebilen <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> uygulamasına bağlısınız."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Cihazınızın kilidini manuel olarak açmadıkça cihaz kilitli kalacak"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Bildirimleri daha hızlı alın"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 57b8d18..11f88c5 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -327,6 +327,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Застереження: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Робочий режим"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Нічний режим"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"Немає нещодавніх завдань"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Ви очистили все"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Інформація про додаток"</string>
@@ -340,6 +346,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Розділити горизонтально"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Розділити вертикально"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Розділити (власний варіант)"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Закрити"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Відкрити"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Розділити екран угорі"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Розділити екран ліворуч"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Розділити екран праворуч"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Заряджено"</string>
@@ -420,20 +431,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Від’єднатися від мережі VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Вашим пристроєм керує додаток <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"Компанія <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> керує вашим пристроєм за допомогою додатка <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Адміністратор може відстежувати й контролювати налаштування, корпоративний доступ, додатки, дані на пристрої та дані про місцезнаходження пристрою."</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Докладніше"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Під’єднано додаток <xliff:g id="VPN_APP">%1$s</xliff:g>, який може відстежувати вашу активність у мережі, як-от доступ до електронної пошти, додатків і веб-сайтів."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Відкрити налаштування мережі VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Ваш адміністратор увімкнув реєстрацію в мережі, під час якої на вашому пристрої відстежується трафік.\n\nЩоб дізнатися більше, зв’яжіться зі своїм адміністратором."</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Ви дозволили додатку під’єднуватися до мережі VPN.\n\nЦей додаток може відстежувати вашу активність на пристрої та в мережі, зокрема в електронній пошті, додатках і на веб-сайтах."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Вашим пристроєм керує організація <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nАдміністратор може відстежувати вашу активність у мережі, зокрема в електронній пошті, додатках і на веб-сайтах.\n\nЗв’яжіться з адміністратором, щоб дізнатися більше.\n\nПристрій під’єднано до мережі VPN, у якій ваша активність може відстежуватись."</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Ваш профіль під’єднано до додатка <xliff:g id="APPLICATION">%1$s</xliff:g>, який може відстежувати вашу активність у мережі, зокрема в електронній пошті, додатках і на веб-сайтах."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Ваш профіль під’єднано до додатка <xliff:g id="APPLICATION">%1$s</xliff:g>, який може відстежувати вашу особисту активність у мережі, зокрема в електронній пошті, додатках і на веб-сайтах."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Ваш профіль під’єднано до додатка <xliff:g id="APPLICATION">%1$s</xliff:g>, який може відстежувати вашу особисту активність у мережі, зокрема доступ до електронної пошти, додатків і веб-сайтів."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Вашим робочим профілем керує <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Профіль під’єднано до додатка <xliff:g id="APPLICATION">%2$s</xliff:g>, який може відстежувати вашу робочу активність у мережі, зокрема в електронній пошті, додатках і на веб-сайтах.\n\nЗв’яжіться з адміністратором, щоб дізнатися більше."</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Вашим робочим профілем керує <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Профіль під’єднано до додатка <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, який може відстежувати вашу робочу активність у мережі, зокрема в електронній пошті, додатках і на веб-сайтах.\n\nВаш профіль також під’єднано до додатка <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, який може відстежувати вашу особисту активність у мережі."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Пристрій залишатиметься заблокованим, доки ви не розблокуєте його вручну"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Швидше отримуйте сповіщення"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 50170b8..f67ce8e 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> وارننگ"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"کام موڈ"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"نائٹ لائٹ"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC غیر فعال ہے"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC فعال ہے"</string>
<string name="recents_empty_message" msgid="808480104164008572">"کوئی حالیہ آئٹم نہیں"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"آپ نے سب کچھ صاف کر دیا ہے"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"ایپلیکیشن کی معلومات"</string>
@@ -334,6 +337,16 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"بلحاظ افقی الگ کریں"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"بلحاظ عمودی الگ کریں"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"بلحاظ حسب ضرورت الگ کریں"</string>
+ <!-- no translation found for recents_accessibility_dismissed (2354459747918667050) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_open (1651449827614876864) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_top (9056056469282256287) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_left (8987144699630620019) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_right (275069779299592867) -->
+ <skip />
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"چارج ہوگئی"</string>
@@ -414,20 +427,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN کو غیر منسلک کریں"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"آپ کا آلہ <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> کے زیر انتظام ہے۔"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> آپ کے آلہ کے نظم کیلئے <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> استعمال کرتا ہے۔"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"آپ کا منتظم ترتیبات، کارپوریٹ رسائی، ایپس، آپ کے آلہ سے وابستہ ڈیٹا اور آپ کے آلہ کے مقام کی معلومات کو مانیٹر اور ان کا نظم کر سکتا ہے۔"</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"آپ کا ایڈمن ترتیبات، کارپوریٹ رسائی، ایپس، آپ کے آلہ سے وابستہ ڈیٹا اور آپ کے آلہ کے مقام کی معلومات کو مانیٹر اور ان کا نظم کر سکتا ہے۔"</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"مزید جانیں"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"آپ <xliff:g id="VPN_APP">%1$s</xliff:g> سے منسلک ہیں جو ای میلز، ایپس اور ویب سائٹس سمیت آپ کے نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN کی ترتیبات کھولیں"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"آپ کے ایڈمن نے نیٹ ورک لاگنگ آن کر دی ہے، جو آپ کے آلہ پر ٹریفک کو مانیٹر کرتی ہے۔\n\nمزید معلومات کیلئے اپنے ایڈمن سے رابطہ کریں۔"</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"آپ کے ایڈمن نے نیٹ ورک لاگنگ آن کر دی ہے، جو آپ کے آلہ پر ٹریفک کو مانیٹر کرتی ہے۔\n\nمزید معلومات کیلئے اپنے ایڈمن سے رابطہ کریں۔"</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"آپ نے ایک ایپ کو VPN کنکشن ترتیب دینے کی اجازت دی ہے۔\n\nیہ ایپ ای میلز، ایپس اور ویب سائٹس سمیت آپ کے آلہ اور نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"آپ کا دفتری پروفائل <xliff:g id="ORGANIZATION">%1$s</xliff:g> کے زیر انتظام ہے۔\n\nآپ کا منتظم ای میلز، ایپس اور ویب سائٹس سیمت آپ کے نیٹ ورک کی سرگرمی مانیٹر کر سکتا ہے۔\n\nمزید معلومات کیلئے اپنے منتظم سے رابطہ کریں۔\n\nآپ ایک VPN سے بھی منسلک ہیں، جو آپ کے نیٹ ورک کی سرگرمی مانیٹر کر سکتا ہے۔"</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"آپ کی دفتری پروفائل <xliff:g id="ORGANIZATION">%1$s</xliff:g> کے زیر نظم ہے۔\n\nآپ کا ایڈمن بشمول ای میلز، ایپس، اور ویب سائٹس، آپ کے نیٹ ورک کی سرگرمی کو مانیٹر کرنے کا اہل ہے۔\n\nمزید معلومات کے لیے اپنے ایڈمن سے رابطہ کریں۔\n\nآپ ایک VPN سے بھی منسلک ہیں، جو آپ کے نیٹ ورک کی سرگرمی کو مانیٹر کر سکتا ہے۔"</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"آپ <xliff:g id="APPLICATION">%1$s</xliff:g> سے منسلک ہیں، جو ای میلز، ایپس اور ویب سائٹس سمیت آپ کے نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"آپ <xliff:g id="APPLICATION">%1$s</xliff:g> سے منسلک ہیں، جو آپ کے نجی نیٹ ورک کی سرگرمی سمیت ای میلز، ایپس اور ویب سائٹس مانیٹر کر سکتی ہے۔"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"آپ <xliff:g id="APPLICATION">%1$s</xliff:g> سے منسلک ہیں، جو ای میلز، ایپس اور ویب سائٹس سمیت آپ کے نجی نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"آپ کا دفتری پروفائل <xliff:g id="ORGANIZATION">%1$s</xliff:g> کے زیر انتظام ہے۔ یہ <xliff:g id="APPLICATION">%2$s</xliff:g> سے منسلک ہے، جو ای میلز، ایپس اور ویب سائٹس سمیت آپ کے نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔\n\nمزید معلومات کیلئے اپنے منتظم سے رابطہ کریں۔"</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"آپ کی دفتری پروفائل <xliff:g id="ORGANIZATION">%1$s</xliff:g> کے زیر نظم ہے۔ یہ <xliff:g id="APPLICATION">%2$s</xliff:g> سے منسلک ہے، جو بشمول ای میلز، ایپس اور ویب سائٹس، آپ کے دفتری نیٹ ورک کی سرگرمی کو مانیٹر کر سکتی ہے۔ \n\nمزید معلومات کے لیے، اپنے ایڈمن سے رابطہ کریں۔"</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"آپ کا دفتری پروفائل <xliff:g id="ORGANIZATION">%1$s</xliff:g> کے زیر انتظام ہے۔ یہ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> سے منسلک ہے، جو ای میلز، ایپس اور ویب سائٹس سمیت آپ کے نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔\n\nآپ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> سے بھی منسلک ہیں، جو آپ کے نجی نیٹ ورک کی سرگرمی کو مانیٹر کر سکتی ہے۔"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"آلہ اس وقت تک مقفل رہے گا جب تک آپ دستی طور پر اسے غیر مقفل نہ کریں"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"تیزی سے اطلاعات حاصل کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index fba46753..cf4435f 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -323,6 +323,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Ogohlantirish: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Ish rejimi"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Tungi rejim"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC o‘chiq"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC yoniq"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Hozircha hech narsa yo‘q"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Hammasi o‘chirildi"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Ilova haqida ma’lumot"</string>
@@ -336,6 +339,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Gorizontal yo‘nalishda bo‘lish"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikal yo‘nalishda bo‘lish"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Boshqa usulda bo‘lish"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Yopish"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Ochish"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ekranni tepaga qadash"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ekranni chap tomonga qadash"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ekranni o‘ng tomonga qadash"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Batareya quvvati to‘ldi"</string>
@@ -416,20 +424,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"VPN ulanishini uzish"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Qurilmangiz <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> tomonidan boshqariladi."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> qurilmangizni boshqarish uchun <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> ilovasidan foydalanadi."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Administratoringiz qurilmangiz bilan bog‘liq sozlamalar, korporativ kirish huquqi, ilova va ma’lumotlarni hamda qurilmangizning joylashuv axborotini kuzatishi va boshqarishi mumkin."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Administratoringiz qurilmangiz bilan bog‘liq sozlamalar, korporativ kirish huquqi, ilova va ma’lumotlarni hamda qurilmangizning joylashuv axborotini kuzatishi va boshqarishi mumkin."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Batafsil"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"<xliff:g id="VPN_APP">%1$s</xliff:g> ilovasi ishga tushirilgan. U internetdagi harakatlaringiz, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"VPN sozlamalarini ochish"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Administrator qurilmangizdagi trafikni nazorat qiluvchi tarmoq jurnalini yoqdi.\n\nBatafsil ma’lumot olish uchun administratoringizga murojaat qiling."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Administrator qurilmangizdagi trafikni nazorat qiluvchi tarmoq jurnalini yoqdi.\n\nBatafsil ma’lumot olish uchun administratoringizga murojaat qiling."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Siz ilovaga VPN tarmog‘iga ulanishga ruxsat bergansiz.\n\nUshbu ilova qurilmangiz va internetdagi harakatlaringizni, jumladan, e-pochta, ilovalar va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Sizning ishchi profilingiz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tomonidan boshqariladi.\n\nAdministrator internetdagi harakatlaringizni, jumladan, e-pochta, ilova va xavfsiz veb-saytlar bilan ishlashingizni kuzatishi mumkin.\n\nBatafsil ma’lumot olish uchun administrator bilan bog‘laning.\n\nShuningdek, siz VPN tarmog‘iga ham ulangansiz. U internetdagi harakatlaringizni kuzatishi mumkin."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Sizning ishchi profilingiz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tomonidan boshqariladi.\n\nAdministrator internetdagi harakatlaringizni, jumladan, e-pochta, ilova va xavfsiz veb-saytlar bilan ishlashingizni kuzatishi mumkin.\n\nBatafsil ma’lumot olish uchun administrator bilan bog‘laning.\n\nShuningdek, siz VPN tarmog‘iga ham ulangansiz. U internetdagi harakatlaringizni kuzatishi mumkin."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"<xliff:g id="APPLICATION">%1$s</xliff:g> ilovasi ishga tushirilgan. U internetdagi harakatlaringiz, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g> ilovasi ishga tushirilgan. U internetdagi harakatlaringiz, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> ilovasi ishga tushirilgan. U internetdagi harakatlaringiz, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Sizning ishchi profilingiz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tomonidan boshqariladi. <xliff:g id="APPLICATION">%2$s</xliff:g> ilovasi ish tarmog‘idagi harakatlaringizni, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin.\n\nBatafsil ma’lumot olish uchun administrator bilan bog‘laning."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Sizning ishchi profilingiz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tomonidan boshqariladi. <xliff:g id="APPLICATION">%2$s</xliff:g> ilovasi ish tarmog‘idagi harakatlaringizni, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin.\n\nBatafsil ma’lumot olish uchun administrator bilan bog‘laning."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Sizning ishchi profilingiz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tomonidan boshqariladi. <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ilovasi ish tarmog‘idagi harakatlaringizni, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin.\n\nShuningdek, <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ilovasi ham shaxsiy tarmoqdagi harakatlaringizni kuzatishi mumkin."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Qurilma qo‘lda qulfdan chiqarilmaguncha qulflangan holatda qoladi"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Bildirishnomalarni tezroq oling"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 1363d8a..d101ec1 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"Cảnh báo <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Chế độ làm việc"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Đèn đọc sách"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC đã được tắt"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"NFC đã được bật"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Không có mục gần đây nào"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Bạn đã xóa mọi nội dung"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Thông tin ứng dụng"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Phân tách ngang"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Phân tách dọc"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Tùy chỉnh phân tách"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Loại bỏ"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Mở"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Chia đôi màn hình lên trên"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Chia đôi màn hình sang trái"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Chia đôi màn hình sang phải"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Đã sạc đầy"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Ngắt kết nối VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Thiết bị của bạn do <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> quản lý."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> sử dụng <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> để quản lý thiết bị của bạn."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Quản trị viên có thể giám sát & q.lý cài đặt, quyền truy cập d.liệu công ty, ứng dụng, d.liệu được liên kết với thiết bị & thông tin vị trí thiết bị của bạn."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Quản trị viên của bạn có thể giám sát và quản lý cài đặt, quyền truy cập dữ liệu công ty, ứng dụng, dữ liệu được liên kết với thiết bị và thông tin vị trí thiết bị của bạn."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Tìm hiểu thêm"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Bạn đang kết nối với <xliff:g id="VPN_APP">%1$s</xliff:g>. Ứng dụng này có thể giám sát hoạt động mạng của bạn, bao gồm email, ứng dụng và trang web."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Mở cài đặt VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Quản trị viên đã bật tính năng ghi nhật ký mạng. Tính năng này giám sát lưu lượng truy cập trên thiết bị của bạn.\n\nĐể biết thêm thông tin, hãy liên hệ với quản trị viên."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Quản trị viên đã bật tính năng ghi nhật ký mạng. Tính năng này giám sát lưu lượng truy cập trên thiết bị của bạn.\n\nĐể biết thêm thông tin, hãy liên hệ với quản trị viên của bạn."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Bạn đã cấp cho ứng dụng quyền thiết lập kết nối VPN.\n\nỨng dụng này có thể giám sát hoạt động mạng và thiết bị của bạn, bao gồm email, ứng dụng và trang web."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Hồ sơ Android Work của bạn được quản lý bởi <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nQuản trị viên có thể giám sát hoạt động mạng của bạn bao gồm email, ứng dụng và trang web.\n\nĐể biết thêm thông tin, hãy liên hệ với quản trị viên của bạn.\n\nBạn cũng được kết nối với VPN, có thể giám sát hoạt động mạng của bạn."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Hồ sơ công việc của bạn do <xliff:g id="ORGANIZATION">%1$s</xliff:g> quản lý.\n\nQuản trị viên có thể giám sát hoạt động mạng của bạn bao gồm email, ứng dụng và trang web.\n\nĐể biết thêm thông tin, hãy liên hệ với quản trị viên của bạn.\n\nBạn cũng được kết nối với VPN. Dịch vụ này có thể giám sát hoạt động mạng của bạn."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Bạn đang kết nối với <xliff:g id="APPLICATION">%1$s</xliff:g>. Ứng dụng này có thể giám sát hoạt động mạng của bạn bao gồm email, ứng dụng và trang web."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Bạn đang kết nối với <xliff:g id="APPLICATION">%1$s</xliff:g>. Ứng dụng này có thể giám sát hoạt động mạng cá nhân của bạn bao gồm email, ứng dụng và trang web."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Bạn đang kết nối với <xliff:g id="APPLICATION">%1$s</xliff:g>. Ứng dụng này có thể giám sát hoạt động mạng cá nhân của bạn bao gồm email, ứng dụng và trang web."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Hồ sơ công việc của bạn được quản lý bởi <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Hồ sơ được kết nối với <xliff:g id="APPLICATION">%2$s</xliff:g>, ứng dụng này có thể giám sát hoạt động mạng cơ quan của bạn, bao gồm email, ứng dụng và trang web.\n\nĐể biết thêm thông tin, hãy liên hệ với quản trị viên của bạn."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Hồ sơ công việc của bạn do <xliff:g id="ORGANIZATION">%1$s</xliff:g> quản lý. Hồ sơ này được kết nối với <xliff:g id="APPLICATION">%2$s</xliff:g>, ứng dụng này có thể giám sát hoạt động mạng của bạn, bao gồm email, ứng dụng và trang web.\n\nĐể biết thêm thông tin, hãy liên hệ với quản trị viên của bạn."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Hồ sơ công việc của bạn được quản lý bởi <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Hồ sơ được kết nối với <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ứng dụng này có thể giám sát hoạt động mạng cơ quan của bạn, bao gồm email, ứng dụng và trang web.\n\nBạn cũng được kết nối với <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, có thể giám sát hoạt động mạng cá nhân của bạn."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Thiết bị sẽ vẫn bị khóa cho tới khi bạn mở khóa theo cách thủ công"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Nhận thông báo nhanh hơn"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 91a4734..f22fc3d 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g>警告"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"工作模式"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"夜间模式"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"近期没有任何内容"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"您已清除所有内容"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"应用信息"</string>
@@ -334,6 +340,16 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"水平分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"垂直分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"自定义分割"</string>
+ <!-- no translation found for recents_accessibility_dismissed (2354459747918667050) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_open (1651449827614876864) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_top (9056056469282256287) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_left (8987144699630620019) -->
+ <skip />
+ <!-- no translation found for recents_accessibility_split_screen_right (275069779299592867) -->
+ <skip />
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"已充满"</string>
@@ -414,20 +430,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"断开VPN连接"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"您的设备由<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>管理。"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>会使用<xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>管理您的设备。"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"您的管理员能够监控和管理与您的设备相关的设置、企业权限、应用、数据以及您设备的位置信息。"</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"了解详情"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"您已连接到<xliff:g id="VPN_APP">%1$s</xliff:g>,该应用可以监控您的网络活动,包括收发电子邮件、使用应用和浏览网站。"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"打开 VPN 设置"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"您的管理员已开启网络日志功能,该功能会监控您设备上的流量。\n\n要了解详情,请与您的管理员联系。"</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"您已授权应用设置 VPN 连接。\n\n该应用可以监控您的设备和网络活动,包括收发电子邮件、使用应用和浏览网站。"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"您的工作资料由以下单位管理:<xliff:g id="ORGANIZATION">%1$s</xliff:g>。\n\n您单位的管理员可以监控您的网络活动,包括收发电子邮件、使用应用和浏览网站。\n\n若要了解详情,请与您单位的管理员联系。\n\n此外,您还连接到了 VPN,此 VPN 也可以监控您的网络活动。"</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"您已连接到<xliff:g id="APPLICATION">%1$s</xliff:g>,该应用可以监控您的网络活动,包括收发电子邮件、使用应用和浏览网站。"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"您已连接到<xliff:g id="APPLICATION">%1$s</xliff:g>,该应用可以监控您的个人网络活动,包括收发电子邮件、使用应用和浏览网站。"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"您已连接到<xliff:g id="APPLICATION">%1$s</xliff:g>,该应用可以监控您的个人网络活动,包括收发电子邮件、使用应用和浏览网站。"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"您的工作资料由以下单位管理:<xliff:g id="ORGANIZATION">%1$s</xliff:g>。您已连接到<xliff:g id="APPLICATION">%2$s</xliff:g>,该应用可以监控您的工作网络活动,包括收发电子邮件、使用应用和浏览网站。\n\n若要了解详情,请与您单位的管理员联系。"</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"您的工作资料由以下单位管理:<xliff:g id="ORGANIZATION">%1$s</xliff:g>。您已连接到<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>,该应用可以监控您的工作网络活动,包括收发电子邮件、使用应用和浏览网站。\n\n此外,您还连接到了<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>,该应用可以监控您的个人网络活动。"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"在您手动解锁之前,设备会保持锁定状态"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"更快捷地查看通知"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 0b62aea..0392078 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -323,6 +323,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> 警告"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"工作模式"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"夜燈模式"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"沒有最近項目"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"您已清除所有項目"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"應用程式資料"</string>
@@ -336,6 +342,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"水平分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"垂直分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"自訂分割"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"關閉"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"開啟"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"將分割畫面顯示喺頂部"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"將分割畫面顯示喺左邊"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"將分割畫面顯示喺右邊"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"已完成充電"</string>
@@ -416,20 +427,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"中斷 VPN 連線"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"您的裝置由「<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>」管理。"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>使用「<xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>」管理您的裝置。"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"您的管理員可以監控及管理您裝置的設定、企業存取、應用程式、資料及位置資訊。"</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"瞭解詳情"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"您已連接至「<xliff:g id="VPN_APP">%1$s</xliff:g>」,此應用程式可以監控您的網絡活動,包括電郵、應用程式及網站。"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"開啟 VPN 設定"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"您的管理員已開啟網絡記錄功能,以監控您裝置上的流量。\n\n如需瞭解詳情,請聯絡您的管理員。"</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"您已授權應用程式設定 VPN 連線。\n\n這個應用程式能夠監控您的裝置和網絡活動,包括電郵、應用程式和網站。"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"您的裝置由 <xliff:g id="ORGANIZATION">%1$s</xliff:g> 管理。\n\n您的管理員可以監控您的網絡活動,包括電郵、應用程式及網站。\n\n如需瞭解更多資訊,請聯絡管理員。\n\n由於您的裝置連至 VPN,因此 VPN 服務供應商也可監控您的網絡活動。"</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"您已連結至<xliff:g id="APPLICATION">%1$s</xliff:g> ,它能夠監控您的網絡活動,包括電郵、應用程式和網站。"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"您已連結至<xliff:g id="APPLICATION">%1$s</xliff:g>,它能夠監控您的個人網絡活動,包括電郵、應用程式和網站。"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"您已連接至「<xliff:g id="APPLICATION">%1$s</xliff:g>」,此應用程式可以監控您的個人網絡活動,包括電郵、應用程式及網站。"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"您的工作設定檔由<xliff:g id="ORGANIZATION">%1$s</xliff:g>管理。它已連結至<xliff:g id="APPLICATION">%2$s</xliff:g>,能夠監控您的工作網絡活動,包括電郵、應用程式和網站。\n\n如需進一步資訊,請聯絡您的管理員。"</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"您的工作設定檔由<xliff:g id="ORGANIZATION">%1$s</xliff:g>管理。它已連結至<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>,能夠監控您的工作網絡活動,包括電郵、應用程式和網站。\n\n此外,您亦連結至<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>,因此它亦能夠監控您的個人網絡活動。"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"裝置將保持上鎖,直到您手動解鎖"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"更快取得通知"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 905506e..1d7159a 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -321,6 +321,12 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> 警告"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"工作模式"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"夜燈"</string>
+ <!-- no translation found for quick_settings_nfc_label (9012153754816969325) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_off (6883274004315134333) -->
+ <skip />
+ <!-- no translation found for quick_settings_nfc_on (6680317193676884311) -->
+ <skip />
<string name="recents_empty_message" msgid="808480104164008572">"最近沒有任何項目"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"您已清除所有工作"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"應用程式資訊"</string>
@@ -334,6 +340,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"水平分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"垂直分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"自訂分割"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"關閉"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"開啟"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"將分割畫面顯示在頂端"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"將分割畫面顯示在左邊"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"將分割畫面顯示在右邊"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"已充飽"</string>
@@ -414,20 +425,24 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"中斷 VPN 連線"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"你的裝置是由「<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>」所管理。"</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> 使用「<xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>」管理你的裝置。"</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"你的管理員可以監控及管理與你的裝置相關聯的設定、公司系統權限、應用程式和資料,以及裝置的位置資訊。"</string>
+ <!-- no translation found for monitoring_description_do_body (3639594537660975895) -->
+ <skip />
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"瞭解詳情"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"由於你已連結至「<xliff:g id="VPN_APP">%1$s</xliff:g>」,你的網路活動 (包括收發電子郵件、使用應用程式及瀏覽網站) 可能會受到這個應用程式監控。"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"開啟 VPN 設定"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"你的管理員已啟用網路紀錄功能,可監控你裝置上的流量。\n\n如需詳細資訊,請與你的管理員聯絡。"</string>
+ <!-- no translation found for monitoring_description_network_logging (7223505523384076027) -->
+ <skip />
<string name="monitoring_description_vpn" msgid="4445150119515393526">"您已授權一個應用程式設定 VPN 連線。\n\n這個應用程式可以監控您的裝置和網路活動,包括收發電子郵件、使用應用程式和瀏覽網站。"</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"您的 Work 設定檔由下列機構管理:<xliff:g id="ORGANIZATION">%1$s</xliff:g>。\n\n您的管理員可以監控您的網路活動,包括收發電子郵件、使用應用程式及瀏覽網站。\n\n如需詳細資訊,請洽您的管理員。\n\n同時,由於您的裝置已連線至 VPN,您的網路活動也會受到 VPN 監控。"</string>
+ <!-- no translation found for monitoring_description_vpn_profile_owned (2958019119161161530) -->
+ <skip />
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"由於您已連線至 <xliff:g id="APPLICATION">%1$s</xliff:g>,您的網路活動也會受到這個應用程式監控,包括收發電子郵件、使用應用程式和瀏覽網站。"</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"由於您已連線至 <xliff:g id="APPLICATION">%1$s</xliff:g>,您的個人網路活動也會受到這個應用程式監控,包括收發電子郵件、使用應用程式和瀏覽網站。"</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"由於你已連結至「<xliff:g id="APPLICATION">%1$s</xliff:g>」,你的個人網路活動 (包括收發電子郵件、使用應用程式及瀏覽網站) 可能會受到這個應用程式監控。"</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"您的 Work 設定檔是由下列機構管理:<xliff:g id="ORGANIZATION">%1$s</xliff:g>。由於設定檔已連線至 <xliff:g id="APPLICATION">%2$s</xliff:g>,您的工作網路活動也會受到這個應用程式監控,包括收發電子郵件、使用應用程式和瀏覽網站。\n\n詳情請洽您的管理員。"</string>
+ <!-- no translation found for monitoring_description_app_work (7777228449969022305) -->
+ <skip />
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"您的 Work 設定檔是由下列機構管理:<xliff:g id="ORGANIZATION">%1$s</xliff:g>。由於設定檔已連線至 <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>,您的工作網路活動也會受到這個應用程式監控,包括收發電子郵件、使用應用程式和瀏覽網站。\n\n同時由於您也連線至<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>,您的個人網路活動也會受到這個應用程式監控。"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"在您手動解鎖前,裝置將保持鎖定狀態"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"更快取得通知"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 6592bd0..7588eee 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -321,6 +321,9 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="2440098045692399009">"<xliff:g id="DATA_LIMIT">%s</xliff:g> isexwayiso"</string>
<string name="quick_settings_work_mode_label" msgid="6244915274350490429">"Imodi yomsebenzi"</string>
<string name="quick_settings_night_display_label" msgid="3577098011487644395">"Ukukhanya kwasebusuku"</string>
+ <string name="quick_settings_nfc_label" msgid="9012153754816969325">"I-NFC"</string>
+ <string name="quick_settings_nfc_off" msgid="6883274004315134333">"I-NFC ikhutshaziwe"</string>
+ <string name="quick_settings_nfc_on" msgid="6680317193676884311">"I-NFC inikwe amandla"</string>
<string name="recents_empty_message" msgid="808480104164008572">"Azikho izinto zakamuva"</string>
<string name="recents_empty_message_dismissed_all" msgid="2791312568666558651">"Usule yonke into"</string>
<string name="recents_app_info_button_label" msgid="2890317189376000030">"Ulwazi lohlelo lokusebenza"</string>
@@ -334,6 +337,11 @@
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Hlukanisa okuvundlile"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Hlukanisa okumile"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Hlukanisa kwezifiso"</string>
+ <string name="recents_accessibility_dismissed" msgid="2354459747918667050">"Cashisa"</string>
+ <string name="recents_accessibility_open" msgid="1651449827614876864">"Kuvuliwe"</string>
+ <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Hlukanisela isikrini phezulu"</string>
+ <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Hlukanisela isikrini ngakwesokunxele"</string>
+ <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Hlukanisela isikrini ngakwesokudla"</string>
<string-array name="recents_blacklist_array">
</string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Kushajiwe"</string>
@@ -414,20 +422,20 @@
<string name="disconnect_vpn" msgid="1324915059568548655">"Nqamula i-VPN"</string>
<string name="monitoring_description_do_header_generic" msgid="96588491028288691">"Idivayisi yakho iphethwe yi-<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g>."</string>
<string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"I-<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> isebenzisa i-<xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> ukuze iphathe idivayisi yakho."</string>
- <string name="monitoring_description_do_body" msgid="6764108354701060766">"Umlawuli wakho angaqaphela aphinde aphathe izilungiselelo, ukufinyelela kwezinkampani, izinhlelo zokusebenza, idatha ehlotshaniswa nedivayisi yakho, nolwazi lwendawo yedivayisi yakho."</string>
+ <string name="monitoring_description_do_body" msgid="3639594537660975895">"Umlawuli wakho angaqaphela aphinde aphathe izilungiselelo, ukufinyelela kwezinkampani, izinhlelo zokusebenza, idatha ehlotshaniswa nedivayisi yakho, nolwazi lwendawo yedivayisi yakho."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
<string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Funda kabanzi"</string>
<string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Uxhumeke ku-<xliff:g id="VPN_APP">%1$s</xliff:g>, engaqapha umsebenzi wenethiwekhi yakho, ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
<string name="monitoring_description_vpn_settings" msgid="8869300202410505143">"Vula izilungiselelo ze-VPN"</string>
- <string name="monitoring_description_network_logging" msgid="3901006351911787915">"Umlawuli wakho uvule ukungena kwenethiwekhi, okuhlola ithrafikhi kudivayisi yakho.\n\nNgolwazi olubanzi xhumana nomlawuli wakho."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"Umlawuli wakho uvule ukungena kwenethiwekhi, okuhlola ithrafikhi kudivayisi yakho.\n\nNgolwazi olubanzi xhumana nomlawuli wakho."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Unikeze uhlelo lokusebenza imvume yokusetha ukuxhumana kwe-VPN.\n\nLolu hlelo lokusebenza lungahlola idivayisi yakho nomsebenzi wenethiwekhi, ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2054949132145039290">"Iphrofayela yakho yomsebenzi iphethwe yi-<xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nUmqondisi wakho unamandla wokuqaphela umsebenzi wenethiwekhi yakho ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi.\n\nUkuze uthole olunye ulwazi, xhumana nomqondisi wakho.\n\nFuthi uxhumeke ku-VPN, engaqaphela umsebenzi wenethiwekhi yakho."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"Iphrofayela yakho yomsebenzi iphethwe ngu-<xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nUmlawuli wakho uyakwazi ukwengamela umsebenzi wakho wenethiwekhi kufaka phakathi ama-imeyili, izinhlelo zokusebenza, namawebhusayithi.\n\nNgolwazi olubanzi, xhumana nomlawuli wakho.\n\nFuthi uxhumekile ku-VPN, engangamela umsebenzi wakho wenethiwekhi."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"I-VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"Uxhumeke ku-<xliff:g id="APPLICATION">%1$s</xliff:g>, engahlola umsebenzi wakho wenethiwekhi ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Uxhumeke ku-<xliff:g id="APPLICATION">%1$s</xliff:g>, engahlola umsebenzi wenethiwekhi yakho yomuntu siqu, ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Uxhumeke ku-<xliff:g id="APPLICATION">%1$s</xliff:g>, engaqapha umsebenzi wakho womuntu siqu wenethiwekhi, ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi."</string>
- <string name="monitoring_description_app_work" msgid="1754325860918060897">"Iphrofayela yakho yomsebenzi iphethwe yi-<xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ixhumeke ku-<xliff:g id="APPLICATION">%2$s</xliff:g>, engahlola umsebenzi wenethiwekhi yakho yokusebenza, ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi.\n\nUkuze uthole olunye ulwazi, xhumana nomqondisi wakho."</string>
+ <string name="monitoring_description_app_work" msgid="7777228449969022305">"Iphrofayela yakho yomsebenzi iphethwe ngu-<xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ixhumeke ku-<xliff:g id="APPLICATION">%2$s</xliff:g>, engangamela umsebenzi wakho wenethiwekhi, kufaka phakathi ama-imeyili, izinhlelo zokusebenza, namawebhusayithi.\n\nNgolwazi olubanzi, xhumana nomlawuli wakho."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"Iphrofayela yakho yomsebenzi iphethwe yi-<xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ixhumeke ku-<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, engahlola umsebenzi wenethiwekhi yakho yomsebenzi, ofaka ama-imeyili, izinhlelo zokusebenza namawebhusayithi.\n\nFuthi uxhumeke ku-<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, engahlola umsebenzi wenethiwekhi yakho yomuntu siqu."</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Idivayisi izohlala ikhiyekile uze uyivule ngokwenza"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Thola izaziso ngokushesha"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index a229866..7fb513c 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -40,8 +40,9 @@
<attr name="decayTime" format="integer" />
<attr name="orientation" />
</declare-styleable>
+ <attr name="frameColor" format="color" />
<declare-styleable name="BatteryMeterView">
- <attr name="frameColor" format="color" />
+ <attr name="frameColor" />
</declare-styleable>
<declare-styleable name="Clock">
<attr name="amPmStyle" format="enum">
@@ -60,7 +61,7 @@
<attr name="framePadding" format="dimension" />
<!-- {@deprecated Use a statelist in frameColor instead.} -->
<attr name="activeFrameColor" format="color" />
- <attr name="frameColor" format="color" />
+ <attr name="frameColor" />
<attr name="badgeDiameter" format="dimension" />
<attr name="badgeMargin" format="dimension" />
</declare-styleable>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b18b6ac..1ec611a 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>
@@ -100,6 +103,10 @@
<color name="notification_guts_icon_tint">#8a000000</color>
<color name="notification_guts_disabled_icon_tint">#4d000000</color>
+ <!-- Colors of the snooze menu reached via snooze icon behind a notification -->
+ <color name="snooze_snackbar_bg">#FF4A4A4A</color>
+ <color name="snooze_snackbar_text">#FFA6BAFF</color>
+
<color name="assist_orb_color">#ffffff</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 50ef392..80f3a0a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -43,7 +43,7 @@
<!-- Component to be used as the status bar service. Must implement the IStatusBar
interface. This name is in the ComponentName flattened format (package/class) -->
- <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.PhoneStatusBar</string>
+ <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
<!-- Whether or not we show the number in the bar. -->
<bool name="config_statusBarShowNumber">false</bool>
@@ -286,15 +286,22 @@
<string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
<!-- Nav bar button default ordering/layout -->
- <string name="config_navBarLayout" translatable="false">space,back;home;recent,menu_ime</string>
+ <string name="config_navBarLayout" translatable="false">left,back;home;recent,right</string>
<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. -->
<integer name="config_warningTemperature">-1</integer>
+ <!-- Accessibility actions -->
+ <item type="id" name="action_split_task_to_left" />
+ <item type="id" name="action_split_task_to_right" />
+ <item type="id" name="action_split_task_to_top" />
+ <item type="id" name="action_open" />
+ <item type="id" name="action_dimiss" />
+
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6ffdbcb..ddcc4ba 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -106,11 +106,20 @@
<!-- Minimum layouted height of a notification in the statusbar-->
<dimen name="min_notification_layout_height">48dp</dimen>
- <!-- Width of the space containing the gear icon behind a notification -->
- <dimen name="notification_gear_width">64dp</dimen>
+ <!-- Size of the space to place a notification menu item -->
+ <dimen name="notification_menu_icon_size">64dp</dimen>
- <!-- The space around the gear icon displayed behind a notification -->
- <dimen name="notification_gear_padding">20dp</dimen>
+ <!-- The space around a notification menu item -->
+ <dimen name="notification_menu_icon_padding">20dp</dimen>
+
+ <!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
+ <dimen name="snooze_snackbar_min_height">48dp</dimen>
+
+ <!-- The text size of options in the snooze menu. -->
+ <dimen name="snooze_option_text_size">14sp</dimen>
+
+ <!-- The padding around options int the snooze menu. -->
+ <dimen name="snooze_option_padding">8dp</dimen>
<!-- size at which Notification icons will be drawn in the status bar -->
<dimen name="status_bar_icon_drawing_size">17dp</dimen>
@@ -164,7 +173,7 @@
<dimen name="close_handle_underlap">32dp</dimen>
<!-- Height of the status bar header bar -->
- <dimen name="status_bar_header_height">104dp</dimen>
+ <dimen name="status_bar_header_height">126dp</dimen>
<!-- Height of the status bar header bar when expanded -->
<dimen name="status_bar_header_height_expanded">116dp</dimen>
@@ -204,8 +213,8 @@
<!-- The size of the gesture span needed to activate the "pull" notification expansion -->
<dimen name="pull_span_min">25dp</dimen>
- <dimen name="qs_tile_height">88dp</dimen>
- <dimen name="qs_tile_margin">16dp</dimen>
+ <dimen name="qs_tile_height">80dp</dimen>
+ <dimen name="qs_tile_margin">36dp</dimen>
<dimen name="qs_tile_margin_top">16dp</dimen>
<dimen name="qs_quick_tile_size">48dp</dimen>
<dimen name="qs_quick_tile_padding">12dp</dimen>
@@ -421,7 +430,7 @@
<dimen name="qs_time_collapsed_size">14sp</dimen>
<!-- The font size of the time when expanded in QS -->
- <dimen name="qs_time_expanded_size">20sp</dimen>
+ <dimen name="qs_time_expanded_size">14sp</dimen>
<!-- The font size of the "emergency calls only" label in QS -->
<dimen name="qs_emergency_calls_only_text_size">12sp</dimen>
diff --git a/packages/SystemUI/res/values/dimens_grid.xml b/packages/SystemUI/res/values/dimens_grid.xml
index 0b9836ff..5858443 100644
--- a/packages/SystemUI/res/values/dimens_grid.xml
+++ b/packages/SystemUI/res/values/dimens_grid.xml
@@ -22,5 +22,6 @@
<dimen name="recents_grid_task_view_header_height">44dp</dimen>
<dimen name="recents_grid_task_view_header_button_padding">8dp</dimen>
<dimen name="recents_grid_task_view_focused_frame_thickness">8dp</dimen>
+ <dimen name="recents_grid_task_view_rounded_corners_radius">8dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 4a19dde..fc0c82c 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -58,7 +58,6 @@
<item type="id" name="transformation_start_y_tag"/>
<item type="id" name="transformation_start_scale_x_tag"/>
<item type="id" name="transformation_start_scale_y_tag"/>
- <item type="id" name="custom_background_color"/>
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a17430a..5d2117a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -794,6 +794,16 @@
<string name="recents_multistack_add_stack_dialog_split_vertical">Split Vertical</string>
<!-- Recents: MultiStack add stack split custom radio button. [CHAR LIMIT=NONE] -->
<string name="recents_multistack_add_stack_dialog_split_custom">Split Custom</string>
+ <!-- Recents: Accessibility dismiss label -->
+ <string name="recents_accessibility_dismissed">Dismiss</string>
+ <!-- Recents: Accessibility open label -->
+ <string name="recents_accessibility_open">Open</string>
+ <!-- Recents: Accessibility split to the top -->
+ <string name="recents_accessibility_split_screen_top">Split screen to the top</string>
+ <!-- Recents: Accessibility split to the left -->
+ <string name="recents_accessibility_split_screen_left">Split screen to the left</string>
+ <!-- Recents: Accessibility split to the right -->
+ <string name="recents_accessibility_split_screen_right">Split screen to the right</string>
<!-- Fully qualified activity class names to be blacklisted in Recents, add package names into overlay as needed -->
<string-array name="recents_blacklist_array">
@@ -1022,10 +1032,10 @@
<string name="monitoring_title">Network monitoring</string>
<!-- STOPSHIP Monitoring strings still need to be finalized and approved -->
- <!-- Monitoring dialog subtitle for the section describing VPN [CHAR LIMIT=TODO]-->
+ <!-- Monitoring dialog subtitle for the section describing VPN [CHAR LIMIT=35]-->
<string name="monitoring_subtitle_vpn">VPN</string>
- <!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=TODO]-->
+ <!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=35]-->
<string name="monitoring_subtitle_network_logging">Network Logging</string>
<!-- Monitoring dialog disable vpn button [CHAR LIMIT=30] -->
@@ -1041,7 +1051,7 @@
<string name="monitoring_description_do_header_with_name"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> uses <xliff:g id="device_owner_app" example="Google Mobile Management">%2$s</xliff:g> to manage your device.</string>
<!-- Monitoring dialog: Part of text body explaining what a Device Owner app can do [CHAR LIMIT=130] -->
- <string name="monitoring_description_do_body">Your administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.</string>
+ <string name="monitoring_description_do_body">Your admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.</string>
<!-- Monitoring dialog: Space that separates the body text and the "learn more" link that follows it. [CHAR LIMIT=5] -->
<string name="monitoring_description_do_learn_more_separator">" "</string>
@@ -1055,18 +1065,18 @@
<!-- Monitoring dialog: Space that separates the VPN body text and the "Open VPN Settings" link that follows it. [CHAR LIMIT=5] -->
<string name="monitoring_description_vpn_settings_separator">" "</string>
- <!-- Monitoring dialog: Link to open the VPN settings page [CHAR LIMIT=TODO] -->
+ <!-- Monitoring dialog: Link to open the VPN settings page [CHAR LIMIT=60] -->
<string name="monitoring_description_vpn_settings">Open VPN Settings</string>
- <!-- Monitoring dialog: Network logging text [CHAR LIMIT=TODO] -->
- <string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information contact your admin.</string>
+ <!-- Monitoring dialog: Network logging text [CHAR LIMIT=400] -->
+ <string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information, contact your admin.</string>
<!-- Monitoring dialog VPN text [CHAR LIMIT=400] -->
<string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps, and websites.</string>
<!-- Monitoring dialog VPN with profile owner text [CHAR LIMIT=400] -->
- <string name="monitoring_description_vpn_profile_owned">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your administrator.\n\nYou\'re also connected to a VPN, which can monitor your network activity.</string>
+ <string name="monitoring_description_vpn_profile_owned">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour admin is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your admin.\n\nYou\'re also connected to a VPN, which can monitor your network activity.</string>
<!-- Name for a generic legacy VPN connection [CHAR LIMIT=20] -->
<string name="legacy_vpn_name">VPN</string>
@@ -1081,7 +1091,7 @@
<string name="branded_monitoring_description_app_personal">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps, and websites.</string>
<!-- Monitoring dialog text for single app (inside work profile) [CHAR LIMIT=400] -->
- <string name="monitoring_description_app_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string>
+ <string name="monitoring_description_app_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nFor more information, contact your admin.</string>
<!-- Monitoring dialog text for multiple apps (in personal and work profiles) [CHAR LIMIT=400] -->
<string name="monitoring_description_app_personal_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application_work">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nYou\'re also connected to <xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network activity.</string>
@@ -1365,8 +1375,28 @@
<!-- Notification: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
<string name="notification_done">Done</string>
- <!-- Notification: Gear: Content description for the gear. [CHAR LIMIT=NONE] -->
- <string name="notification_gear_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> notification controls</string>
+ <!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
+ <string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
+
+ <!-- Notification: Menu row: Content description for the gear menu item. [CHAR LIMIT=NONE] -->
+ <string name="notification_menu_gear_description">notification controls</string>
+
+ <!-- Notification: Menu row: Content description for the snooze icon. [CHAR LIMIT=NONE] -->
+ <string name="notification_menu_snooze_description">notification snooze options</string>
+
+ <!-- Notification: Menu row: Snooze options: 15 minute option. [CHAR LIMIT=50]-->
+ <string name="snooze_option_15_min">15 minutes</string>
+ <!-- Notification: Menu row: Snooze options: 30 minute option. [CHAR LIMIT=50]-->
+ <string name="snooze_option_30_min">30 minutes</string>
+ <!-- Notification: Menu row: Snooze options: 1 hour option. [CHAR LIMIT=50]-->
+ <string name="snooze_option_1_hour">1 hour</string>
+ <!-- Notification: Menu row: Snooze options: don't snooze option. [CHAR LIMIT=50] -->
+ <string name="snooze_option_dont_snooze">Don\'t snooze</string>
+ <!-- Notification: Menu row: Snooze undo button label. [CHAR LIMIT=50]-->
+ <string name="snooze_undo">UNDO</string>
+
+ <!-- Notification: Menu row: Snooze: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
+ <string name="snoozed_for_time">Snoozed for <xliff:g id="time_amount" example="15 minutes">%1$s</xliff:g></string>
<!-- Title of the battery settings detail panel [CHAR LIMIT=20] -->
<string name="battery_panel_title">Battery usage</string>
@@ -1513,56 +1543,71 @@
<!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
<string name="nav_bar">Navigation bar</string>
- <!-- SysUI Tuner: Group of buttons that show on the start of the screen [CHAR LIMIT=30] -->
- <string name="start">Start</string>
- <!-- SysUI Tuner: Group of buttons that show on the center of the screen [CHAR LIMIT=30] -->
- <string name="center">Center</string>
- <!-- SysUI Tuner: Group of buttons that show on the end of the screen [CHAR LIMIT=30] -->
- <string name="end">End</string>
- <!-- SysUI Tuner: Name of space used in custom navigation bar layouts [CHAR LIMIT=30] -->
- <string name="space">Spacer</string>
+ <!-- SysUI Tuner: Button that controls layout of navigation bar [CHAR LIMIT=60] -->
+ <string name="nav_bar_layout">Layout</string>
+
+ <!-- SysUI Tuner: Label for section of settings about the left nav button [CHAR LIMIT=60] -->
+ <string name="nav_bar_left">Left</string>
+
+ <!-- SysUI Tuner: Label for section of settings about the right nav button [CHAR LIMIT=60] -->
+ <string name="nav_bar_right">Right</string>
+
+ <!-- SysUI Tuner: Setting for button type in nav bar [CHAR LIMIT=60] -->
+ <string name="nav_bar_button_type">Button type</string>
+
+ <!-- SysUI Tuner: Added to nav bar option to indicate it is the default [CHAR LIMIT=60] -->
+ <string name="nav_bar_default"> (default)</string>
+
+ <!-- SysUI Tuner: Labels for different types of navigation bar buttons [CHAR LIMIT=60] -->
+ <string-array name="nav_bar_buttons">
+ <item>Clipboard</item>
+ <item>Keycode</item>
+ <item>Menu / Keyboard Switcher</item>
+ <item>None</item>
+ </string-array>
+ <string-array name="nav_bar_button_values" translatable="false">
+ <item>clipboard</item>
+ <item>key</item>
+ <item>menu_ime</item>
+ <item>space</item>
+ </string-array>
+
+ <!-- SysUI Tuner: Labels for different types of navigation bar layouts [CHAR LIMIT=60] -->
+ <string-array name="nav_bar_layouts">
+ <item>Divided (default)</item>
+ <item>Centered</item>
+ <item>Left-aligned</item>
+ <item>Right-aligned</item>
+ </string-array>
+
+ <string-array name="nav_bar_layouts_values" translatable="false">
+ <item>default</item>
+ <item>left;back,home,recent;right</item>
+ <item>left,back,home,recent,right;space;space</item>
+ <item>space;space;left,back,home,recent,right</item>
+ </string-array>
+
<!-- SysUI Tuner: Name of Combination Menu / Keyboard Switcher button [CHAR LIMIT=30] -->
<string name="menu_ime">Menu / Keyboard Switcher</string>
- <!-- SysUI Tuner: Title for dialog to add a button [CHAR LIMIT=30] -->
- <string name="select_button">Select button to add</string>
- <!-- SysUI Tuner: Button to add a button [CHAR LIMIT=30] -->
- <string name="add_button">Add button</string>
<!-- SysUI Tuner: Save the current settings [CHAR LIMIT=30] -->
<string name="save">Save</string>
<!-- SysUI Tuner: Reset to default settings [CHAR LIMIT=30] -->
<string name="reset">Reset</string>
- <!-- SysUI Tuner: Title of no home warning dialog [CHAR LIMIT=30] -->
- <string name="no_home_title">No home button found</string>
- <!-- SysUI Tuner: Message of no home warning dialog [CHAR LIMIT=NONE] -->
- <string name="no_home_message">A home button is required to be able to navigate this device. Please add a home button before saving.</string>
-
<!-- SysUI Tuner: Adjust button width dialog title [CHAR LIMIT=60] -->
<string name="adjust_button_width">Adjust button width</string>
<!-- SysUI Tuner: Nav bar button that holds the clipboard [CHAR LIMIT=30] -->
<string name="clipboard">Clipboard</string>
- <!-- SysUI Tuner: Description of nav bar button that holds the clipboard [CHAR LIMIT=NONE] -->
- <string name="clipboard_description">The Clipboard allows items to be dragged directly to the clipboard. Items can also be dragged directly out of the clipboard when present.</string>
-
<!-- SysUI Tuner: Accessibility description for custom nav key [CHAR LIMIT=NONE] -->
<string name="accessibility_key">Custom navigation button</string>
<!-- SysUI Tuner: Nav bar button that emulates a keycode [CHAR LIMIT=30] -->
<string name="keycode">Keycode</string>
- <!-- SysUI Tuner: Description of nav bar button that emulates a keycode [CHAR LIMIT=NONE] -->
- <string name="keycode_description">Keycode buttons allow keyboard keys to
- be added to the Navigation Bar. When pressed they emulate the selected
- keyboard key. First the key must be selected for the button, followed
- by an image to be shown on the button.</string>
-
- <!-- SysUI Tuner: Title of dialog to select which key to emulate [CHAR LIMIT=60] -->
- <string name="select_keycode">Select Keyboard Button</string>
-
- <!-- SysUI Tuner: Label for preview area in navigation bar tuner [CHAR LIMIT=NONE] -->
- <string name="preview">Preview</string>
+ <!-- SysUI Tuner: Settings to change nav bar icon [CHAR LIMIT=30] -->
+ <string name="icon">Icon</string>
<!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
<string name="drag_to_add_tiles">Drag to add tiles</string>
@@ -1742,5 +1787,30 @@
<!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] -->
<string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string>
+ <!-- SysUI Tuner: Group of settings for left lock screen affordance [CHAR LIMIT=60] -->
+ <string name="lockscreen_left">Left</string>
+
+ <!-- SysUI Tuner: Group of settings for right lock screen affordance [CHAR LIMIT=60] -->
+ <string name="lockscreen_right">Right</string>
+
+ <!-- SysUI Tuner: Switch controlling whether to customize lock screen button [CHAR LIMIT=60] -->
+ <string name="lockscreen_customize">Customize shortcut</string>
+
+ <!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] -->
+ <string name="lockscreen_shortcut">Shortcut</string>
+
+ <!-- SysUI Tuner: Switch to control if device gets unlocked [CHAR LIMIT=60] -->
+ <string name="lockscreen_unlock">Prompt for password</string>
+
+ <!-- Title for the notification channel containing important alerts like low battery. [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_alerts">Alerts</string>
+ <!-- Title for the notification channel dedicated to screenshot progress. [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_screenshot">Screenshots</string>
+ <!-- Title for the notification channel for urgent security issues. [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_security">Security</string>
+ <!-- Title for the notification channel containing multi-user status information. [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_user_status">User status</string>
+ <!-- Title for the notification channel for problems with storage (i.e. low disk). [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_storage">Storage</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index a9c858c..c5a5518 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -93,37 +93,34 @@
<item name="android:textColor">#FF808080</item>
</style>
- <style name="TextAppearance.StatusBar.TextButton"
- parent="@*android:style/TextAppearance.StatusBar">
- <item name="android:textAppearance">?android:attr/textAppearance</item>
- <item name="android:textStyle">normal</item>
- <item name="android:textColor">#FFFFFFFF</item>
- </style>
-
<style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/status_bar_clock_size</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
- <style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar" />
+ <style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar">
+ <item name="android:textColor">?android:attr/textColorTertiary</item>
+ </style>
<style name="TextAppearance.StatusBar.Expanded.Clock">
<item name="android:textSize">@dimen/qs_time_expanded_size</item>
- <item name="android:fontFamily">sans-serif-medium</item>
+ <item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textStyle">normal</item>
</style>
<style name="TextAppearance.StatusBar.Expanded.Date">
<item name="android:textSize">@dimen/qs_date_collapsed_size</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:fontFamily">sans-serif-condensed</item>
</style>
<style name="TextAppearance.StatusBar.Expanded.AboveDateTime">
<item name="android:textSize">@dimen/qs_emergency_calls_only_text_size</item>
<item name="android:textStyle">normal</item>
- <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textColor">?android:attr/textColorTertiary</item>
</style>
<style name="TextAppearance.StatusBar.Expanded.EmergencyCallsOnly"
@@ -135,7 +132,7 @@
<style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
<item name="android:textSize">16sp</item>
<item name="android:textStyle">normal</item>
- <item name="android:textColor">@color/qs_user_detail_name</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="TextAppearance.StatusBar.Expanded.UserSwitcher.UserName" />
@@ -172,7 +169,7 @@
<style name="TextAppearance.QS.DetailButton">
<item name="android:textSize">@dimen/qs_detail_button_text_size</item>
- <item name="android:textColor">@color/qs_detail_button</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textAllCaps">true</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:gravity">center</item>
@@ -184,7 +181,7 @@
<style name="TextAppearance.QS.DetailEmpty">
<item name="android:textSize">@dimen/qs_detail_empty_text_size</item>
- <item name="android:textColor">@color/qs_subhead</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="TextAppearance.QS.Subhead">
@@ -386,6 +383,20 @@
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
+ <style name="TextAppearance.SnoozeSnackBar">
+ <item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">roboto-regular</item>
+ <item name="android:textColor">@android:color/white</item>
+ </style>
+
+ <style name="TextAppearance.SnoozeSnackBar.Button">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:fontFamily">sans-serif-medium</item>
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">@color/snooze_snackbar_text</item>
+ </style>
+
<style name="edit_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
<item name="android:colorBackground">?android:attr/colorSecondary</item>
</style>
diff --git a/packages/SystemUI/res/xml/lockscreen_settings.xml b/packages/SystemUI/res/xml/lockscreen_settings.xml
new file mode 100644
index 0000000..73e29af
--- /dev/null
+++ b/packages/SystemUI/res/xml/lockscreen_settings.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:title="@string/other">
+
+ <PreferenceCategory
+ android:key="left"
+ android:title="@string/lockscreen_left">
+
+ <SwitchPreference
+ android:key="customize"
+ android:title="@string/lockscreen_customize" />
+
+ <Preference
+ android:key="shortcut"
+ android:title="@string/lockscreen_shortcut" />
+
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="sysui_keyguard_left_unlock"
+ android:title="@string/lockscreen_unlock"
+ sysui:defValue="true" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="right"
+ android:title="@string/lockscreen_right">
+
+ <SwitchPreference
+ android:key="customize"
+ android:title="@string/lockscreen_customize" />
+
+ <Preference
+ android:key="shortcut"
+ android:title="@string/lockscreen_shortcut" />
+
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="sysui_keyguard_right_unlock"
+ android:title="@string/lockscreen_unlock"
+ sysui:defValue="true" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/nav_bar_tuner.xml b/packages/SystemUI/res/xml/nav_bar_tuner.xml
new file mode 100644
index 0000000..6fa8bec
--- /dev/null
+++ b/packages/SystemUI/res/xml/nav_bar_tuner.xml
@@ -0,0 +1,79 @@
+<?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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:title="@string/nav_bar">
+
+ <ListPreference
+ android:key="layout"
+ android:title="@string/nav_bar_layout"
+ android:summary="%s"
+ android:persistent="false"
+ android:entries="@array/nav_bar_layouts"
+ android:entryValues="@array/nav_bar_layouts_values" />
+
+ <PreferenceCategory
+ android:key="left"
+ android:title="@string/nav_bar_left">
+
+ <DropDownPreference
+ android:key="type_left"
+ android:title="@string/nav_bar_button_type"
+ android:persistent="false"
+ android:summary="%s"
+ android:entries="@array/nav_bar_buttons"
+ android:entryValues="@array/nav_bar_button_values" />
+
+ <Preference
+ android:key="keycode_left"
+ android:persistent="false"
+ android:title="@string/keycode" />
+
+ <com.android.systemui.tuner.BetterListPreference
+ android:key="icon_left"
+ android:persistent="false"
+ android:summary="%s"
+ android:title="@string/icon" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="right"
+ android:title="@string/nav_bar_right">
+
+ <DropDownPreference
+ android:key="type_right"
+ android:title="@string/nav_bar_button_type"
+ android:summary="%s"
+ android:persistent="false"
+ android:entries="@array/nav_bar_buttons"
+ android:entryValues="@array/nav_bar_button_values" />
+
+ <Preference
+ android:key="keycode_right"
+ android:persistent="false"
+ android:title="@string/keycode" />
+
+ <com.android.systemui.tuner.BetterListPreference
+ android:key="icon_right"
+ android:persistent="false"
+ android:summary="%s"
+ android:title="@string/icon" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/other_settings.xml b/packages/SystemUI/res/xml/other_settings.xml
index 18cb930..7719d5e 100644
--- a/packages/SystemUI/res/xml/other_settings.xml
+++ b/packages/SystemUI/res/xml/other_settings.xml
@@ -23,10 +23,5 @@
android:key="power_notification_controls"
android:title="@string/tuner_full_importance_settings"
android:fragment="com.android.systemui.tuner.PowerNotificationControlsFragment"/>
-e
- <com.android.systemui.tuner.ThemePreference
- android:key="theme"
- android:title="@string/theme"
- android:summary="%s" />
</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 59a10da..94a7c07 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -150,12 +150,15 @@
</PreferenceScreen>
- <!--
<Preference
android:key="nav_bar"
android:title="@string/nav_bar"
android:fragment="com.android.systemui.tuner.NavBarTuner" />
- -->
+
+ <Preference
+ android:key="lockscreen"
+ android:title="@string/accessibility_desc_lock_screen"
+ android:fragment="com.android.systemui.tuner.LockscreenFragment" />
<Preference
android:key="other"
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..14c67fe
--- /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 StatusBar, 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/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index 07ef5e0..5a6afca 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -292,12 +292,16 @@
}
int backgroundColor = getBackgroundColor(darkIntensity);
int fillColor = getFillColor(darkIntensity);
+ setColors(fillColor, backgroundColor);
+ mOldDarkIntensity = darkIntensity;
+ }
+
+ public void setColors(int fillColor, int backgroundColor) {
mIconTint = fillColor;
mFramePaint.setColor(backgroundColor);
mBoltPaint.setColor(fillColor);
mChargeColor = fillColor;
invalidateSelf();
- mOldDarkIntensity = darkIntensity;
}
private int getBackgroundColor(float darkIntensity) {
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index ef1c25d..b30b596 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -72,6 +72,8 @@
@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);
@@ -97,12 +99,11 @@
}
- public void setBatteryController(BatteryController mBatteryController) {
- this.mBatteryController = mBatteryController;
- mDrawable.setBatteryController(mBatteryController);
- }
-
public void setDarkIntensity(float f) {
mDrawable.setDarkIntensity(f);
}
+
+ public void setRawColors(int fgColor, int bgColor) {
+ mDrawable.setColors(fgColor, bgColor);
+ }
}
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/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index c14b17f..1d55ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -27,7 +27,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.LatencyTracker;
import com.android.systemui.statusbar.phone.FingerprintUnlockController;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
/**
* Class that only runs on debuggable builds that listens to broadcasts that simulate actions in the
@@ -72,7 +72,7 @@
}
private void fakeWakeAndUnlock() {
- FingerprintUnlockController fingerprintUnlockController = getComponent(PhoneStatusBar.class)
+ FingerprintUnlockController fingerprintUnlockController = getComponent(StatusBar.class)
.getFingerprintUnlockController();
fingerprintUnlockController.onFingerprintAcquired();
fingerprintUnlockController.onFingerprintAuthenticated(
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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 5c50b5c..a95713f 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -32,7 +32,9 @@
import android.view.accessibility.AccessibilityEvent;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.NotificationMenuRow;
import java.util.HashMap;
@@ -85,10 +87,12 @@
private int mFalsingThreshold;
private boolean mTouchAboveFalsingThreshold;
private boolean mDisableHwLayers;
+ private Context mContext;
private HashMap<View, Animator> mDismissPendingMap = new HashMap<>();
public SwipeHelper(int swipeDirection, Callback callback, Context context) {
+ mContext = context;
mCallback = callback;
mHandler = new Handler();
mSwipeDirection = swipeDirection;
@@ -249,6 +253,7 @@
}
}
+ @Override
public boolean onInterceptTouchEvent(final MotionEvent ev) {
final int action = ev.getAction();
@@ -280,7 +285,8 @@
mCurrView.getLocationOnScreen(mTmpPos);
final int x = (int) ev.getRawX() - mTmpPos[0];
final int y = (int) ev.getRawY() - mTmpPos[1];
- mLongPressListener.onLongPress(mCurrView, x, y);
+ mLongPressListener.onLongPress(mCurrView, x, y,
+ NotificationMenuRow.getLongpressMenuItem(mContext));
}
}
};
@@ -379,6 +385,7 @@
animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+ @Override
public void onAnimationUpdate(ValueAnimator animation) {
onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
}
@@ -401,10 +408,12 @@
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
+ @Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
+ @Override
public void onAnimationEnd(Animator animation) {
updateSwipeProgressFromOffset(animView, canBeDismissed);
mDismissPendingMap.remove(animView);
@@ -435,6 +444,7 @@
public void snapChild(final View animView, final float targetLeft, float velocity) {
final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+ @Override
public void onAnimationUpdate(ValueAnimator animation) {
onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
}
@@ -447,6 +457,7 @@
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.addListener(new AnimatorListenerAdapter() {
+ @Override
public void onAnimationEnd(Animator animator) {
mSnappingChild = false;
updateSwipeProgressFromOffset(animView, canBeDismissed);
@@ -522,6 +533,7 @@
}
}
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLongPressSent) {
return true;
@@ -690,6 +702,6 @@
* Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
* @return whether the longpress was handled
*/
- boolean onLongPress(View v, int x, int y);
+ boolean onLongPress(View v, int x, int y, MenuItem item);
}
}
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/statusbar/SystemBars.java b/packages/SystemUI/src/com/android/systemui/SystemBars.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/SystemBars.java
rename to packages/SystemUI/src/com/android/systemui/SystemBars.java
index 275fd70..6623cabe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SystemBars.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemBars.java
@@ -1,20 +1,18 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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.
+ * 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;
+package com.android.systemui;
import android.content.res.Configuration;
import android.provider.Settings;
@@ -36,7 +34,7 @@
private static final int WAIT_FOR_BARS_TO_DIE = 500;
// in-process fallback implementation, per the product config
- private BaseStatusBar mStatusBar;
+ private SystemUI mStatusBar;
@Override
public void start() {
@@ -71,7 +69,7 @@
throw andLog("Error loading status bar component: " + clsName, t);
}
try {
- mStatusBar = (BaseStatusBar) cls.newInstance();
+ mStatusBar = (SystemUI) cls.newInstance();
} catch (Throwable t) {
throw andLog("Error creating status bar component: " + clsName, t);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index bd4e3dc..9515585 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;
@@ -43,10 +42,10 @@
import com.android.systemui.shortcut.ShortcutKeyDispatcher;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.SystemBars;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.usb.StorageNotification;
+import com.android.systemui.util.NotificationChannels;
import com.android.systemui.volume.VolumeUI;
import java.util.HashMap;
@@ -64,8 +63,10 @@
* The classes of the stuff to start.
*/
private final Class<?>[] SERVICES = new Class[] {
+ Dependency.class,
FragmentService.class,
TunerService.class,
+ NotificationChannels.class,
CommandQueue.CommandQueueStart.class,
KeyguardViewMediator.class,
Recents.class,
@@ -207,11 +208,11 @@
PluginManager.getInstance(this).addPluginListener(OverlayPlugin.ACTION,
new PluginListener<OverlayPlugin>() {
@Override
- public void onPluginConnected(OverlayPlugin plugin) {
- PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
- if (phoneStatusBar != null) {
- plugin.setup(phoneStatusBar.getStatusBarWindow(),
- phoneStatusBar.getNavigationBarView());
+ public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
+ StatusBar statusBar = getComponent(StatusBar.class);
+ if (statusBar != null) {
+ plugin.setup(statusBar.getStatusBarWindow(),
+ statusBar.getNavigationBarView());
}
}
}, OverlayPlugin.VERSION, true /* Allow multiple plugins */);
@@ -239,8 +240,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..1ff0701 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -18,41 +18,27 @@
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.R;
-import com.android.systemui.assist.AssistManager;
+import com.android.systemui.Dependency.DependencyProvider;
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;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarWindowManager;
-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.NextAlarmController;
-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.volume.VolumeDialogController;
/**
@@ -109,29 +95,24 @@
}
public NotificationIconAreaController createNotificationIconAreaController(Context context,
- PhoneStatusBar phoneStatusBar) {
- return new NotificationIconAreaController(context, phoneStatusBar);
+ StatusBar statusBar) {
+ return new NotificationIconAreaController(context, statusBar);
}
- 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);
+ public KeyguardIndicationController createKeyguardIndicationController(Context context,
+ ViewGroup indicationArea, LockIcon lockIcon) {
+ return new KeyguardIndicationController(context, indicationArea, lockIcon);
+ }
+
+ public QSTileHost createQSTileHost(Context context, StatusBar statusBar,
+ 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/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 05e5f6b..11e0f28 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -18,7 +18,9 @@
import android.app.Service;
import android.content.Intent;
+import android.os.Build;
import android.os.IBinder;
+import android.os.SystemProperties;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -29,6 +31,11 @@
public void onCreate() {
super.onCreate();
((SystemUIApplication) getApplication()).startServicesIfNeeded();
+
+ // For debugging RescueParty
+ if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
+ throw new RuntimeException();
+ }
}
@Override
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/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ce89aab..99c8c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -80,7 +80,7 @@
import com.android.systemui.SystemUIFactory;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.statusbar.phone.FingerprintUnlockController;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarWindowManager;
@@ -1955,11 +1955,11 @@
Trace.endSection();
}
- public StatusBarKeyguardViewManager registerStatusBar(PhoneStatusBar phoneStatusBar,
+ public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
ViewGroup container, StatusBarWindowManager statusBarWindowManager,
ScrimController scrimController,
FingerprintUnlockController fingerprintUnlockController) {
- mStatusBarKeyguardViewManager.registerStatusBar(phoneStatusBar, container,
+ mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
statusBarWindowManager, scrimController, fingerprintUnlockController,
mDismissCallbackRegistry);
return mStatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 63d1cc2..23eaed9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard;
import static android.app.ActivityManager.TaskDescription;
+import static android.app.ActivityManager.StackId;
import android.annotation.ColorInt;
import android.annotation.UserIdInt;
@@ -31,6 +32,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
+import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -91,10 +93,6 @@
// Draw captions overlaid on the content view, so the whole window is one solid color.
setOverlayWithDecorCaptionEnabled(true);
- // Match task description to the task stack we are replacing so it's still recognizably the
- // original task stack with the same icon and title text.
- setTaskDescription(new TaskDescription(null, null, color));
-
// Blank out the activity. When it is on-screen it will look like a Recents thumbnail with
// redaction switched on.
final View blankView = new View(this);
@@ -127,6 +125,11 @@
return;
}
+ @Override
+ public void setTaskDescription(TaskDescription taskDescription) {
+ // Use the previous activity's task description.
+ }
+
private final BroadcastReceiver mLockEventReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -159,9 +162,23 @@
credential.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
try {
- ActivityManager.getService().startConfirmDeviceCredentialIntent(credential);
+ ActivityManager.getService().startConfirmDeviceCredentialIntent(credential,
+ getChallengeOptions().toBundle());
} catch (RemoteException e) {
Log.e(TAG, "Failed to start confirm credential intent", e);
}
}
+
+ private ActivityOptions getChallengeOptions() {
+ // If we are taking up the whole screen, just use the default animation of clipping the
+ // credentials activity into the entire foreground.
+ if (!isInMultiWindowMode()) {
+ return ActivityOptions.makeBasic();
+ }
+
+ // Otherwise, animate the transition from this part of the screen to fullscreen
+ // using our own decor as the starting position.
+ final View view = getWindow().getDecorView();
+ return ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index 2c41a08..e6483f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -42,9 +42,8 @@
Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
.setComponent(new ComponentName(mContext, WorkLockActivity.class))
.putExtra(Intent.EXTRA_USER_ID, userId)
- .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
- | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchTaskId(taskId);
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 728d558..9788903 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -22,6 +22,7 @@
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
+import android.media.PlayerBase;
import android.net.Uri;
import android.os.Looper;
import android.os.PowerManager;
@@ -81,6 +82,12 @@
(AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
try {
MediaPlayer player = new MediaPlayer();
+ if (mCmd.attributes == null) {
+ mCmd.attributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build();
+ }
player.setAudioAttributes(mCmd.attributes);
player.setDataSource(mCmd.context, mCmd.uri);
player.setLooping(mCmd.looping);
@@ -92,13 +99,11 @@
if (mAudioManagerWithAudioFocus == null) {
if (mDebug) Log.d(mTag, "requesting AudioFocus");
if (mCmd.looping) {
- audioManager.requestAudioFocus(null,
- AudioAttributes.toLegacyStreamType(mCmd.attributes),
- AudioManager.AUDIOFOCUS_GAIN);
+ audioManager.requestAudioFocus(null, mCmd.attributes,
+ AudioManager.AUDIOFOCUS_GAIN, 0);
} else {
- audioManager.requestAudioFocus(null,
- AudioAttributes.toLegacyStreamType(mCmd.attributes),
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
+ audioManager.requestAudioFocus(null, mCmd.attributes,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
}
mAudioManagerWithAudioFocus = audioManager;
} else {
@@ -296,6 +301,7 @@
*/
@Deprecated
public void play(Context context, Uri uri, boolean looping, int stream) {
+ PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play");
Command cmd = new Command();
cmd.requestTime = SystemClock.uptimeMillis();
cmd.code = PLAY;
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 a2d7d6b..3df557d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -16,14 +16,20 @@
package com.android.systemui.pip.phone;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackInfo;
+import android.app.ActivityOptions;
import android.app.IActivityManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
@@ -49,6 +55,7 @@
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
private PipMenuActivityController mMenuController;
+ private PipMediaController mMediaController;
private PipTouchHandler mTouchHandler;
/**
@@ -57,7 +64,11 @@
TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onActivityPinned() {
+ if (!checkCurrentUserId(false /* debug */)) {
+ return;
+ }
mTouchHandler.onActivityPinned();
+ mMediaController.onActivityPinned();
}
@Override
@@ -66,8 +77,27 @@
}
@Override
- public void onPinnedActivityRestartAttempt() {
- // TODO(winsonc): Hide the menu and expand the PiP
+ public void onPinnedActivityRestartAttempt(ComponentName sourceComponent) {
+ if (!checkCurrentUserId(false /* debug */)) {
+ return;
+ }
+
+ // Expand the activity back to fullscreen only if it was attempted to be restarted from
+ // another package than the top activity in the stack
+ boolean expandPipToFullscreen = true;
+ if (sourceComponent != null) {
+ ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
+ mActivityManager);
+ if (topActivity != null && topActivity.getPackageName().equals(
+ sourceComponent.getPackageName())) {
+ expandPipToFullscreen = false;
+ }
+ }
+ if (expandPipToFullscreen) {
+ mTouchHandler.expandPinnedStackToFullscreen();
+ } else {
+ Log.w(TAG, "Can not expand PiP to fullscreen via intent from the same package.");
+ }
}
};
@@ -91,7 +121,7 @@
@Override
public void onActionsChanged(ParceledListSlice actions) {
mHandler.post(() -> {
- mMenuController.setActions(actions);
+ mMenuController.setAppActions(actions);
});
}
@@ -127,7 +157,9 @@
}
SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
- mMenuController = new PipMenuActivityController(context, mActivityManager, mWindowManager);
+ mMediaController = new PipMediaController(context, mActivityManager);
+ mMenuController = new PipMenuActivityController(context, mActivityManager, mWindowManager,
+ mMediaController);
mTouchHandler = new PipTouchHandler(context, mMenuController, mActivityManager,
mWindowManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
new file mode 100644
index 0000000..d96baa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip.phone;
+
+import android.app.IActivityManager;
+import android.app.RemoteAction;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.media.session.MediaController;
+import android.media.session.MediaController.TransportControls;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Interfaces with the {@link MediaSessionManager} to compose the right set of actions to show (only
+ * if there are no actions from the PiP activity itself). The active media controller is only set
+ * when there is a media session from the top PiP activity.
+ */
+public class PipMediaController {
+
+ /**
+ * A listener interface to receive notification on changes to the media actions.
+ */
+ public interface ActionListener {
+ /**
+ * Called when the media actions changes.
+ */
+ void onMediaActionsChanged(List<RemoteAction> actions);
+ }
+
+ private final Context mContext;
+ private final IActivityManager mActivityManager;
+
+ private final MediaSessionManager mMediaSessionManager;
+ private MediaController mMediaController;
+
+ private RemoteAction mPauseAction;
+ private RemoteAction mPlayAction;
+
+ private MediaController.Callback mPlaybackChangedListener = new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ if (!mListeners.isEmpty()) {
+ notifyActionsChanged(getMediaActions());
+ }
+ }
+ };
+
+ private ArrayList<ActionListener> mListeners = new ArrayList<>();
+
+ public PipMediaController(Context context, IActivityManager activityManager) {
+ mContext = context;
+ mActivityManager = activityManager;
+
+ createMediaActions();
+ mMediaSessionManager =
+ (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ mMediaSessionManager.addOnActiveSessionsChangedListener(controllers -> {
+ resolveActiveMediaController(controllers);
+ }, null);
+ }
+
+ /**
+ * Handles when an activity is pinned.
+ */
+ public void onActivityPinned() {
+ // Once we enter PiP, try to find the active media controller for the top most activity
+ resolveActiveMediaController(mMediaSessionManager.getActiveSessions(null));
+ }
+
+ /**
+ * Adds a new media action listener.
+ */
+ public void addListener(ActionListener listener) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ listener.onMediaActionsChanged(getMediaActions());
+ }
+ }
+
+ /**
+ * Removes a media action listener.
+ */
+ public void removeListener(ActionListener listener) {
+ listener.onMediaActionsChanged(Collections.EMPTY_LIST);
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Gets the set of media actions currently available.
+ */
+ private List<RemoteAction> getMediaActions() {
+ if (mMediaController == null || mMediaController.getPlaybackState() == null) {
+ return Collections.EMPTY_LIST;
+ }
+
+ ArrayList<RemoteAction> mediaActions = new ArrayList<>();
+ int state = mMediaController.getPlaybackState().getState();
+ boolean isPlaying = MediaSession.isActiveState(state);
+ long actions = mMediaController.getPlaybackState().getActions();
+ if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
+ mediaActions.add(mPauseAction);
+ } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
+ mediaActions.add(mPlayAction);
+ }
+ return mediaActions;
+ }
+
+ /**
+ * Creates the standard media buttons that we may show.
+ */
+ private void createMediaActions() {
+ String pauseDescription = mContext.getString(R.string.pip_pause);
+ mPauseAction = new RemoteAction(Icon.createWithResource(mContext,
+ R.drawable.ic_pause_white_24dp), pauseDescription, pauseDescription,
+ action -> mMediaController.getTransportControls().pause());
+
+ String playDescription = mContext.getString(R.string.pip_play);
+ mPlayAction = new RemoteAction(Icon.createWithResource(mContext,
+ R.drawable.ic_play_arrow_white_24dp), playDescription, playDescription,
+ action -> mMediaController.getTransportControls().play());
+ }
+
+ /**
+ * Tries to find and set the active media controller for the top PiP activity.
+ */
+ private void resolveActiveMediaController(List<MediaController> controllers) {
+ if (controllers != null) {
+ final ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
+ mActivityManager);
+ if (topActivity != null) {
+ for (int i = 0; i < controllers.size(); i++) {
+ final MediaController controller = controllers.get(i);
+ if (controller.getPackageName().equals(topActivity.getPackageName())) {
+ setActiveMediaController(controller);
+ return;
+ }
+ }
+ }
+ }
+ setActiveMediaController(null);
+ }
+
+ /**
+ * Sets the active media controller for the top PiP activity.
+ */
+ private void setActiveMediaController(MediaController controller) {
+ if (controller != mMediaController) {
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mPlaybackChangedListener);
+ }
+ mMediaController = controller;
+ if (controller != null) {
+ controller.registerCallback(mPlaybackChangedListener);
+ }
+ if (!mListeners.isEmpty()) {
+ notifyActionsChanged(getMediaActions());
+ }
+
+ // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV)
+ }
+ }
+
+ /**
+ * Notifies all listeners that the actions have changed.
+ */
+ private void notifyActionsChanged(List<RemoteAction> actions) {
+ if (!mListeners.isEmpty()) {
+ mListeners.forEach(l -> l.onMediaActionsChanged(actions));
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 0f7647d..6ef30c0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.pip.phone;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
@@ -5,6 +21,7 @@
import android.app.ActivityManager.StackInfo;
import android.app.ActivityOptions;
import android.app.IActivityManager;
+import android.app.RemoteAction;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
@@ -16,11 +33,20 @@
import android.util.Log;
import android.view.IWindowManager;
-import java.util.ArrayList;
+import com.android.systemui.pip.phone.PipMediaController.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the PiP menu activity.
+ *
+ * The current media session provides actions whenever there are no valid actions provided by the
+ * current PiP activity. Otherwise, those actions always take precedence.
+ */
public class PipMenuActivityController {
- private static final String TAG = "PipMenuActivityController";
+ private static final String TAG = "PipMenuActController";
public static final String EXTRA_CONTROLLER_MESSENGER = "messenger";
public static final String EXTRA_ACTIONS = "actions";
@@ -59,9 +85,12 @@
private Context mContext;
private IActivityManager mActivityManager;
private IWindowManager mWindowManager;
+ private PipMediaController mMediaController;
private ArrayList<Listener> mListeners = new ArrayList<>();
- private ParceledListSlice mActions;
+ private ParceledListSlice mAppActions;
+ private ParceledListSlice mMediaActions;
+ private boolean mVisible;
private Messenger mToActivityMessenger;
private Messenger mMessenger = new Messenger(new Handler() {
@@ -70,13 +99,13 @@
switch (msg.what) {
case MESSAGE_MENU_VISIBILITY_CHANGED: {
boolean visible = msg.arg1 > 0;
- mListeners.forEach(l -> l.onPipMenuVisibilityChanged(visible));
+ onMenuVisibilityChanged(visible);
break;
}
case MESSAGE_EXPAND_PIP: {
mListeners.forEach(l -> l.onPipExpand());
// Preemptively mark the menu as invisible once we expand the PiP
- mListeners.forEach(l -> l.onPipMenuVisibilityChanged(false));
+ onMenuVisibilityChanged(false);
break;
}
case MESSAGE_MINIMIZE_PIP: {
@@ -86,14 +115,14 @@
case MESSAGE_DISMISS_PIP: {
mListeners.forEach(l -> l.onPipDismiss());
// Preemptively mark the menu as invisible once we dismiss the PiP
- mListeners.forEach(l -> l.onPipMenuVisibilityChanged(false));
+ onMenuVisibilityChanged(false);
break;
}
case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
mToActivityMessenger = msg.replyTo;
// Mark the menu as invisible once the activity finishes as well
if (mToActivityMessenger == null) {
- mListeners.forEach(l -> l.onPipMenuVisibilityChanged(false));
+ onMenuVisibilityChanged(false);
}
break;
}
@@ -101,11 +130,20 @@
}
});
+ private ActionListener mMediaActionListener = new ActionListener() {
+ @Override
+ public void onMediaActionsChanged(List<RemoteAction> mediaActions) {
+ mMediaActions = new ParceledListSlice<>(mediaActions);
+ updateMenuActions();
+ }
+ };
+
public PipMenuActivityController(Context context, IActivityManager activityManager,
- IWindowManager windowManager) {
+ IWindowManager windowManager, PipMediaController mediaController) {
mContext = context;
mActivityManager = activityManager;
mWindowManager = windowManager;
+ mMediaController = mediaController;
}
/**
@@ -137,7 +175,7 @@
pinnedStackInfo.taskIds.length > 0) {
Intent intent = new Intent(mContext, PipMenuActivity.class);
intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
- intent.putExtra(EXTRA_ACTIONS, mActions);
+ intent.putExtra(EXTRA_ACTIONS, resolveMenuActions());
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
options.setLaunchTaskId(
pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
@@ -168,15 +206,31 @@
}
/**
- * Sets the {@param actions} associated with the PiP.
+ * Sets the menu actions to the actions provided by the current PiP activity.
*/
- public void setActions(ParceledListSlice actions) {
- mActions = actions;
+ public void setAppActions(ParceledListSlice appActions) {
+ mAppActions = appActions;
+ updateMenuActions();
+ }
+ /**
+ * @return the best set of actions to show in the PiP menu.
+ */
+ private ParceledListSlice resolveMenuActions() {
+ if (isValidActions(mAppActions)) {
+ return mAppActions;
+ }
+ return mMediaActions;
+ }
+
+ /**
+ * Updates the PiP menu activity with the best set of actions provided.
+ */
+ private void updateMenuActions() {
if (mToActivityMessenger != null) {
Message m = Message.obtain();
m.what = PipMenuActivity.MESSAGE_UPDATE_ACTIONS;
- m.obj = actions;
+ m.obj = resolveMenuActions();
try {
mToActivityMessenger.send(m);
} catch (RemoteException e) {
@@ -184,4 +238,30 @@
}
}
}
+
+ /**
+ * Returns whether the set of actions are valid.
+ */
+ private boolean isValidActions(ParceledListSlice actions) {
+ return actions != null && actions.getList().size() > 0;
+ }
+
+ /**
+ * Handles changes in menu visibility.
+ */
+ private void onMenuVisibilityChanged(boolean visible) {
+ mListeners.forEach(l -> l.onPipMenuVisibilityChanged(visible));
+ if (visible != mVisible) {
+ if (visible) {
+ // Once visible, start listening for media action changes. This call will trigger
+ // the menu actions to be updated again.
+ mMediaController.addListener(mMediaActionListener);
+ } else {
+ // Once hidden, stop listening for media action changes. This call will trigger
+ // the menu actions to be updated again.
+ mMediaController.removeListener(mMediaActionListener);
+ }
+ }
+ mVisible = visible;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 18ae3cf..5727684 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -67,7 +67,7 @@
private static final int MINIMIZE_STACK_MAX_DURATION = 200;
// The fraction of the stack width that the user has to drag offscreen to minimize the PIP
- private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.15f;
+ private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.2f;
private final Context mContext;
private final IActivityManager mActivityManager;
@@ -87,6 +87,7 @@
// Allow the PIP to be "docked" slightly offscreen
private boolean mEnableMinimizing = true;
+ private final Rect mStableInsets = new Rect();
private final Rect mPinnedStackBounds = new Rect();
private final Rect mBoundedPinnedStackBounds = new Rect();
private ValueAnimator mPinnedStackBoundsAnimator = null;
@@ -182,7 +183,7 @@
mTouchState = new PipTouchState(mViewConfig);
mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
mGestures = new PipTouchGesture[] {
- mDragToDismissGesture, mTapThroughGesture, mMinimizeGesture, mDefaultMovementGesture
+ mDragToDismissGesture, mDefaultMovementGesture
};
mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
registerInputConsumer();
@@ -230,6 +231,7 @@
public void onMinimizedStateChanged(boolean isMinimized) {
mIsMinimized = isMinimized;
+ mSnapAlgorithm.setMinimized(isMinimized);
}
public void onSnapToEdgeStateChanged(boolean isSnapToEdge) {
@@ -297,14 +299,13 @@
}
/**
- * @return whether the current touch state is a horizontal drag offscreen.
+ * @return whether the current touch state places the pip partially offscreen.
*/
private boolean isDraggingOffscreen(PipTouchState touchState) {
PointF lastDelta = touchState.getLastTouchDelta();
PointF downDelta = touchState.getDownTouchDelta();
float left = mPinnedStackBounds.left + lastDelta.x;
- return !(mBoundedPinnedStackBounds.left <= left && left <= mBoundedPinnedStackBounds.right)
- && Math.abs(downDelta.x) > Math.abs(downDelta.y);
+ return !(mBoundedPinnedStackBounds.left <= left && left <= mBoundedPinnedStackBounds.right);
}
/**
@@ -421,13 +422,14 @@
mContext.getDisplay().getRealSize(displaySize);
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds,
mPinnedStackBounds);
- mSnapAlgorithm.applyMinimizedOffset(toBounds, mBoundedPinnedStackBounds, displaySize);
+ mSnapAlgorithm.applyMinimizedOffset(toBounds, mBoundedPinnedStackBounds, displaySize,
+ mStableInsets);
mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
toBounds, MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN,
mUpdatePinnedStackBoundsListener);
mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationEnd(Animator animation) {
+ public void onAnimationStart(Animator animation) {
mMenuController.hideMenu();
}
});
@@ -485,7 +487,7 @@
/**
* Resizes the pinned stack back to fullscreen.
*/
- private void expandPinnedStackToFullscreen() {
+ void expandPinnedStackToFullscreen() {
BackgroundThread.getHandler().post(() -> {
try {
mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
@@ -528,6 +530,7 @@
if (updatePinnedStackBounds) {
mPinnedStackBounds.set(info.bounds);
}
+ mWindowManager.getStableInsets(info.displayId, mStableInsets);
mBoundedPinnedStackBounds.set(mWindowManager.getPictureInPictureMovementBounds(
info.displayId));
}
@@ -587,115 +590,22 @@
/**** Gestures ****/
/**
- * Gesture controlling dragging the PIP slightly offscreen to minimize it.
- */
- private PipTouchGesture mMinimizeGesture = new PipTouchGesture() {
- @Override
- boolean onMove(PipTouchState touchState) {
- if (mEnableMinimizing) {
- boolean isDraggingOffscreen = isDraggingOffscreen(touchState);
- if (touchState.startedDragging() && isDraggingOffscreen) {
- // Reset the minimized state once we drag horizontally
- setMinimizedState(false);
- }
-
- if (touchState.allowDraggingOffscreen() && isDraggingOffscreen) {
- // Move the pinned stack, but ignore the vertical movement
- float left = mPinnedStackBounds.left + touchState.getLastTouchDelta().x;
- mTmpBounds.set(mPinnedStackBounds);
- mTmpBounds.offsetTo((int) left, mPinnedStackBounds.top);
- if (!mTmpBounds.equals(mPinnedStackBounds)) {
- mPinnedStackBounds.set(mTmpBounds);
- mMotionHelper.resizeToBounds(mPinnedStackBounds);
- }
- return true;
- } else if (mIsMinimized && touchState.isDragging()) {
- // Move the pinned stack, but ignore the horizontal movement
- PointF lastDelta = touchState.getLastTouchDelta();
- float top = mPinnedStackBounds.top + lastDelta.y;
- top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
- mBoundedPinnedStackBounds.bottom, top));
- mTmpBounds.set(mPinnedStackBounds);
- mTmpBounds.offsetTo(mPinnedStackBounds.left, (int) top);
- movePinnedStack(mTmpBounds);
- return true;
- }
- }
- return false;
- }
-
- @Override
- public boolean onUp(PipTouchState touchState) {
- if (mEnableMinimizing) {
- if (touchState.isDragging()) {
- if (isDraggingOffscreen(touchState)) {
- if (shouldMinimizedPinnedStack()) {
- setMinimizedState(true);
- animateToClosestMinimizedTarget();
- return true;
- }
- } else if (mIsMinimized) {
- PointF vel = touchState.getVelocity();
- if (vel.length() > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- flingToMinimizedSnapTarget(vel.y);
- } else {
- animateToClosestMinimizedTarget();
- }
- return true;
- }
- } else if (mIsMinimized) {
- setMinimizedState(false);
- animateToClosestSnapTarget();
- return true;
- }
- }
- return false;
- }
- };
-
- /**
- * Gesture controlling tapping on the PIP to show an overlay.
- */
- private PipTouchGesture mTapThroughGesture = new PipTouchGesture() {
- @Override
- boolean onMove(PipTouchState touchState) {
- return false;
- }
-
- @Override
- public boolean onUp(PipTouchState touchState) {
- if (!touchState.isDragging() && !mIsMinimized && !mIsTappingThrough) {
- mMenuController.showMenu();
- mIsTappingThrough = true;
- return true;
- }
- return false;
- }
- };
-
- /**
* Gesture controlling normal movement of the PIP.
*/
private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
@Override
boolean onMove(PipTouchState touchState) {
- if (touchState.startedDragging()) {
- // For now, once the user has started a drag that the other gestures have not
- // intercepted, disallow those gestures from intercepting again to drag offscreen
- touchState.setDisallowDraggingOffscreen();
- }
-
if (touchState.isDragging()) {
// Move the pinned stack freely
PointF lastDelta = touchState.getLastTouchDelta();
float left = mPinnedStackBounds.left + lastDelta.x;
float top = mPinnedStackBounds.top + lastDelta.y;
- if (!DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) {
+ if (!touchState.allowDraggingOffscreen()) {
left = Math.max(mBoundedPinnedStackBounds.left, Math.min(
mBoundedPinnedStackBounds.right, left));
- top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
- mBoundedPinnedStackBounds.bottom, top));
}
+ top = Math.max(mBoundedPinnedStackBounds.top, Math.min(
+ mBoundedPinnedStackBounds.bottom, top));
mTmpBounds.set(mPinnedStackBounds);
mTmpBounds.offsetTo((int) left, (int) top);
movePinnedStack(mTmpBounds);
@@ -708,16 +618,58 @@
public boolean onUp(PipTouchState touchState) {
if (touchState.isDragging()) {
PointF vel = mTouchState.getVelocity();
- float velocity = PointF.length(vel.x, vel.y);
+ if (!mIsMinimized && (shouldMinimizedPinnedStack()
+ || isHorizontalFlingTowardsCurrentEdge(vel))) {
+ // Pip should be minimized
+ setMinimizedState(true);
+ animateToClosestMinimizedTarget();
+ return true;
+ }
+ if (mIsMinimized) {
+ // If we're dragging and it wasn't a minimize gesture
+ // then we shouldn't be minimized.
+ setMinimizedState(false);
+ }
+
+ final float velocity = PointF.length(vel.x, vel.y);
if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
flingToSnapTarget(velocity, vel.x, vel.y);
} else {
animateToClosestSnapTarget();
}
+ } else if (mIsMinimized) {
+ // This was a tap, so no longer minimized
+ animateToClosestSnapTarget();
+ setMinimizedState(false);
+ } else if (!mIsTappingThrough) {
+ mMenuController.showMenu();
+ mIsTappingThrough = true;
} else {
expandPinnedStackToFullscreen();
}
return true;
}
};
+
+ /**
+ * @return whether the gesture ending in the {@param vel} is fast enough to be a fling towards
+ * the same edge the PIP is on. Used to identify a minimize gesture.
+ */
+ private boolean isHorizontalFlingTowardsCurrentEdge(PointF vel) {
+ final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
+ final boolean isFling = PointF.length(vel.x, vel.y) > mFlingAnimationUtils
+ .getMinVelocityPxPerSecond();
+ final boolean towardsCurrentEdge = onEdge(true /* left */) && vel.x < 0
+ || onEdge(false /* right */) && vel.x > 0;
+ return towardsCurrentEdge && isHorizontal && isFling;
+ }
+
+ private boolean onEdge(boolean checkLeft) {
+ if (checkLeft) {
+ return mPinnedStackBounds.left <= mBoundedPinnedStackBounds.left;
+ } else {
+ return mPinnedStackBounds.right >= mBoundedPinnedStackBounds.right
+ + mPinnedStackBounds.width();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
new file mode 100644
index 0000000..a8cdd1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
@@ -0,0 +1,56 @@
+/*
+ * 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.pip.phone;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+
+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;
+
+public class PipUtils {
+
+ private static final String TAG = "PipUtils";
+
+ /**
+ * @return the ComponentName of the top non-SystemUI activity in the pinned stack, or null if
+ * none exists.
+ */
+ public static ComponentName getTopPinnedActivity(Context context,
+ IActivityManager activityManager) {
+ try {
+ final String sysUiPackageName = context.getPackageName();
+ final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
+ if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
+ pinnedStackInfo.taskIds.length > 0) {
+ 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.");
+ }
+ return null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index a622656..964fefa 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -377,16 +377,18 @@
mCurrentPipBounds = mPipBounds;
break;
}
- try {
- int animationDurationMs = -1;
- if (wasRecentsShown
- && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) {
- animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
+ if (mCurrentPipBounds != null) {
+ try {
+ int animationDurationMs = -1;
+ if (wasRecentsShown
+ && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) {
+ animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
+ }
+ mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
+ true, true, true, animationDurationMs);
+ } catch (RemoteException e) {
+ Log.e(TAG, "resizeStack failed", e);
}
- mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
- true, true, true, animationDurationMs);
- } catch (RemoteException e) {
- Log.e(TAG, "resizeStack failed", e);
}
}
@@ -590,7 +592,7 @@
@Override
public void onTaskStackChanged() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
- if (!checkCurrentUserId()) {
+ if (!checkCurrentUserId(DEBUG)) {
return;
}
if (mState != STATE_NO_PIP) {
@@ -627,7 +629,7 @@
@Override
public void onActivityPinned() {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
- if (!checkCurrentUserId()) {
+ if (!checkCurrentUserId(DEBUG)) {
return;
}
StackInfo stackInfo = getPinnedStackInfo();
@@ -658,9 +660,9 @@
}
@Override
- public void onPinnedActivityRestartAttempt() {
+ public void onPinnedActivityRestartAttempt(ComponentName sourceComponent) {
if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
- if (!checkCurrentUserId()) {
+ if (!checkCurrentUserId(DEBUG)) {
return;
}
// If PIPed activity is launched again by Launcher or intent, make it fullscreen.
@@ -670,7 +672,7 @@
@Override
public void onPinnedStackAnimationEnded() {
if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
- if (!checkCurrentUserId()) {
+ if (!checkCurrentUserId(DEBUG)) {
return;
}
switch (mState) {
@@ -693,26 +695,6 @@
break;
}
}
-
- // {@link android.app.ITaskStackListener} isn't multi-user aware.
- // Check the current uid and current SystemUI's running uid
- // so we can handle the PIP status change only once.
- private boolean checkCurrentUserId() {
- try {
- int processUserId = UserHandle.myUserId();
- int currentUserId = mActivityManager.getCurrentUser().id;
- if (processUserId != currentUserId) {
- if (DEBUG) {
- Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId
- + " and the current user is uid=" + currentUserId);
- }
- return false;
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to get current user.");
- }
- return true;
- }
};
/**
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 71dda2d..82ec69d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -41,8 +41,9 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.util.NotificationChannels;
import java.io.PrintWriter;
import java.text.NumberFormat;
@@ -97,7 +98,7 @@
private SystemUIDialog mHighTempDialog;
public PowerNotificationWarnings(Context context, NotificationManager notificationManager,
- PhoneStatusBar phoneStatusBar) {
+ StatusBar statusBar) {
mContext = context;
mNoMan = notificationManager;
mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -151,8 +152,7 @@
.setOngoing(true)
.setContentTitle(mContext.getString(R.string.invalid_charger_title))
.setContentText(mContext.getString(R.string.invalid_charger_text))
- .setPriority(Notification.PRIORITY_MAX)
- .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setChannel(NotificationChannels.ALERTS)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
SystemUI.overrideNotificationAppName(mContext, nb);
@@ -173,7 +173,7 @@
.setContentText(mContext.getString(textRes, percentage))
.setOnlyAlertOnce(true)
.setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
- .setPriority(Notification.PRIORITY_MAX)
+ .setChannel(NotificationChannels.ALERTS)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setColor(mContext.getColor(
com.android.internal.R.color.battery_saver_mode_color));
@@ -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
@@ -233,7 +241,7 @@
.setShowWhen(false)
.setContentTitle(mContext.getString(R.string.high_temp_title))
.setContentText(mContext.getString(R.string.high_temp_notif_message))
- .setPriority(Notification.PRIORITY_HIGH)
+ .setChannel(NotificationChannels.ALERTS)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(pendingBroadcast(ACTION_CLICKED_TEMP_WARNING))
.setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_TEMP_WARNING))
@@ -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..3d36868 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;
@@ -35,7 +36,7 @@
import android.util.Slog;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -71,7 +72,7 @@
mWarnings = new PowerNotificationWarnings(
mContext,
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE),
- getComponent(PhoneStatusBar.class));
+ getComponent(StatusBar.class));
ContentObserver obs = new ContentObserver(mHandler) {
@Override
@@ -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.
@@ -245,8 +250,8 @@
}
private void updateTemperatureWarning() {
- PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
- if (phoneStatusBar != null && phoneStatusBar.isDeviceInVrMode()) {
+ StatusBar statusBar = getComponent(StatusBar.class);
+ if (statusBar != null && statusBar.isDeviceInVrMode()) {
// ensure the warning isn't showing, since VR shows its own warning
mWarnings.dismissTemperatureWarning();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 26da551..b22ea4c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -1,9 +1,12 @@
package com.android.systemui.qs;
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@@ -49,9 +52,14 @@
while (numPages < getChildCount()) {
removeViewAt(getChildCount() - 1);
}
+ TypedArray array = getContext().obtainStyledAttributes(
+ new int[]{android.R.attr.colorForeground});
+ int color = array.getColor(0, 0);
+ array.recycle();
while (numPages > getChildCount()) {
ImageView v = new ImageView(mContext);
v.setImageResource(R.drawable.minor_a_b);
+ v.setImageTintList(ColorStateList.valueOf(color));
addView(v, new LayoutParams(mPageIndicatorWidth, mPageIndicatorHeight));
}
// Refresh state.
@@ -196,7 +204,7 @@
for (int i = 0; i < N; i++) {
getChildAt(i).measure(widthChildSpec, heightChildSpec);
}
- int width = (mPageIndicatorWidth - mPageDotWidth) * N + mPageDotWidth;
+ int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth;
setMeasuredDimension(width, mPageIndicatorHeight);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 1c242e9..a231e79 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -29,7 +29,6 @@
private PageIndicator mPageIndicator;
private int mNumPages;
- private View mDecorGroup;
private PageListener mPageListener;
private int mPosition;
@@ -145,14 +144,14 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator);
- mDecorGroup = findViewById(R.id.page_decor);
- ((LayoutParams) mDecorGroup.getLayoutParams()).isDecor = true;
-
- mPages.add((TilePage) LayoutInflater.from(mContext)
+ mPages.add((TilePage) LayoutInflater.from(getContext())
.inflate(R.layout.qs_paged_page, this, false));
}
+ public void setPageIndicator(PageIndicator indicator) {
+ mPageIndicator = indicator;
+ }
+
@Override
public int getOffsetTop(TileRecord tile) {
final ViewGroup parent = (ViewGroup) tile.tileView.getParent();
@@ -196,7 +195,7 @@
if (++index == mPages.size()) {
if (DEBUG) Log.d(TAG, "Adding page for "
+ tile.tile.getClass().getSimpleName());
- mPages.add((TilePage) LayoutInflater.from(mContext)
+ mPages.add((TilePage) LayoutInflater.from(getContext())
.inflate(R.layout.qs_paged_page, this, false));
}
}
@@ -211,7 +210,7 @@
}
if (DEBUG) Log.d(TAG, "Size: " + mNumPages);
mPageIndicator.setNumPages(mNumPages);
- mDecorGroup.setVisibility(mNumPages > 1 ? View.VISIBLE : View.GONE);
+ mPageIndicator.setVisibility(mNumPages > 1 ? View.VISIBLE : View.GONE);
setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
setCurrentItem(0, false);
@@ -243,8 +242,7 @@
maxHeight = height;
}
}
- setMeasuredDimension(getMeasuredWidth(), maxHeight
- + (mDecorGroup.getVisibility() != View.GONE ? mDecorGroup.getMeasuredHeight() : 0));
+ setMeasuredDimension(getMeasuredWidth(), maxHeight);
}
private final Runnable mDistribute = new Runnable() {
@@ -265,7 +263,7 @@
public TilePage(Context context, AttributeSet attrs) {
super(context, attrs);
updateResources();
- setContentDescription(mContext.getString(R.string.accessibility_desc_quick_settings));
+ setContentDescription(getContext().getString(R.string.accessibility_desc_quick_settings));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index fdefcf9..602f9bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -193,8 +193,9 @@
translationXBuilder.addFloat(label, "translationX", -xDiff, 0);
translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
- mTopFiveQs.add(tileIcon);
- mAllViews.add(tileIcon);
+ mTopFiveQs.add(tileView.getIcon());
+ mTopFiveQs.add(tileView.getBgCicle());
+ mAllViews.add(tileView.getIcon());
mAllViews.add(quickTileView);
} else if (mFullRows && isIconInAnimatedRow(count)) {
// TODO: Refactor some of this, it shares a lot with the above block.
@@ -223,9 +224,10 @@
// Make brightness appear static position and alpha in through second half.
View brightness = mQsPanel.getBrightnessView();
if (brightness != null) {
- firstPageBuilder.addFloat(brightness, "translationY", mQsPanel.getHeight(), 0);
+// firstPageBuilder.addFloat(brightness, "translationY", mQsPanel.getHeight(), 0);
mBrightnessAnimator = new TouchAnimator.Builder()
.addFloat(brightness, "alpha", 0, 1)
+ .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
.setStartDelay(.5f)
.build();
mAllViews.add(brightness);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index f4da5ec..91b4d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -18,18 +18,12 @@
import android.content.Context;
import android.graphics.Point;
-import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QS.HeightListener;
import com.android.systemui.qs.customize.QSCustomizer;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.phone.QuickStatusBarHeader;
/**
* Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
@@ -95,7 +89,7 @@
updateBottom();
}
- void updateBottom() {
+ public void updateBottom() {
int height = calculateContainerHeight();
setBottom(getTop() + height);
mQSDetail.setBottom(getTop() + height);
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..1835afd 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;
+ protected H mHandler;
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 H(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/QSIconView.java b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
index 9c4a149..f28a0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
@@ -16,14 +16,20 @@
package com.android.systemui.qs;
+import static com.android.systemui.qs.QSTile.getColorForState;
+
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
+
import com.android.systemui.R;
import java.util.Objects;
@@ -34,6 +40,8 @@
protected final int mIconSizePx;
protected final int mTilePaddingBelowIconPx;
private boolean mAnimationEnabled = true;
+ private int mState = -1;
+ private int mTint;
public QSIconView(Context context) {
super(context);
@@ -65,7 +73,6 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int w = getMeasuredWidth();
- final int h = getMeasuredHeight();
int top = 0;
final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
layout(mIcon, iconLeft, top);
@@ -100,8 +107,43 @@
} else {
iv.clearColorFilter();
}
+ if (state.state != mState) {
+ int color = getColorForState(getContext(), state.state);
+ mState = state.state;
+ if (iv.isShown()) {
+ animateGrayScale(mTint, color, iv);
+ mTint = color;
+ } else {
+ setTint(iv, color);
+ mTint = color;
+ }
+ }
}
+ public static void animateGrayScale(int fromColor, int toColor, ImageView iv) {
+ final float fromAlpha = Color.alpha(fromColor);
+ final float toAlpha = Color.alpha(toColor);
+ final float fromChannel = Color.red(fromColor);
+ final float toChannel = Color.red(toColor);
+
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ anim.setDuration(350);
+
+ anim.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction);
+ int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction);
+
+ setTint(iv, Color.argb(alpha, channel, channel, channel));
+ });
+ anim.start();
+ }
+
+ public static void setTint(ImageView iv, int color) {
+ iv.setImageTintList(ColorStateList.valueOf(color));
+ }
+
+
protected int getIconMeasureMode() {
return MeasureSpec.EXACTLY;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 9feaa0a..e004828 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -54,6 +54,7 @@
protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
protected final View mBrightnessView;
private final H mHandler = new H();
+ private final View mPageIndicator;
private int mPanelPaddingBottom;
private int mBrightnessPaddingTop;
@@ -84,21 +85,31 @@
setOrientation(VERTICAL);
- mBrightnessView = LayoutInflater.from(context).inflate(
- R.layout.quick_settings_brightness_dialog, this, false);
- addView(mBrightnessView);
-
setupTileLayout();
mFooter = new QSFooter(this, context);
addView(mFooter.getView());
+ mPageIndicator = LayoutInflater.from(context).inflate(
+ R.layout.qs_page_indicator, this, false);
+ addView(mPageIndicator);
+ if (mTileLayout instanceof PagedTileLayout) {
+ ((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator);
+ }
+
updateResources();
+ mBrightnessView = LayoutInflater.from(context).inflate(
+ R.layout.quick_settings_brightness_dialog, this, false);
+ addView(mBrightnessView);
+
mBrightnessController = new BrightnessController(getContext(),
(ImageView) findViewById(R.id.brightness_icon),
(ToggleSliderView) findViewById(R.id.brightness_slider));
+ }
+ public View getPageIndicator() {
+ return mPageIndicator;
}
protected void setupTileLayout() {
@@ -179,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() {
@@ -359,20 +369,10 @@
};
r.tile.addCallback(callback);
r.callback = callback;
- final View.OnClickListener click = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onTileClick(r.tile);
- }
- };
- final View.OnLongClickListener longClick = new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- r.tile.longClick();
- return true;
- }
- };
- r.tileView.init(click, longClick);
+ r.tileView.init(v -> r.tile.click(), v -> r.tile.secondaryClick(), v -> {
+ r.tile.longClick();
+ return true;
+ });
r.tile.refreshState();
mRecords.add(r);
@@ -402,10 +402,6 @@
});
}
- protected void onTileClick(QSTile<?> tile) {
- tile.click();
- }
-
public void closeDetail() {
if (mCustomizePanel != null && mCustomizePanel.isShown()) {
// Treat this as a detail panel for now, to make things easy.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index f73241c..e18654e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -17,13 +17,13 @@
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;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.service.quicksettings.Tile;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -31,29 +31,22 @@
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;
import java.util.Objects;
+import com.android.settingslib.Utils;
+
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.systemui.R;
+
/**
* Base quick-settings tile, extend this to create a new tile.
*
@@ -79,7 +72,9 @@
private String mTileSpec;
public abstract TState newTileState();
+
abstract protected void handleClick();
+
abstract protected void handleUpdateState(TState state, Object arg);
/**
@@ -93,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));
}
/**
@@ -134,7 +129,9 @@
return null; // optional
}
- protected DetailAdapter createDetailAdapter() { throw new UnsupportedOperationException(); }
+ protected DetailAdapter createDetailAdapter() {
+ throw new UnsupportedOperationException();
+ }
/**
* Is a startup check whether this device currently supports this tile.
@@ -233,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();
@@ -319,6 +317,21 @@
public abstract CharSequence getTileLabel();
+ public static int getColorForState(Context context, int state) {
+ switch (state) {
+ case Tile.STATE_UNAVAILABLE:
+ return Utils.getDisabled(context,
+ Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+ case Tile.STATE_INACTIVE:
+ return Utils.getColorAttr(context, android.R.attr.textColorSecondary);
+ case Tile.STATE_ACTIVE:
+ return Utils.getColorAttr(context, android.R.attr.colorPrimary);
+ default:
+ Log.e("QSTile", "Invalid state " + state);
+ return 0;
+ }
+ }
+
protected final class H extends Handler {
private static final int ADD_CALLBACK = 1;
private static final int CLICK = 2;
@@ -357,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();
@@ -412,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();
}
}
@@ -546,6 +541,7 @@
public static class State {
public Icon icon;
+ public int state = Tile.STATE_ACTIVE;
public CharSequence label;
public CharSequence contentDescription;
public CharSequence dualLabelContentDescription;
@@ -572,6 +568,7 @@
|| !Objects.equals(other.expandedAccessibilityClassName,
expandedAccessibilityClassName)
|| !Objects.equals(other.disabledByPolicy, disabledByPolicy)
+ || !Objects.equals(other.state, state)
|| !Objects.equals(other.enforcedAdmin, enforcedAdmin);
other.icon = icon;
other.label = label;
@@ -582,6 +579,7 @@
other.expandedAccessibilityClassName = expandedAccessibilityClassName;
other.autoMirrorDrawable = autoMirrorDrawable;
other.disabledByPolicy = disabledByPolicy;
+ other.state = state;
if (enforcedAdmin == null) {
other.enforcedAdmin = null;
} else if (other.enforcedAdmin == null) {
@@ -609,6 +607,7 @@
sb.append(",autoMirrorDrawable=").append(autoMirrorDrawable);
sb.append(",disabledByPolicy=").append(disabledByPolicy);
sb.append(",enforcedAdmin=").append(enforcedAdmin);
+ sb.append(",state=").append(state);
return sb.append(']');
}
}
@@ -648,7 +647,6 @@
public boolean connected;
public boolean activityIn;
public boolean activityOut;
- public int overlayIconId;
public boolean filter;
public boolean isOverlayIconWide;
@@ -657,12 +655,10 @@
final SignalState o = (SignalState) other;
final boolean changed = o.connected != connected || o.activityIn != activityIn
|| o.activityOut != activityOut
- || o.overlayIconId != overlayIconId
|| o.isOverlayIconWide != isOverlayIconWide;
o.connected = connected;
o.activityIn = activityIn;
o.activityOut = activityOut;
- o.overlayIconId = overlayIconId;
o.filter = filter;
o.isOverlayIconWide = isOverlayIconWide;
return super.copyTo(other) || changed;
@@ -674,7 +670,6 @@
rt.insert(rt.length() - 1, ",connected=" + connected);
rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
- rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
rt.insert(rt.length() - 1, ",filter=" + filter);
rt.insert(rt.length() - 1, ",wideOverlayIcon=" + isOverlayIconWide);
return rt;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
index 2b7fcc5..a177cc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
@@ -15,31 +15,49 @@
*/
package com.android.systemui.qs;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.service.quicksettings.Tile;
import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.Switch;
+import com.android.settingslib.Utils;
+
import com.android.systemui.R;
public class QSTileBaseView extends LinearLayout {
+ private static final String TAG = "QSTileBaseView";
private final H mHandler = new H();
+ private final ImageView mBg;
protected QSIconView mIcon;
protected RippleDrawable mRipple;
private Drawable mTileBackground;
private String mAccessibilityClass;
private boolean mTileState;
private boolean mCollapsedView;
+ private final int mColorActive;
+ private final int mColorInactive;
+ private final int mColorDisabled;
+ private int mCircleColor;
public QSTileBaseView(Context context, QSIconView icon) {
this(context, icon, false);
@@ -47,8 +65,26 @@
public QSTileBaseView(Context context, QSIconView icon, boolean collapsedView) {
super(context);
+ // Default to Quick Tile padding, and QSTileView will specify its own padding.
+ int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
+
+ FrameLayout frame = new FrameLayout(context);
+ frame.setForegroundGravity(Gravity.CENTER);
+ int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
+ addView(frame, new LayoutParams(size, size));
+ mBg = new ImageView(getContext());
+ mBg.setScaleType(ScaleType.FIT_CENTER);
+ mBg.setImageResource(R.drawable.ic_qs_circle);
+ frame.addView(mBg);
mIcon = icon;
- addView(mIcon);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ params.setMargins(0, padding, 0, padding);
+ frame.addView(mIcon, params);
+ mColorActive = Utils.getColorAttr(context, android.R.attr.textColorPrimary);
+ mColorDisabled = Utils.getDisabled(context,
+ Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+ mColorInactive = Utils.getColorAttr(context, android.R.attr.textColorSecondary);
mTileBackground = newTileBackground();
if (mTileBackground instanceof RippleDrawable) {
@@ -57,18 +93,20 @@
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
setBackground(mTileBackground);
- // Default to Quick Tile padding, and QSTileView will specify its own padding.
- int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
- setPadding(0, padding, 0, padding);
+ setPadding(0, 0, 0, 0);
setClipChildren(false);
setClipToPadding(false);
mCollapsedView = collapsedView;
setFocusable(true);
}
+ public View getBgCicle() {
+ return mBg;
+ }
+
protected Drawable newTileBackground() {
- final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless };
- final TypedArray ta = mContext.obtainStyledAttributes(attrs);
+ final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless};
+ final TypedArray ta = getContext().obtainStyledAttributes(attrs);
final Drawable d = ta.getDrawable(0);
ta.recycle();
return d;
@@ -85,11 +123,12 @@
// center the touch feedback on the center of the icon, and dial it down a bit
final int cx = width / 2;
final int cy = height / 2;
- final int rad = (int)(mIcon.getHeight() * .85f);
+ final int rad = (int) (mIcon.getHeight() * .85f);
mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
}
- public void init(OnClickListener click, OnLongClickListener longClick) {
+ public void init(OnClickListener click, OnClickListener secondaryClick,
+ OnLongClickListener longClick) {
setClickable(true);
setOnClickListener(click);
setOnLongClickListener(longClick);
@@ -128,6 +167,16 @@
}
protected void handleStateChanged(QSTile.State state) {
+ int circleColor = getCircleColor(state.state);
+ if (circleColor != mCircleColor) {
+ if (mBg.isShown()) {
+ QSIconView.animateGrayScale(mCircleColor, circleColor, mBg);
+ } else {
+ QSIconView.setTint(mBg, circleColor);
+ }
+ mCircleColor = circleColor;
+ }
+
mIcon.setIcon(state);
if (mCollapsedView && !TextUtils.isEmpty(state.minimalContentDescription)) {
setContentDescription(state.minimalContentDescription);
@@ -144,6 +193,19 @@
}
}
+ private int getCircleColor(int state) {
+ switch (state) {
+ case Tile.STATE_ACTIVE:
+ return mColorActive;
+ case Tile.STATE_INACTIVE:
+ case Tile.STATE_UNAVAILABLE:
+ return mColorDisabled;
+ default:
+ Log.e(TAG, "Invalid state " + state);
+ return 0;
+ }
+ }
+
public QSIconView getIcon() {
return mIcon;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index e59873a..7126f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -16,27 +16,34 @@
package com.android.systemui.qs;
+import static com.android.systemui.qs.QSTile.getColorForState;
+
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.MathUtils;
+import android.service.quicksettings.Tile;
+import android.text.SpannableStringBuilder;
+import android.text.style.ForegroundColorSpan;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+
import libcore.util.Objects;
/** View that represents a standard quick settings tile. **/
public class QSTileView extends QSTileBaseView {
- private final int mTileSpacingPx;
- private int mTilePaddingTopPx;
protected TextView mLabel;
private ImageView mPadLock;
+ private int mState;
+ private OnClickListener mClick;
+ private OnClickListener mSecondaryClick;
public QSTileView(Context context, QSIconView icon) {
this(context, icon, false);
@@ -45,13 +52,10 @@
public QSTileView(Context context, QSIconView icon, boolean collapsedView) {
super(context, icon, collapsedView);
- final Resources res = context.getResources();
- mTileSpacingPx = res.getDimensionPixelSize(R.dimen.qs_tile_spacing);
-
setClipChildren(false);
+ setClipToPadding(false);
setClickable(true);
- updateTopPadding();
setId(View.generateViewId());
createLabel();
setOrientation(VERTICAL);
@@ -62,27 +66,17 @@
return mLabel;
}
- protected void updateTopPadding() {
- Resources res = getResources();
- int padding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top);
- int largePadding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top_large_text);
- float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale,
- 1.0f, FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f);
- mTilePaddingTopPx = Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
- setPadding(mTileSpacingPx, mTilePaddingTopPx + mTileSpacingPx, mTileSpacingPx,
- mTileSpacingPx);
- requestLayout();
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- updateTopPadding();
FontSizeUtils.updateFontSize(mLabel, R.dimen.qs_tile_text_size);
}
protected void createLabel() {
- View view = LayoutInflater.from(mContext).inflate(R.layout.qs_tile_label, null);
+ ViewGroup view = (ViewGroup) LayoutInflater.from(getContext())
+ .inflate(R.layout.qs_tile_label, null);
+ view.setClipChildren(false);
+ view.setClipToPadding(false);
mLabel = (TextView) view.findViewById(R.id.tile_label);
mPadLock = (ImageView) view.findViewById(R.id.restricted_padlock);
addView(view);
@@ -91,10 +85,32 @@
@Override
protected void handleStateChanged(QSTile.State state) {
super.handleStateChanged(state);
- if (!Objects.equal(mLabel.getText(), state.label)) {
+ if (!Objects.equal(mLabel.getText(), state.label) || mState != state.state) {
+ if (state.state == Tile.STATE_UNAVAILABLE) {
+ int color = getColorForState(getContext(), state.state);
+ state.label = new SpannableStringBuilder().append(state.label,
+ new ForegroundColorSpan(color),
+ SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ mState = state.state;
mLabel.setText(state.label);
}
mLabel.setEnabled(!state.disabledByPolicy);
mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
}
+
+ @Override
+ public void init(OnClickListener click, OnClickListener secondaryClick, OnLongClickListener longClick) {
+ mClick = click;
+ mSecondaryClick = secondaryClick;
+ super.init(click, secondaryClick, longClick);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ setOnClickListener(event.getY() < (getMeasuredHeight() / 2) ? mClick : mSecondaryClick);
+ }
+ return super.onTouchEvent(event);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index de3e19c..16b351e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -56,7 +56,7 @@
}
mTileLayout = new HeaderTileLayout(context);
mTileLayout.setListening(mListening);
- addView((View) mTileLayout, 1 /* Between brightness and footer */);
+ addView((View) mTileLayout, 0 /* Between brightness and footer */);
}
@Override
@@ -113,11 +113,6 @@
}
@Override
- protected void onTileClick(QSTile<?> tile) {
- tile.secondaryClick();
- }
-
- @Override
public void onTuningChanged(String key, String newValue) {
// No tunings for you.
if (key.equals(QS_SHOW_BRIGHTNESS)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
index b2bfa06..8555e31 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
@@ -33,7 +33,6 @@
private FrameLayout mIconFrame;
private ImageView mSignal;
- private ImageView mOverlay;
private ImageView mIn;
private ImageView mOut;
@@ -62,8 +61,6 @@
mIconFrame = new FrameLayout(mContext);
mSignal = new ImageView(mContext);
mIconFrame.addView(mSignal);
- mOverlay = new ImageView(mContext);
- mIconFrame.addView(mOverlay, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
return mIconFrame;
}
@@ -109,17 +106,6 @@
public void setIcon(QSTile.State state) {
final SignalState s = (SignalState) state;
setIcon(mSignal, s);
- if (s.overlayIconId > 0) {
- mOverlay.setVisibility(VISIBLE);
- mOverlay.setImageResource(s.overlayIconId);
- } else {
- mOverlay.setVisibility(GONE);
- }
- if (s.overlayIconId > 0 && s.isOverlayIconWide) {
- mSignal.setPaddingRelative(mWideOverlayIconStartPadding, 0, 0, 0);
- } else {
- mSignal.setPaddingRelative(0, 0, 0, 0);
- }
Drawable drawable = mSignal.getDrawable();
if (state.autoMirrorDrawable && drawable != null) {
drawable.setAutoMirrored(true);
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 9e3889b..3afbc35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -15,15 +15,10 @@
*/
package com.android.systemui.qs.external;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-
import android.app.ActivityManager;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.Drawable;
@@ -39,14 +34,21 @@
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.IWindowManager;
+import android.view.WindowManager;
import android.view.WindowManagerGlobal;
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;
import libcore.util.Objects;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+
public class CustomTile extends QSTile<QSTile.State> implements TileChangeListener {
public static final String PREFIX = "custom(";
@@ -63,7 +65,6 @@
private final IQSTileService mService;
private final TileServiceManager mServiceManager;
private final int mUser;
- private Context mAppContext;
private android.graphics.drawable.Icon mDefaultIcon;
private boolean mListening;
@@ -81,10 +82,6 @@
mService = mServiceManager.getTileService();
mServiceManager.setTileChangeListener(this);
mUser = ActivityManager.getCurrentUser();
- try {
- mAppContext = mContext.createPackageContext(mComponent.getPackageName(), 0);
- } catch (NameNotFoundException e) {
- }
}
private void setTileIcon() {
@@ -287,27 +284,17 @@
if (mServiceManager.hasPendingBind()) {
tileState = Tile.STATE_UNAVAILABLE;
}
+ state.state = tileState;
Drawable drawable;
- boolean mHasRes = false;
- android.graphics.drawable.Icon icon = mTile.getIcon();
try {
- drawable = icon.loadDrawable(mAppContext);
- mHasRes = icon.getType() == android.graphics.drawable.Icon.TYPE_RESOURCE;
+ drawable = mTile.getIcon().loadDrawable(mContext);
} catch (Exception e) {
Log.w(TAG, "Invalid icon, forcing into unavailable state");
tileState = Tile.STATE_UNAVAILABLE;
- drawable = mDefaultIcon.loadDrawable(mAppContext);
+ drawable = mDefaultIcon.loadDrawable(mContext);
}
- final int color = TileColorPicker.getInstance(mContext).getColor(tileState);
- drawable.setTint(color);
- state.icon = mHasRes ? new DrawableIconWithRes(drawable, icon.getResId())
- : new DrawableIcon(drawable);
+ state.icon = new DrawableIcon(drawable);
state.label = mTile.getLabel();
- if (tileState == Tile.STATE_UNAVAILABLE) {
- state.label = new SpannableStringBuilder().append(state.label,
- new ForegroundColorSpan(color),
- SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
- }
if (mTile.getContentDescription() != null) {
state.contentDescription = mTile.getContentDescription();
} else {
@@ -321,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/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index e57cd58..e8f90ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -23,6 +23,7 @@
import android.net.ConnectivityManager;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.service.quicksettings.Tile;
import android.widget.Switch;
import com.android.internal.logging.MetricsLogger;
@@ -92,6 +93,7 @@
} else {
state.icon = mDisable;
}
+ state.state = airplaneMode ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.contentDescription = state.label;
state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
= Switch.class.getName();
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 833375e..06f4d9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -19,10 +19,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
import android.service.quicksettings.Tile;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -41,11 +37,10 @@
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.qs.external.TileColorPicker;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.policy.BatteryController;
import java.text.NumberFormat;
@@ -63,7 +58,7 @@
public BatteryTile(Host host) {
super(host);
- mBatteryController = host.getBatteryController();
+ mBatteryController = Dependency.get(BatteryController.class);
}
@Override
@@ -105,6 +100,11 @@
@Override
protected void handleClick() {
+ mBatteryController.setPowerSaveMode(!mPowerSave);
+ }
+
+ @Override
+ protected void handleSecondaryClick() {
showDetail(true);
}
@@ -118,26 +118,10 @@
int level = (arg != null) ? (Integer) arg : mLevel;
String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
- state.icon = new Icon() {
- @Override
- public Drawable getDrawable(Context context) {
- BatteryMeterDrawable drawable =
- new BatteryMeterDrawable(context,
- context.getColor(R.color.batterymeter_frame_color));
- drawable.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
- drawable.onPowerSaveChanged(mPowerSave);
- final int color = TileColorPicker.getInstance(context).getColor(Tile.STATE_ACTIVE);
- drawable.setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_IN));
- return drawable;
- }
-
- @Override
- public int getPadding() {
- return mHost.getContext().getResources().getDimensionPixelSize(
- R.dimen.qs_battery_padding);
- }
- };
- state.label = percentage;
+ state.state = mCharging ? Tile.STATE_UNAVAILABLE
+ : mPowerSave ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_battery_saver);
+ state.label = mContext.getString(R.string.battery_detail_switch_title);
state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_battery,
percentage) + "," +
(mPowerSave ? mContext.getString(R.string.battery_saver_notification_title)
@@ -153,7 +137,7 @@
mLevel = level;
mPluggedIn = pluggedIn;
mCharging = charging;
- refreshState((Integer) level);
+ refreshState(level);
if (mDetailShown) {
mBatteryDetail.postBindView();
}
@@ -213,11 +197,6 @@
mDrawable.onBatteryLevelChanged(100, false, false);
mDrawable.onPowerSaveChanged(true);
mDrawable.disableShowPercent();
-
- final int color = TileColorPicker.getInstance(mCurrentView.getContext())
- .getColor(Tile.STATE_ACTIVE);
- mDrawable.setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_IN));
-
((ImageView) mCurrentView.findViewById(android.R.id.icon)).setImageDrawable(mDrawable);
Checkable checkbox = (Checkable) mCurrentView.findViewById(android.R.id.toggle);
checkbox.setChecked(mPowerSave);
@@ -293,7 +272,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 f83b279..91e76ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
+import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -31,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;
@@ -47,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();
}
@@ -74,7 +79,7 @@
}
@Override
- protected void handleSecondaryClick() {
+ protected void handleClick() {
// Secondary clicks are header clicks, just toggle.
final boolean isEnabled = (Boolean)mState.value;
MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
@@ -87,9 +92,10 @@
}
@Override
- protected void handleClick() {
+ 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);
@@ -140,11 +146,13 @@
if (TextUtils.isEmpty(state.label)) {
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
}
+ state.state = Tile.STATE_ACTIVE;
} else {
state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_off);
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth_off);
+ state.state = Tile.STATE_INACTIVE;
}
CharSequence bluetoothName = state.label;
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 8afa91e..7415765 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
+import android.service.quicksettings.Tile;
import android.util.Log;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
@@ -27,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;
@@ -48,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
@@ -93,15 +98,17 @@
}
@Override
+ protected void handleSecondaryClick() {
+ handleClick();
+ }
+
+ @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;
}
@@ -135,6 +142,7 @@
if (!state.value && connecting) {
state.label = mContext.getString(R.string.quick_settings_connecting);
}
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.icon = ResourceIcon.get(state.value ? R.drawable.ic_qs_cast_on
: R.drawable.ic_qs_cast_off);
mDetailAdapter.updateItems(devices);
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 1406c9f..bdc95c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.service.quicksettings.Tile;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -28,14 +29,16 @@
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;
import com.android.systemui.qs.SignalTileView;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
/** Quick settings tile: Cellular **/
public class CellularTile extends QSTile<QSTile.SignalState> {
@@ -47,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();
}
@@ -86,11 +91,16 @@
@Override
protected void handleClick() {
+ mDataController.setMobileDataEnabled(!mDataController.isMobileDataEnabled());
+ }
+
+ @Override
+ protected void handleSecondaryClick() {
MetricsLogger.action(mContext, getMetricsCategory());
if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
- mHost.startActivityDismissingKeyguard(CELLULAR_SETTINGS);
+ mActivityStarter.postStartActivityDismissingKeyguard(CELLULAR_SETTINGS, 0);
}
}
@@ -111,10 +121,13 @@
: !cb.enabled || cb.airplaneModeEnabled ? R.drawable.ic_qs_signal_disabled
: cb.mobileSignalIconId > 0 ? cb.mobileSignalIconId
: R.drawable.ic_qs_signal_no_signal;
- state.icon = ResourceIcon.get(iconId);
+ if (cb.dataTypeIconId != 0) {
+ state.icon = ResourceIcon.get(cb.dataTypeIconId);
+ } else {
+ state.icon = ResourceIcon.get(iconId);
+ }
state.isOverlayIconWide = cb.isDataTypeIconWide;
state.autoMirrorDrawable = !cb.noSim;
- state.overlayIconId = cb.enabled && (cb.dataTypeIconId > 0) ? cb.dataTypeIconId : 0;
state.filter = iconId != R.drawable.ic_qs_no_sim;
state.activityIn = cb.enabled && cb.activityIn;
state.activityOut = cb.enabled && cb.activityOut;
@@ -148,6 +161,7 @@
= Button.class.getName();
state.value = mDataController.isMobileDataSupported()
&& mDataController.isMobileDataEnabled();
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
@Override
@@ -183,9 +197,10 @@
String enabledDesc;
boolean noSim;
boolean isDataTypeIconWide;
+ boolean roaming;
}
- private final class CellSignalCallback extends SignalCallbackAdapter {
+ private final class CellSignalCallback implements SignalCallback {
private final CallbackInfo mInfo = new CallbackInfo();
@Override
public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
@@ -197,7 +212,7 @@
@Override
public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId) {
+ String description, boolean isWide, int subId, boolean roaming) {
if (qsIcon == null) {
// Not data sim, don't display.
return;
@@ -211,6 +226,7 @@
mInfo.activityOut = activityOut;
mInfo.enabledDesc = description;
mInfo.isDataTypeIconWide = qsType != 0 && isWide;
+ mInfo.roaming = roaming;
refreshState(mInfo);
}
@@ -280,6 +296,8 @@
final DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo();
if (info == null) return v;
v.bind(info);
+ v.findViewById(R.id.roaming_text).setVisibility(mSignalCallback.mInfo.roaming
+ ? View.VISIBLE : View.INVISIBLE);
return v;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 77f063d..0a8afb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.provider.Settings;
import android.provider.Settings.Secure;
+import android.service.quicksettings.Tile;
import android.widget.Switch;
import com.android.internal.logging.MetricsLogger;
@@ -95,6 +96,7 @@
final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
final boolean enabled = value != 0;
state.value = enabled;
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.label = mContext.getString(R.string.quick_settings_inversion_label);
state.icon = enabled ? mEnable : mDisable;
state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
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 65432dc..412fe3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -16,15 +16,18 @@
import android.content.DialogInterface;
import android.content.Intent;
+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.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{
@@ -33,7 +36,7 @@
public DataSaverTile(Host host) {
super(host);
- mDataSaverController = host.getNetworkController().getDataSaverController();
+ mDataSaverController = Dependency.get(NetworkController.class).getDataSaverController();
}
@Override
@@ -96,6 +99,7 @@
protected void handleUpdateState(BooleanState state, Object arg) {
state.value = arg instanceof Boolean ? (Boolean) arg
: mDataSaverController.isDataSaverEnabled();
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.label = mContext.getString(R.string.data_saver);
state.contentDescription = state.label;
state.icon = ResourceIcon.get(state.value ? R.drawable.ic_data_saver
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 198375d..3c1f504 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -25,6 +25,7 @@
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.service.quicksettings.Tile;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
@@ -34,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;
@@ -73,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;
@@ -121,7 +124,17 @@
}
@Override
- public void handleClick() {
+ protected void handleClick() {
+ if (mState.value) {
+ mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
+ } else {
+ int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
+ mController.setZen(zen, null, TAG);
+ }
+ }
+
+ @Override
+ protected void handleSecondaryClick() {
if (mController.isVolumeRestricted()) {
// Collapse the panels, so the user can see the toast.
mHost.collapsePanels();
@@ -131,13 +144,9 @@
return;
}
MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
- if (mState.value) {
- mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
- } else {
- showDetail(true);
- int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
- mController.setZen(zen, null, TAG);
- }
+ showDetail(true);
+ int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
+ mController.setZen(zen, null, TAG);
}
@Override
@@ -151,6 +160,7 @@
final boolean newValue = zen != Global.ZEN_MODE_OFF;
final boolean valueChanged = state.value != newValue;
state.value = newValue;
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
switch (zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
@@ -305,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 416c7ce..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,9 +27,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.qs.QSTile;
-import com.android.systemui.qs.external.TileColorPicker;
import com.android.systemui.statusbar.policy.FlashlightController;
/** Quick settings tile: Control flashlight **/
@@ -46,7 +46,7 @@
public FlashlightTile(Host host) {
super(host);
- mFlashlightController = host.getFlashlightController();
+ mFlashlightController = Dependency.get(FlashlightController.class);
}
@Override
@@ -107,17 +107,12 @@
protected void handleUpdateState(BooleanState state, Object arg) {
state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
if (!mFlashlightController.isAvailable()) {
- Drawable icon = mHost.getContext().getDrawable(R.drawable.ic_signal_flashlight_disable)
+ Drawable icon = mHost.getContext().getDrawable(R.drawable.ic_signal_flashlight_enable)
.mutate();
- final int disabledColor = TileColorPicker.getInstance(mContext)
- .getColor(Tile.STATE_UNAVAILABLE);
- icon.setTint(disabledColor);
state.icon = new DrawableIcon(icon);
- state.label = new SpannableStringBuilder().append(state.label,
- new ForegroundColorSpan(disabledColor),
- SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_flashlight_unavailable);
+ state.state = Tile.STATE_UNAVAILABLE;
return;
}
if (arg instanceof Boolean) {
@@ -134,6 +129,7 @@
state.contentDescription = mContext.getString(R.string.quick_settings_flashlight_label);
state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
= Switch.class.getName();
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
@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 99485bb..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,10 +29,10 @@
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;
-import com.android.systemui.qs.external.TileColorPicker;
import com.android.systemui.statusbar.policy.HotspotController;
/** Quick settings tile: Hotspot **/
@@ -53,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) {
@@ -126,11 +126,6 @@
boolean wasAirplane = state.isAirplaneMode;
state.isAirplaneMode = mAirplaneMode.getValue() != 0;
if (state.isAirplaneMode) {
- final int disabledColor = TileColorPicker.getInstance(mContext)
- .getColor(Tile.STATE_UNAVAILABLE);
- state.label = new SpannableStringBuilder().append(state.label,
- new ForegroundColorSpan(disabledColor),
- SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
state.icon = mUnavailable;
} else if (wasAirplane) {
state.icon = mDisableNoAnimation;
@@ -138,6 +133,8 @@
state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
= Switch.class.getName();
state.contentDescription = state.label;
+ state.state = state.isAirplaneMode ? Tile.STATE_UNAVAILABLE
+ : state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
@Override
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 8a9a696..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,13 +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;
@@ -46,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
@@ -74,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);
}
@@ -115,6 +114,7 @@
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_location_off);
}
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
= Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 10fde30..b8ba28d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -19,6 +19,7 @@
import android.app.ActivityManager;
import android.content.Intent;
import android.provider.Settings;
+import android.service.quicksettings.Tile;
import android.widget.Switch;
import com.android.internal.app.NightDisplayController;
@@ -81,6 +82,7 @@
: R.drawable.ic_qs_night_display_off);
state.minimalAccessibilityClassName = state.expandedAccessibilityClassName =
Switch.class.getName();
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
@Override
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 cec5f15..2c0af17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -21,10 +21,12 @@
import android.content.res.Configuration;
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.qs.QSTile;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -50,7 +52,7 @@
public RotationLockTile(Host host) {
super(host);
- mController = host.getRotationLockController();
+ mController = Dependency.get(RotationLockController.class);
}
@Override
@@ -106,6 +108,7 @@
state.contentDescription = getAccessibilityString(rotationLocked);
state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
= Switch.class.getName();
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
public static boolean isCurrentOrientationLockPortrait(RotationLockController controller,
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 3876c88..2d61857 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -21,6 +21,7 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.provider.Settings;
+import android.service.quicksettings.Tile;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -30,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;
@@ -40,7 +43,7 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import java.util.List;
@@ -54,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
@@ -104,7 +109,7 @@
}
@Override
- protected void handleSecondaryClick() {
+ protected void handleClick() {
// Secondary clicks are header clicks, just toggle.
mState.copyTo(mStateBeforeClick);
MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
@@ -112,9 +117,10 @@
}
@Override
- protected void handleClick() {
+ 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);
@@ -191,6 +197,7 @@
state.dualLabelContentDescription = wifiName;
state.expandedAccessibilityClassName = Button.class.getName();
state.minimalAccessibilityClassName = Switch.class.getName();
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
@Override
@@ -249,7 +256,7 @@
}
}
- protected final class WifiSignalCallback extends SignalCallbackAdapter {
+ protected final class WifiSignalCallback implements SignalCallback {
final CallbackInfo mInfo = new CallbackInfo();
@Override
@@ -327,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 ce7fbd3..ae4d6c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -18,10 +18,12 @@
import android.content.Intent;
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.qs.QSTile;
import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -40,7 +42,7 @@
public WorkModeTile(Host host) {
super(host);
- mProfileController = host.getManagedProfileController();
+ mProfileController = Dependency.get(ManagedProfileController.class);
}
@Override
@@ -108,6 +110,7 @@
}
state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
= Switch.class.getName();
+ state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
@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/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index d5a6a58..a6fe0ea 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -76,6 +76,8 @@
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
@@ -86,7 +88,7 @@
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
-import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -281,7 +283,7 @@
dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
overrideAnimation));
Recents.getSystemServices().sendCloseSystemWindows(
- BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+ StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
EventBus.getDefault().send(dismissEvent);
}
@@ -599,13 +601,12 @@
}
return true;
}
- case KeyEvent.KEYCODE_DPAD_UP: {
- EventBus.getDefault().send(
- new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
- return true;
- }
- case KeyEvent.KEYCODE_DPAD_DOWN: {
- EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT: {
+ final Direction direction = NavigateTaskViewEvent.getDirectionFromKeyCode(keyCode);
+ EventBus.getDefault().send(new NavigateTaskViewEvent(direction));
return true;
}
case KeyEvent.KEYCODE_DEL:
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 711f0c6..ff8d4bf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -17,12 +17,33 @@
package com.android.systemui.recents;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.SystemProperties;
import com.android.systemui.R;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.TaskStack;
+
+/**
+ * Represents the dock regions for each orientation.
+ */
+class DockRegion {
+ public static TaskStack.DockState[] PHONE_LANDSCAPE = {
+ // We only allow docking to the left in landscape for now on small devices
+ TaskStack.DockState.LEFT
+ };
+ public static TaskStack.DockState[] PHONE_PORTRAIT = {
+ // We only allow docking to the top for now on small devices
+ TaskStack.DockState.TOP
+ };
+ public static TaskStack.DockState[] TABLET_LANDSCAPE = {
+ TaskStack.DockState.LEFT,
+ TaskStack.DockState.RIGHT
+ };
+ public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
+}
/**
* Application resources that can be retrieved from the application context and are not specifically
@@ -63,12 +84,14 @@
// Recents will layout task views in a grid mode when there's enough space in the screen.
public boolean isGridEnabled;
+ private final Context mAppContext;
+
public RecentsConfiguration(Context context) {
// Load only resources that can not change after the first load either through developer
// settings or via multi window
SystemServicesProxy ssp = Recents.getSystemServices();
- Context appContext = context.getApplicationContext();
- Resources res = appContext.getResources();
+ mAppContext = context.getApplicationContext();
+ Resources res = mAppContext.getResources();
fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows);
svelteLevel = res.getInteger(R.integer.recents_svelte_level);
isGridEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
@@ -86,4 +109,20 @@
public RecentsActivityLaunchState getLaunchState() {
return mLaunchState;
}
+
+ /**
+ * Returns the preferred dock states for the current orientation.
+ * @return a list of dock states for device and its orientation
+ */
+ public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
+ boolean isLandscape = mAppContext.getResources().getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE;
+ RecentsConfiguration config = Recents.getConfiguration();
+ if (config.isLargeScreen) {
+ return isLandscape ? DockRegion.TABLET_LANDSCAPE : DockRegion.TABLET_PORTRAIT;
+ } else {
+ return isLandscape ? DockRegion.PHONE_LANDSCAPE : DockRegion.PHONE_PORTRAIT;
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index cf6357b..9a8b267 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -18,7 +18,6 @@
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
import static android.app.ActivityManager.StackId.isHomeOrRecentsStack;
import static android.view.View.MeasureSpec;
@@ -77,9 +76,8 @@
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
@@ -229,7 +227,7 @@
*/
public void onStartScreenPinning(Context context, int taskId) {
SystemUIApplication app = (SystemUIApplication) context;
- PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+ StatusBar statusBar = app.getComponent(StatusBar.class);
if (statusBar != null) {
statusBar.showScreenPinningRequest(taskId, false);
}
@@ -351,7 +349,7 @@
growTarget);
// Only close the other system windows if we are actually showing recents
- ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
+ ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
mLastToggleTime = SystemClock.elapsedRealtime();
}
} catch (ActivityNotFoundException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index b368bd3..e57fa2d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -30,10 +30,17 @@
public final Task task;
public final TaskView taskView;
public final Point tlOffset;
+ public final boolean isUserTouchInitiated;
public DragStartEvent(Task task, TaskView taskView, Point tlOffset) {
+ this(task, taskView, tlOffset, true);
+ }
+
+ public DragStartEvent(Task task, TaskView taskView, Point tlOffset,
+ boolean isUserTouchInitiated) {
this.task = task;
this.taskView = taskView;
this.tlOffset = tlOffset;
+ this.isUserTouchInitiated = isUserTouchInitiated;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/NavigateTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/NavigateTaskViewEvent.java
new file mode 100644
index 0000000..5508d26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/NavigateTaskViewEvent.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 com.android.systemui.recents.events.ui.focus;
+
+import android.view.KeyEvent;
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Navigates the task view by arrow keys.
+ */
+public class NavigateTaskViewEvent extends EventBus.Event {
+ public enum Direction {
+ UNDEFINED, UP, DOWN, LEFT, RIGHT;
+ }
+
+ public Direction direction;
+ public NavigateTaskViewEvent(Direction direction) {
+ this.direction = direction;
+ }
+
+ public static Direction getDirectionFromKeyCode(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ return Direction.UP;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ return Direction.DOWN;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ return Direction.LEFT;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ return Direction.RIGHT;
+ default:
+ return Direction.UNDEFINED;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index a2b86d1..49074a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -79,6 +79,7 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.pip.tv.PipMenuActivity;
import com.android.systemui.pip.tv.PipOnboardingActivity;
@@ -152,11 +153,30 @@
public void onTaskStackChanged() { }
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
public void onActivityPinned() { }
- public void onPinnedActivityRestartAttempt() { }
+ public void onPinnedActivityRestartAttempt(ComponentName sourceComponent) { }
public void onPinnedStackAnimationEnded() { }
public void onActivityForcedResizable(String packageName, int taskId) { }
public void onActivityDismissingDockedStack() { }
public void onTaskProfileLocked(int taskId, int userId) { }
+
+ /**
+ * Checks that the current user matches the user's SystemUI process. Since
+ * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
+ * TaskStackListener should make this call to verify that we don't act on events from other
+ * user's processes.
+ */
+ protected final boolean checkCurrentUserId(boolean debug) {
+ int processUserId = UserHandle.myUserId();
+ int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+ if (processUserId != currentUserId) {
+ if (debug) {
+ Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId
+ + " and the current user is uid=" + currentUserId);
+ }
+ return false;
+ }
+ return true;
+ }
}
/**
@@ -178,9 +198,11 @@
}
@Override
- public void onPinnedActivityRestartAttempt() throws RemoteException{
+ public void onPinnedActivityRestartAttempt(ComponentName sourceComponent)
+ throws RemoteException{
mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
- mHandler.sendEmptyMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
+ mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, sourceComponent)
+ .sendToTarget();
}
@Override
@@ -1214,7 +1236,8 @@
}
case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onPinnedActivityRestartAttempt();
+ mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
+ (ComponentName) msg.obj);
}
break;
}
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/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 6ea51e5..ededf96 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -209,7 +209,8 @@
// When svelte, we trim the memory to just the visible thumbnails when
// leaving, so don't thrash the cache as the user scrolls (just load
// them from scratch each time)
- if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+ if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE
+ && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
mThumbnailCache.put(t.key, cachedThumbnailData);
}
}
@@ -553,7 +554,9 @@
// Load the thumbnail from the system
thumbnailData = ssp.getTaskThumbnail(taskKey.id);
if (thumbnailData.thumbnail != null) {
- mThumbnailCache.put(taskKey, thumbnailData);
+ if (!ActivityManager.ENABLE_TASK_SNAPSHOTS) {
+ mThumbnailCache.put(taskKey, thumbnailData);
+ }
return thumbnailData.thumbnail;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index a2a8199..a691a424 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -30,6 +30,8 @@
import android.widget.FrameLayout.LayoutParams;
import com.android.systemui.R;
+import com.android.systemui.pip.tv.PipManager;
+import com.android.systemui.pip.tv.PipRecentsOverlayManager;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
@@ -57,9 +59,7 @@
import com.android.systemui.recents.tv.views.TaskCardView;
import com.android.systemui.recents.tv.views.TaskStackHorizontalGridView;
import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.pip.tv.PipManager;
-import com.android.systemui.pip.tv.PipRecentsOverlayManager;
+import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
import java.util.Collections;
@@ -258,7 +258,7 @@
@Override
public void run() {
Recents.getSystemServices().sendCloseSystemWindows(
- BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+ StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
}
};
DismissRecentsToHomeAnimationStarted dismissEvent =
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
index d64a676..79a774f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
@@ -30,6 +30,7 @@
import android.util.Log;
import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
/**
@@ -90,8 +91,10 @@
mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mCornerShadowPaint.setStyle(Paint.Style.FILL);
mCornerShadowPaint.setDither(true);
- mCornerRadius = resources.getDimensionPixelSize(
- R.dimen.recents_task_view_rounded_corners_radius);
+ mCornerRadius = Recents.getConfiguration().isGridEnabled ?
+ resources.getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_rounded_corners_radius) :
+ resources.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
mCardBounds = new RectF();
mEdgeShadowPaint = new Paint(mCornerShadowPaint);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 2bd651b..6a66fca7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -51,7 +51,7 @@
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
import java.util.Collections;
@@ -170,7 +170,7 @@
}
}
Recents.getSystemServices().sendCloseSystemWindows(
- BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+ StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
}
public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index ee79330..8f9c457 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -454,7 +454,7 @@
}
public final void onBusEvent(DragStartEvent event) {
- updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
+ updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(),
true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
TaskStack.DockState.NONE.viewState.hintTextAlpha,
true /* animateAlpha */, false /* animateBounds */);
@@ -471,7 +471,8 @@
public final void onBusEvent(DragDropTargetChangedEvent event) {
if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
- updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
+ updateVisibleDockRegions(
+ Recents.getConfiguration().getDockStatesForCurrentOrientation(),
true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
TaskStack.DockState.NONE.viewState.hintTextAlpha,
true /* animateAlpha */, true /* animateBounds */);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index d3ec984..db0c95e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -28,7 +28,6 @@
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
@@ -45,25 +44,6 @@
import java.util.ArrayList;
/**
- * Represents the dock regions for each orientation.
- */
-class DockRegion {
- public static TaskStack.DockState[] PHONE_LANDSCAPE = {
- // We only allow docking to the left in landscape for now on small devices
- TaskStack.DockState.LEFT
- };
- public static TaskStack.DockState[] PHONE_PORTRAIT = {
- // We only allow docking to the top for now on small devices
- TaskStack.DockState.TOP
- };
- public static TaskStack.DockState[] TABLET_LANDSCAPE = {
- TaskStack.DockState.LEFT,
- TaskStack.DockState.RIGHT
- };
- public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
-}
-
-/**
* Handles touch events for a RecentsView.
*/
public class RecentsViewTouchHandler {
@@ -111,20 +91,6 @@
}
/**
- * Returns the preferred dock states for the current orientation.
- */
- public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
- boolean isLandscape = mRv.getResources().getConfiguration().orientation ==
- Configuration.ORIENTATION_LANDSCAPE;
- RecentsConfiguration config = Recents.getConfiguration();
- if (config.isLargeScreen) {
- return isLandscape ? DockRegion.TABLET_LANDSCAPE : DockRegion.TABLET_PORTRAIT;
- } else {
- return isLandscape ? DockRegion.PHONE_LANDSCAPE : DockRegion.PHONE_PORTRAIT;
- }
- }
-
- /**
* Returns the set of visible dock states for this current drag.
*/
public ArrayList<TaskStack.DockState> getVisibleDockStates() {
@@ -162,10 +128,14 @@
mRv.getLocationInWindow(recentsViewLocation);
mTaskViewOffset.set(mTaskView.getLeft() - recentsViewLocation[0] + event.tlOffset.x,
mTaskView.getTop() - recentsViewLocation[1] + event.tlOffset.y);
- float x = mDownPos.x - mTaskViewOffset.x;
- float y = mDownPos.y - mTaskViewOffset.y;
- mTaskView.setTranslationX(x);
- mTaskView.setTranslationY(y);
+
+ // Change space coordinates relative to the view to RecentsView when user initiates a touch
+ if (event.isUserTouchInitiated) {
+ float x = mDownPos.x - mTaskViewOffset.x;
+ float y = mDownPos.y - mTaskViewOffset.y;
+ mTaskView.setTranslationX(x);
+ mTaskView.setTranslationY(y);
+ }
mVisibleDockStates.clear();
if (ActivityManager.supportsMultiWindow() && !ssp.hasDockedTask()
@@ -176,7 +146,8 @@
EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent());
} else {
// Add the dock state drop targets (these take priority)
- TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
+ TaskStack.DockState[] dockStates = Recents.getConfiguration()
+ .getDockStatesForCurrentOrientation();
for (TaskStack.DockState dockState : dockStates) {
registerDropTargetForCurrentDrag(dockState);
dockState.update(mRv.getContext());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index fc2550a..8ae7a83 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -87,6 +87,7 @@
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
@@ -265,8 +266,9 @@
mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm);
mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
mAnimationHelper = new TaskStackAnimationHelper(context, this);
- mTaskCornerRadiusPx = res.getDimensionPixelSize(
- R.dimen.recents_task_view_rounded_corners_radius);
+ mTaskCornerRadiusPx = Recents.getConfiguration().isGridEnabled ?
+ res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
+ res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
mDividerSize = ssp.getDockedDividerSize(context);
mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
mDisplayRect = ssp.getDisplayRect();
@@ -1869,6 +1871,26 @@
setRelativeFocusedTask(false, false /* stackTasksOnly */, true /* animated */);
}
+ public final void onBusEvent(NavigateTaskViewEvent event) {
+ if (useGridLayout()) {
+ final int taskCount = mStack.getTaskCount();
+ final int currentIndex = mStack.indexOfStackTask(getFocusedTask());
+ final int nextIndex = mLayoutAlgorithm.mTaskGridLayoutAlgorithm.navigateFocus(taskCount,
+ currentIndex, event.direction);
+ setFocusedTask(nextIndex, false, true);
+ } else {
+ switch (event.direction) {
+ case UP:
+ EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
+ break;
+ case DOWN:
+ EventBus.getDefault().send(
+ new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
+ break;
+ }
+ }
+ }
+
public final void onBusEvent(UserInteractionEvent event) {
// Poke the doze trigger on user interaction
mUIDozeTrigger.poke();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index e41a718..e941c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -183,6 +183,7 @@
}
setOutlineProvider(mViewBounds);
setOnLongClickListener(this);
+ setAccessibilityDelegate(new TaskViewAccessibilityDelegate(this));
}
/** Set callback */
@@ -257,6 +258,11 @@
}
@Override
+ public void addChildrenForAccessibility(ArrayList<View> outChildren) {
+ // Prevent any children from being focusable during talkback
+ }
+
+ @Override
public boolean hasOverlappingRendering() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
new file mode 100644
index 0000000..759daf1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.recents.views;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
+import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.TaskStack;
+
+public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
+ private static final String TAG = "TaskViewAccessibilityDelegate";
+
+ private final TaskView mTaskView;
+
+ protected static final int OPEN = R.id.action_open;
+ protected static final int DIMISS = R.id.action_dimiss;
+ protected static final int SPLIT_TASK_TOP = R.id.action_split_task_to_top;
+ protected static final int SPLIT_TASK_LEFT = R.id.action_split_task_to_left;
+ protected static final int SPLIT_TASK_RIGHT = R.id.action_split_task_to_right;
+
+ protected final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
+
+ public TaskViewAccessibilityDelegate(TaskView taskView) {
+ mTaskView = taskView;
+ Context context = taskView.getContext();
+ mActions.put(OPEN, new AccessibilityAction(OPEN,
+ context.getString(R.string.recents_accessibility_open)));
+ mActions.put(DIMISS, new AccessibilityAction(DIMISS,
+ context.getString(R.string.recents_accessibility_dismissed)));
+ mActions.put(SPLIT_TASK_TOP, new AccessibilityAction(SPLIT_TASK_TOP,
+ context.getString(R.string.recents_accessibility_split_screen_top)));
+ mActions.put(SPLIT_TASK_LEFT, new AccessibilityAction(SPLIT_TASK_LEFT,
+ context.getString(R.string.recents_accessibility_split_screen_left)));
+ mActions.put(SPLIT_TASK_RIGHT, new AccessibilityAction(SPLIT_TASK_RIGHT,
+ context.getString(R.string.recents_accessibility_split_screen_right)));
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(mActions.get(OPEN));
+ info.addAction(mActions.get(DIMISS));
+ if (ActivityManager.supportsSplitScreenMultiWindow()
+ && !Recents.getSystemServices().hasDockedTask()) {
+ TaskStack.DockState[] dockStates = Recents.getConfiguration()
+ .getDockStatesForCurrentOrientation();
+ for (TaskStack.DockState dockState: dockStates) {
+ if (dockState == TaskStack.DockState.TOP) {
+ info.addAction(mActions.get(SPLIT_TASK_TOP));
+ } else if (dockState == TaskStack.DockState.LEFT) {
+ info.addAction(mActions.get(SPLIT_TASK_LEFT));
+ } else if (dockState == TaskStack.DockState.RIGHT) {
+ info.addAction(mActions.get(SPLIT_TASK_RIGHT));
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == OPEN) {
+ mTaskView.onClick(host);
+ } else if (action == DIMISS) {
+ mTaskView.dismissTask();
+ } else if (action == SPLIT_TASK_TOP) {
+ simulateDragIntoMultiwindow(TaskStack.DockState.TOP);
+ } else if (action == SPLIT_TASK_LEFT) {
+ simulateDragIntoMultiwindow(TaskStack.DockState.LEFT);
+ } else if (action == SPLIT_TASK_RIGHT) {
+ simulateDragIntoMultiwindow(TaskStack.DockState.RIGHT);
+ } else {
+ return super.performAccessibilityAction(host, action, args);
+ }
+ return true;
+ }
+
+ /** Simulate a user drag event to split the screen to the respected side */
+ private void simulateDragIntoMultiwindow(TaskStack.DockState dockState) {
+ int orientation = Utilities.getAppConfiguration(mTaskView.getContext()).orientation;
+ EventBus.getDefault().send(new DragStartEvent(mTaskView.getTask(), mTaskView,
+ new Point(0,0), false /* isUserTouchInitiated */));
+ EventBus.getDefault().send(new DragEndEvent(mTaskView.getTask(), mTaskView, dockState));
+ }
+}
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 c0cc83f..b318ea7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -201,7 +201,9 @@
Resources res = context.getResources();
mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
- mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
+ mCornerRadius = Recents.getConfiguration().isGridEnabled ?
+ res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
+ res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
@@ -460,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);
@@ -497,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/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index bae5daa..4ac0f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents.views;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -60,11 +61,11 @@
// Drawing
@ViewDebug.ExportedProperty(category="recents")
- private Rect mTaskViewRect = new Rect();
+ protected Rect mTaskViewRect = new Rect();
@ViewDebug.ExportedProperty(category="recents")
- private Rect mThumbnailRect = new Rect();
+ protected Rect mThumbnailRect = new Rect();
@ViewDebug.ExportedProperty(category="recents")
- private float mThumbnailScale;
+ protected float mThumbnailScale;
private float mFullscreenThumbnailScale;
/** The height, in pixels, of the task view's title bar. */
private int mTitleBarHeight;
@@ -72,14 +73,14 @@
private boolean mOverlayHeaderOnThumbnailActionBar = true;
private ThumbnailData mThumbnailData;
- private int mCornerRadius;
+ protected int mCornerRadius;
@ViewDebug.ExportedProperty(category="recents")
private float mDimAlpha;
private Matrix mMatrix = new Matrix();
- private Paint mDrawPaint = new Paint();
+ protected Paint mDrawPaint = new Paint();
private Paint mLockedPaint = new Paint();
- private Paint mBgFillPaint = new Paint();
- private BitmapShader mBitmapShader;
+ protected Paint mBgFillPaint = new Paint();
+ protected BitmapShader mBitmapShader;
private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
// Clip the top of the thumbnail against the opaque header bar that overlaps this view
@@ -114,8 +115,12 @@
mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
mBgFillPaint.setColor(Color.WHITE);
mLockedPaint.setColor(Color.WHITE);
- mFullscreenThumbnailScale = res.getFraction(
- com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
+ if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
+ mFullscreenThumbnailScale = 1f;
+ } else {
+ mFullscreenThumbnailScale = res.getFraction(
+ com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
+ }
mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
new file mode 100644
index 0000000..2c3e42b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
@@ -0,0 +1,185 @@
+/*
+ * 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.recents.views.grid;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.util.AttributeSet;
+
+import com.android.systemui.R;
+import com.android.systemui.recents.views.TaskViewThumbnail;
+
+public class GridTaskViewThumbnail extends TaskViewThumbnail {
+
+ private Path mThumbnailOutline;
+ private Path mRestBackgroundOutline;
+ private Path mFullBackgroundOutline;
+ // True if either this view's size or thumbnail scale has changed and mThumbnailOutline should
+ // be updated.
+ private boolean mUpdateThumbnailOutline = true;
+
+ public GridTaskViewThumbnail(Context context) {
+ this(context, null);
+ }
+
+ public GridTaskViewThumbnail(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public GridTaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public GridTaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mCornerRadius = getResources().getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_rounded_corners_radius);
+ }
+
+ /**
+ * Called when the task view frame changes, allowing us to move the contents of the header
+ * to match the frame changes.
+ */
+ public void onTaskViewSizeChanged(int width, int height) {
+ mUpdateThumbnailOutline = true;
+ super.onTaskViewSizeChanged(width, height);
+ }
+
+ /**
+ * Updates the scale of the bitmap relative to this view.
+ */
+ public void updateThumbnailMatrix() {
+ mUpdateThumbnailOutline = true;
+ super.updateThumbnailMatrix();
+ }
+
+ private void updateThumbnailOutline() {
+ final int titleHeight = getResources().getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_header_height);
+ final int viewWidth = mTaskViewRect.width();
+ final int viewHeight = mTaskViewRect.height() - titleHeight;
+ final int thumbnailWidth = Math.min(viewWidth,
+ (int) (mThumbnailRect.width() * mThumbnailScale));
+ final int thumbnailHeight = Math.min(viewHeight,
+ (int) (mThumbnailRect.height() * mThumbnailScale));
+ // Draw the thumbnail, we only round the bottom corners:
+ //
+ // outerLeft outerRight
+ // <-----------------------> mRestBackgroundOutline
+ // _________________________ (thumbnailWidth < viewWidth)
+ // |_______________________| outerTop A ____ B
+ // | | ↑ | |
+ // | | | | |
+ // | | | | |
+ // | | | | | C
+ // \_______________________/ ↓ |__/
+ // mCornerRadius outerBottom E D
+ //
+ // mRestBackgroundOutline (thumbnailHeight < viewHeight)
+ // A _________________________ B
+ // | | C
+ // F \_______________________/
+ // E D
+ final int outerLeft = 0;
+ final int outerTop = 0;
+ final int outerRight = outerLeft + thumbnailWidth;
+ final int outerBottom = outerTop + thumbnailHeight;
+ mThumbnailOutline = new Path();
+ mThumbnailOutline.moveTo(outerLeft, outerTop);
+ mThumbnailOutline.lineTo(outerRight, outerTop);
+ mThumbnailOutline.lineTo(outerRight, outerBottom - mCornerRadius);
+ mThumbnailOutline.arcTo(outerRight - 2 * mCornerRadius, outerBottom - 2 * mCornerRadius,
+ outerRight, outerBottom, 0, 90, false);
+ mThumbnailOutline.lineTo(outerLeft + mCornerRadius, outerBottom);
+ mThumbnailOutline.arcTo(outerLeft, outerBottom - 2 * mCornerRadius,
+ outerLeft + 2 * mCornerRadius, outerBottom, 90, 90, false);
+ mThumbnailOutline.lineTo(outerLeft, outerTop);
+ mThumbnailOutline.close();
+
+ if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+ if (thumbnailWidth < viewWidth) {
+ final int l = Math.max(0, outerRight - mCornerRadius);
+ final int r = outerRight;
+ final int t = outerTop;
+ final int b = outerBottom;
+ mRestBackgroundOutline = new Path();
+ mRestBackgroundOutline.moveTo(l, t); // A
+ mRestBackgroundOutline.lineTo(r, t); // B
+ mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
+ mRestBackgroundOutline.arcTo(r - 2 * mCornerRadius, b - 2 * mCornerRadius, r, b,
+ 0, 90, false); // D
+ mRestBackgroundOutline.lineTo(l, b); // E
+ mRestBackgroundOutline.lineTo(l, t); // A
+ mRestBackgroundOutline.close();
+
+ }
+ if (thumbnailHeight < viewHeight) {
+ final int l = outerLeft;
+ final int r = outerRight;
+ final int t = Math.max(0, thumbnailHeight - mCornerRadius);
+ final int b = outerBottom;
+ mRestBackgroundOutline = new Path();
+ mRestBackgroundOutline.moveTo(l, t); // A
+ mRestBackgroundOutline.lineTo(r, t); // B
+ mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
+ mRestBackgroundOutline.arcTo(r - 2 * mCornerRadius, b - 2 * mCornerRadius, r, b,
+ 0, 90, false); // D
+ mRestBackgroundOutline.lineTo(l + mCornerRadius, b); // E
+ mRestBackgroundOutline.arcTo(l, b - 2 * mCornerRadius, l + 2 * mCornerRadius, b,
+ 90, 90, false); // F
+ mRestBackgroundOutline.lineTo(l, t); // A
+ mRestBackgroundOutline.close();
+
+ }
+ } else {
+ mFullBackgroundOutline = mThumbnailOutline;
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final int titleHeight = getResources().getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_header_height);
+ final int viewWidth = mTaskViewRect.width();
+ final int viewHeight = mTaskViewRect.height() - titleHeight;
+ final int thumbnailWidth = Math.min(viewWidth,
+ (int) (mThumbnailRect.width() * mThumbnailScale));
+ final int thumbnailHeight = Math.min(viewHeight,
+ (int) (mThumbnailRect.height() * mThumbnailScale));
+
+ if (mUpdateThumbnailOutline) {
+ updateThumbnailOutline();
+ mUpdateThumbnailOutline = false;
+ }
+ if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+ // Draw the background, there will be some small overdraw with the thumbnail
+ if (thumbnailWidth < viewWidth) {
+ // Portrait thumbnail on a landscape task view
+ canvas.drawPath(mRestBackgroundOutline, mBgFillPaint);
+ }
+ if (thumbnailHeight < viewHeight) {
+ // Landscape thumbnail on a portrait task view
+ canvas.drawPath(mRestBackgroundOutline, mBgFillPaint);
+ }
+ canvas.drawPath(mThumbnailOutline, mDrawPaint);
+ } else {
+ canvas.drawPath(mFullBackgroundOutline, mBgFillPaint);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index 70536b1..02d1cc1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -23,6 +23,8 @@
import android.view.WindowManager;
import com.android.systemui.R;
+import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewTransform;
@@ -63,6 +65,8 @@
Rect size;
int[] xOffsets;
int[] yOffsets;
+ int tasksPerLine;
+ int lines;
TaskGridRectInfo(int taskCount) {
size = new Rect();
@@ -71,10 +75,10 @@
int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount);
- int tasksPerLine = layoutTaskCount < 2 ? 1 : (
+ tasksPerLine = layoutTaskCount < 2 ? 1 : (
layoutTaskCount < 5 ? 2 : (
layoutTaskCount < 7 ? 3 : 4));
- int lines = layoutTaskCount < 3 ? 1 : 2;
+ lines = layoutTaskCount < 3 ? 1 : 2;
// A couple of special cases.
boolean landscapeWindow = mWindowRect.width() > mWindowRect.height();
@@ -101,11 +105,13 @@
if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
// Width bound.
taskWidth = maxTaskWidth;
- taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight);
+ // Here we should round the height to the nearest integer.
+ taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight + 0.5);
} else {
// Height bound.
taskHeight = maxTaskHeight;
- taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio);
+ // Here we should round the width to the nearest integer.
+ taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio + 0.5);
}
size.set(0, 0, taskWidth, taskHeight);
@@ -200,6 +206,48 @@
return transformOut;
}
+ /**
+ * Return the proper task index to focus for arrow key navigation.
+ * @param taskCount The amount of tasks.
+ * @param currentFocusedIndex The index of the currently focused task.
+ * @param direction The direction we're navigating.
+ * @return The index of the task that should get the focus.
+ */
+ public int navigateFocus(int taskCount, int currentFocusedIndex, Direction direction) {
+ if (taskCount < 1 || taskCount > MAX_LAYOUT_TASK_COUNT) {
+ return -1;
+ }
+ if (currentFocusedIndex == -1) {
+ return 0;
+ }
+ int newIndex = currentFocusedIndex;
+ final TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
+ final int currentLine = (taskCount - 1 - currentFocusedIndex) / gridInfo.tasksPerLine;
+ switch (direction) {
+ case UP:
+ newIndex += gridInfo.tasksPerLine;
+ newIndex = newIndex >= taskCount ? currentFocusedIndex : newIndex;
+ break;
+ case DOWN:
+ newIndex -= gridInfo.tasksPerLine;
+ newIndex = newIndex < 0 ? currentFocusedIndex : newIndex;
+ break;
+ case LEFT:
+ newIndex++;
+ final int leftMostIndex = (taskCount - 1) - currentLine * gridInfo.tasksPerLine;
+ newIndex = newIndex > leftMostIndex ? currentFocusedIndex : newIndex;
+ break;
+ case RIGHT:
+ newIndex--;
+ int rightMostIndex =
+ (taskCount - 1) - (currentLine + 1) * gridInfo.tasksPerLine + 1;
+ rightMostIndex = rightMostIndex < 0 ? 0 : rightMostIndex;
+ newIndex = newIndex < rightMostIndex ? currentFocusedIndex : newIndex;
+ break;
+ }
+ return newIndex;
+ }
+
public void initialize(Rect windowRect) {
mWindowRect = windowRect;
// Define paddings in terms of percentage of the total area.
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index db021ff..7135caf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -62,6 +62,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.systemui.util.NotificationChannels;
import java.io.File;
import java.io.FileOutputStream;
@@ -178,6 +179,7 @@
// The public notification will show similar info but with the actual screenshot omitted
mPublicNotificationBuilder = new Notification.Builder(context)
+ .setChannel(NotificationChannels.SCREENSHOTS)
.setContentTitle(r.getString(R.string.screenshot_saving_title))
.setContentText(r.getString(R.string.screenshot_saving_text))
.setSmallIcon(R.drawable.stat_notify_image)
@@ -189,6 +191,7 @@
SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder);
mNotificationBuilder = new Notification.Builder(context)
+ .setChannel(NotificationChannels.SCREENSHOTS)
.setTicker(r.getString(R.string.screenshot_saving_ticker)
+ (mTickerAddSpace ? " " : ""))
.setContentTitle(r.getString(R.string.screenshot_saving_title))
@@ -332,6 +335,7 @@
// Update the text and the icon for the existing notification
mPublicNotificationBuilder
+ .setChannel(NotificationChannels.SCREENSHOTS)
.setContentTitle(r.getString(R.string.screenshot_saved_title))
.setContentText(r.getString(R.string.screenshot_saved_text))
.setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
@@ -340,6 +344,7 @@
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color));
mNotificationBuilder
+ .setChannel(NotificationChannels.SCREENSHOTS)
.setContentTitle(r.getString(R.string.screenshot_saved_title))
.setContentText(r.getString(R.string.screenshot_saved_text))
.setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
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/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 3cd2a7a..b9a0f74 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -44,6 +44,7 @@
private boolean mVisible = false;
private boolean mMinimized = false;
private boolean mAdjustedForIme = false;
+ private boolean mHomeStackResizable = false;
private ForcedResizableInfoActivityController mForcedResizableController;
@Override
@@ -75,6 +76,7 @@
mView = (DividerView)
LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
+ mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
final int size = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
@@ -92,7 +94,7 @@
removeDivider();
addDivider(configuration);
if (mMinimized) {
- mView.setMinimizedDockStack(true);
+ mView.setMinimizedDockStack(true, mHomeStackResizable);
updateTouchable();
}
}
@@ -106,13 +108,14 @@
mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
// Update state because animations won't finish.
- mView.setMinimizedDockStack(mMinimized);
+ mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
}
}
});
}
- private void updateMinimizedDockedStack(final boolean minimized, final long animDuration) {
+ private void updateMinimizedDockedStack(final boolean minimized, final long animDuration,
+ final boolean isHomeStackResizable) {
mView.post(new Runnable() {
@Override
public void run() {
@@ -120,9 +123,9 @@
mMinimized = minimized;
updateTouchable();
if (animDuration > 0) {
- mView.setMinimizedDockStack(minimized, animDuration);
+ mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable);
} else {
- mView.setMinimizedDockStack(minimized);
+ mView.setMinimizedDockStack(minimized, isHomeStackResizable);
}
}
}
@@ -139,7 +142,7 @@
}
private void updateTouchable() {
- mWindowManager.setTouchable(!mMinimized && !mAdjustedForIme);
+ mWindowManager.setTouchable((mHomeStackResizable || !mMinimized) && !mAdjustedForIme);
}
@Override
@@ -162,9 +165,10 @@
}
@Override
- public void onDockedStackMinimizedChanged(boolean minimized, long animDuration)
- throws RemoteException {
- updateMinimizedDockedStack(minimized, animDuration);
+ public void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
+ boolean isHomeStackResizable) throws RemoteException {
+ mHomeStackResizable = isHomeStackResizable;
+ updateMinimizedDockedStack(minimized, animDuration, isHomeStackResizable);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 47d2def..49035ba 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -64,6 +64,7 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
+import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.activity.UndockingTaskEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
@@ -123,6 +124,8 @@
private boolean mMoving;
private int mTouchSlop;
private boolean mBackgroundLifted;
+ private boolean mIsInMinimizeInteraction;
+ private int mDividerPositionBeforeMinimized;
private int mDividerInsets;
private int mDisplayWidth;
@@ -145,6 +148,7 @@
private VelocityTracker mVelocityTracker;
private FlingAnimationUtils mFlingAnimationUtils;
private DividerSnapAlgorithm mSnapAlgorithm;
+ private DividerSnapAlgorithm mMinimizedSnapAlgorithm;
private final Rect mStableInsets = new Rect();
private boolean mGrowRecents;
@@ -154,6 +158,7 @@
private int mExitStartPosition;
private GestureDetector mGestureDetector;
private boolean mDockedStackMinimized;
+ private boolean mHomeStackResizable;
private boolean mAdjustedForIme;
private DividerState mState;
@@ -350,8 +355,9 @@
|| mStableInsets.bottom != insets.getStableInsetBottom()) {
mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
insets.getStableInsetRight(), insets.getStableInsetBottom());
- if (mSnapAlgorithm != null) {
+ if (mSnapAlgorithm != null || mMinimizedSnapAlgorithm != null) {
mSnapAlgorithm = null;
+ mMinimizedSnapAlgorithm = null;
initializeSnapAlgorithm();
}
}
@@ -446,11 +452,17 @@
mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
}
+ if (mMinimizedSnapAlgorithm == null) {
+ mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
+ mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
+ mStableInsets, mDockedStackMinimized && mHomeStackResizable);
+ }
}
public DividerSnapAlgorithm getSnapAlgorithm() {
initializeSnapAlgorithm();
- return mSnapAlgorithm;
+ return mDockedStackMinimized && mHomeStackResizable ? mMinimizedSnapAlgorithm :
+ mSnapAlgorithm;
}
public int getCurrentPosition() {
@@ -495,7 +507,7 @@
mMoving = true;
}
if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
- SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(
+ SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget(
mStartPosition, 0 /* velocity */, false /* hardDismiss */);
resizeStackDelayed(calculatePosition(x, y), mStartPosition, snapTarget);
}
@@ -551,9 +563,10 @@
private void fling(int position, float velocity, boolean avoidDismissStart,
boolean logMetrics) {
- SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
- if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
- snapTarget = mSnapAlgorithm.getFirstSplitTarget();
+ DividerSnapAlgorithm currentSnapAlgorithm = getSnapAlgorithm();
+ SnapTarget snapTarget = currentSnapAlgorithm.calculateSnapTarget(position, velocity);
+ if (avoidDismissStart && snapTarget == currentSnapAlgorithm.getDismissStartTarget()) {
+ snapTarget = currentSnapAlgorithm.getFirstSplitTarget();
}
if (logMetrics) {
logResizeEvent(snapTarget);
@@ -574,6 +587,10 @@
private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget,
final long endDelay) {
+ if (mCurrentAnimator != null) {
+ cancelFlingAnimation();
+ updateDockSide();
+ }
final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
anim.addUpdateListener(animation -> resizeStackDelayed((int) animation.getAnimatedValue(),
@@ -590,6 +607,12 @@
mExitAnimationRunning = false;
EventBus.getDefault().send(new StoppedDragingEvent());
};
+ Runnable notCancelledEndAction = () -> {
+ // Reset minimized divider position after unminimized state animation finishes
+ if (!mDockedStackMinimized && mIsInMinimizeInteraction) {
+ mIsInMinimizeInteraction = false;
+ }
+ };
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@@ -612,8 +635,14 @@
}
if (delay == 0) {
endAction.run();
+ if (!mCancelled) {
+ notCancelledEndAction.run();
+ }
} else {
mHandler.postDelayed(endAction, delay);
+ if (!mCancelled) {
+ mHandler.postDelayed(notCancelledEndAction, delay);
+ }
}
}
});
@@ -692,57 +721,92 @@
}
- public void setMinimizedDockStack(boolean minimized) {
+ public void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable) {
+ mHomeStackResizable = isHomeStackResizable;
updateDockSide();
- mHandle.setAlpha(minimized ? 0f : 1f);
if (!minimized) {
resetBackground();
- } else if (mDockSide == WindowManager.DOCKED_TOP) {
- mBackground.setPivotY(0);
- mBackground.setScaleY(MINIMIZE_DOCK_SCALE);
- } else if (mDockSide == WindowManager.DOCKED_LEFT
- || mDockSide == WindowManager.DOCKED_RIGHT) {
- mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
- ? 0
- : mBackground.getWidth());
- mBackground.setScaleX(MINIMIZE_DOCK_SCALE);
+ } else if (!isHomeStackResizable) {
+ if (mDockSide == WindowManager.DOCKED_TOP) {
+ mBackground.setPivotY(0);
+ mBackground.setScaleY(MINIMIZE_DOCK_SCALE);
+ } else if (mDockSide == WindowManager.DOCKED_LEFT
+ || mDockSide == WindowManager.DOCKED_RIGHT) {
+ mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
+ ? 0
+ : mBackground.getWidth());
+ mBackground.setScaleX(MINIMIZE_DOCK_SCALE);
+ }
}
mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
- mDockedStackMinimized = minimized;
+ if (!isHomeStackResizable) {
+ mHandle.setAlpha(minimized ? 0f : 1f);
+ mDockedStackMinimized = minimized;
+ } else if (mDockedStackMinimized != minimized) {
+ if (mStableInsets.isEmpty()) {
+ SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
+ }
+ if (!mIsInMinimizeInteraction && minimized) {
+ mIsInMinimizeInteraction = true;
+ mDividerPositionBeforeMinimized = DockedDividerUtils.calculateMiddlePosition(
+ isHorizontalDivision(), mStableInsets, mDisplayWidth, mDisplayHeight,
+ mDividerSize);
+ }
+ mMinimizedSnapAlgorithm = null;
+ mDockedStackMinimized = minimized;
+ initializeSnapAlgorithm();
+ }
}
- public void setMinimizedDockStack(boolean minimized, long animDuration) {
+ public void setMinimizedDockStack(boolean minimized, long animDuration,
+ boolean isHomeStackResizable) {
+ mHomeStackResizable = isHomeStackResizable;
updateDockSide();
- mHandle.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(animDuration)
- .alpha(minimized ? 0f : 1f)
- .start();
- if (mDockSide == WindowManager.DOCKED_TOP) {
- mBackground.setPivotY(0);
- mBackground.animate()
- .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
- } else if (mDockSide == WindowManager.DOCKED_LEFT
- || mDockSide == WindowManager.DOCKED_RIGHT) {
- mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
- ? 0
- : mBackground.getWidth());
- mBackground.animate()
- .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
+ if (!isHomeStackResizable) {
+ mMinimizedShadow.animate()
+ .alpha(minimized ? 1f : 0f)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ .setDuration(animDuration)
+ .start();
+ mHandle.animate()
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setDuration(animDuration)
+ .alpha(minimized ? 0f : 1f)
+ .start();
+ if (mDockSide == WindowManager.DOCKED_TOP) {
+ mBackground.setPivotY(0);
+ mBackground.animate()
+ .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
+ } else if (mDockSide == WindowManager.DOCKED_LEFT
+ || mDockSide == WindowManager.DOCKED_RIGHT) {
+ mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
+ ? 0
+ : mBackground.getWidth());
+ mBackground.animate()
+ .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
+ }
+ mDockedStackMinimized = minimized;
+ } else if (mDockedStackMinimized != minimized) {
+ mIsInMinimizeInteraction = true;
+ if (minimized) {
+ mDividerPositionBeforeMinimized = getCurrentPosition();
+ }
+ mMinimizedSnapAlgorithm = null;
+ mDockedStackMinimized = minimized;
+ initializeSnapAlgorithm();
+ stopDragging(getCurrentPosition(), minimized ?
+ mMinimizedSnapAlgorithm.getMiddleTarget() :
+ mSnapAlgorithm.calculateNonDismissingSnapTarget(
+ mDividerPositionBeforeMinimized),
+ animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
}
if (!minimized) {
mBackground.animate().withEndAction(mResetBackgroundRunnable);
}
- mMinimizedShadow.animate()
- .alpha(minimized ? 1f : 0f)
- .setInterpolator(Interpolators.ALPHA_IN)
- .setDuration(animDuration)
- .start();
mBackground.animate()
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setDuration(animDuration)
.start();
- mDockedStackMinimized = minimized;
}
public void setAdjustedForIme(boolean adjustedForIme) {
@@ -809,6 +873,7 @@
mDisplayWidth = info.logicalWidth;
mDisplayHeight = info.logicalHeight;
mSnapAlgorithm = null;
+ mMinimizedSnapAlgorithm = null;
initializeSnapAlgorithm();
}
@@ -871,6 +936,15 @@
}
mLastResizeRect.set(mDockedRect);
+ if (mHomeStackResizable && mIsInMinimizeInteraction) {
+ calculateBoundsForPosition(mDividerPositionBeforeMinimized, mDockSide, mDockedTaskRect);
+ calculateBoundsForPosition(mDividerPositionBeforeMinimized,
+ DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
+ mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedTaskRect,
+ mOtherTaskRect, null);
+ return;
+ }
+
if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
if (mCurrentAnimator != null) {
calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
@@ -922,7 +996,7 @@
} else {
mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
}
- SnapTarget closestDismissTarget = mSnapAlgorithm.getClosestDismissTarget(position);
+ SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
float dimFraction = getDimFraction(position, closestDismissTarget);
mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
getStackIdForDismissTarget(closestDismissTarget),
@@ -943,7 +1017,7 @@
if (mEntranceAnimationRunning) {
return 0f;
}
- float fraction = mSnapAlgorithm.calculateDismissingFraction(position);
+ float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
fraction = Math.max(0, Math.min(fraction, 1f));
fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
if (hasInsetsAtDismissTarget(dismissTarget)) {
@@ -959,13 +1033,13 @@
*/
private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
if (isHorizontalDivision()) {
- if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
+ if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
return mStableInsets.top != 0;
} else {
return mStableInsets.bottom != 0;
}
} else {
- if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
+ if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
return mStableInsets.left != 0;
} else {
return mStableInsets.right != 0;
@@ -1135,6 +1209,7 @@
if (mStableInsets.isEmpty()) {
SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
mSnapAlgorithm = null;
+ mMinimizedSnapAlgorithm = null;
initializeSnapAlgorithm();
}
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
deleted file mode 100644
index faf143e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ /dev/null
@@ -1,2531 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
-import android.app.ActivityOptions;
-import android.app.INotificationManager;
-import android.app.KeyguardManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.app.TaskStackBuilder;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.content.res.Configuration;
-import android.database.ContentObserver;
-import android.graphics.Rect;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.service.dreams.DreamService;
-import android.service.dreams.IDreamManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-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;
-import android.view.ViewParent;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.RemoteViews;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardHostView.OnDismissAction;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.RecentsComponent;
-import com.android.systemui.SwipeHelper;
-import com.android.systemui.SystemUI;
-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.HeadsUpManager;
-import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.statusbar.policy.RemoteInputView;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.Stack;
-
-public abstract class BaseStatusBar extends SystemUI implements
- CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
- ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
- ExpandableNotificationRow.OnExpandClickListener {
- public static final String TAG = "StatusBar";
- public static final boolean DEBUG = false;
- public static final boolean MULTIUSER_DEBUG = false;
-
- public static final boolean ENABLE_REMOTE_INPUT =
- SystemProperties.getBoolean("debug.enable_remote_input", true);
- public static final boolean ENABLE_CHILD_NOTIFICATIONS
- = SystemProperties.getBoolean("debug.child_notifs", true);
- public static final boolean FORCE_REMOTE_INPUT_HISTORY =
- SystemProperties.getBoolean("debug.force_remoteinput_history", false);
- private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
-
- protected static final int MSG_SHOW_RECENT_APPS = 1019;
- protected static final int MSG_HIDE_RECENT_APPS = 1020;
- protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
- protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
- protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
- protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
- protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
- protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
- protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
-
- protected static final boolean ENABLE_HEADS_UP = true;
- protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
-
- private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-
- // Should match the values in PhoneWindowManager
- public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
- public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
-
- private static final String BANNER_ACTION_CANCEL =
- "com.android.systemui.statusbar.banner_action_cancel";
- private static final String BANNER_ACTION_SETUP =
- "com.android.systemui.statusbar.banner_action_setup";
- private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
- = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
-
- protected CommandQueue mCommandQueue;
- protected IStatusBarService mBarService;
- protected H mHandler = createHandler();
-
- // all notifications
- protected NotificationData mNotificationData;
- protected NotificationStackScrollLayout mStackScroller;
-
- protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
-
- protected RemoteInputController mRemoteInputController;
-
- // for heads up notifications
- protected HeadsUpManager mHeadsUpManager;
-
- // handling reordering
- protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
-
- protected int mCurrentUserId = 0;
- final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
-
- protected int mLayoutDirection = -1; // invalid
- protected AccessibilityManager mAccessibilityManager;
-
- protected boolean mDeviceInteractive;
-
- protected boolean mVisible;
- protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
- protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
-
- /**
- * Notifications with keys in this set are not actually around anymore. We kept them around
- * when they were canceled in response to a remote input interaction. This allows us to show
- * what you replied and allows you to continue typing into it.
- */
- protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
-
- // mScreenOnFromKeyguard && mVisible.
- private boolean mVisibleToUser;
-
- private Locale mLocale;
- private float mFontScale;
-
- protected boolean mUseHeadsUp = false;
- protected boolean mHeadsUpTicker = false;
- protected boolean mDisableNotificationAlerts = false;
-
- protected DevicePolicyManager mDevicePolicyManager;
- protected IDreamManager mDreamManager;
- protected PowerManager mPowerManager;
- protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-
- // public mode, private notifications, etc
- private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
- private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
- private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
-
- private UserManager mUserManager;
- private int mDensity;
-
- protected KeyguardManager mKeyguardManager;
- private LockPatternUtils mLockPatternUtils;
-
- // UI-specific methods
-
- /**
- * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
- * and add them to the window manager.
- */
- protected abstract void createAndAddWindows();
-
- protected WindowManager mWindowManager;
- protected IWindowManager mWindowManagerService;
-
- protected Display mDisplay;
-
- private boolean mDeviceProvisioned = false;
-
- protected RecentsComponent mRecents;
-
- protected int mZenMode;
-
- // which notification is currently being longpress-examined by the user
- private NotificationGuts mNotificationGutsExposed;
-
- private KeyboardShortcuts mKeyboardShortcuts;
-
- /**
- * The {@link StatusBarState} of the status bar.
- */
- protected int mState;
- protected boolean mBouncerShowing;
- protected boolean mShowLockscreenNotifications;
- protected boolean mAllowLockscreenRemoteInput;
-
- protected NotificationShelf mNotificationShelf;
- protected DismissView mDismissView;
- protected EmptyShadeView mEmptyShadeView;
-
- private NotificationClicker mNotificationClicker = new NotificationClicker();
-
- protected AssistManager mAssistManager;
-
- protected boolean mVrMode;
-
- private Set<String> mNonBlockablePkgs;
-
- @Override // NotificationData.Environment
- public boolean isDeviceProvisioned() {
- return mDeviceProvisioned;
- }
-
- private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
- @Override
- public void onVrStateChanged(boolean enabled) {
- mVrMode = enabled;
- }
- };
-
- public boolean isDeviceInVrMode() {
- return mVrMode;
- }
-
- 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);
-
- updateLockscreenNotificationSetting();
- }
- };
-
- private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
- // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
- mUsersAllowingPrivateNotifications.clear();
- mUsersAllowingNotifications.clear();
- // ... and refresh all the notifications
- updateLockscreenNotificationSetting();
- updateNotifications();
- }
- };
-
- private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
- @Override
- public boolean onClickHandler(
- final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
- if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
- return true;
- }
-
- if (DEBUG) {
- Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
- }
- logActionClick(view);
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- try {
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- final boolean isActivity = pendingIntent.isActivity();
- if (isActivity) {
- final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
- final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
- mContext, pendingIntent.getIntent(), mCurrentUserId);
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- try {
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
-
- boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
-
- // close the shade if it was open
- if (handled) {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
- visibilityChanged(false);
- mAssistManager.hideAssist();
- }
-
- // Wait for activity start.
- return handled;
- }
- }, afterKeyguardGone);
- return true;
- } else {
- return superOnClickHandler(view, pendingIntent, fillInIntent);
- }
- }
-
- private void logActionClick(View view) {
- ViewParent parent = view.getParent();
- String key = getNotificationKeyForParent(parent);
- if (key == null) {
- Log.w(TAG, "Couldn't determine notification for click.");
- return;
- }
- int index = -1;
- // If this is a default template, determine the index of the button.
- if (view.getId() == com.android.internal.R.id.action0 &&
- parent != null && parent instanceof ViewGroup) {
- ViewGroup actionGroup = (ViewGroup) parent;
- index = actionGroup.indexOfChild(view);
- }
- try {
- mBarService.onNotificationActionClick(key, index);
- } catch (RemoteException e) {
- // Ignore
- }
- }
-
- private String getNotificationKeyForParent(ViewParent parent) {
- while (parent != null) {
- if (parent instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey();
- }
- parent = parent.getParent();
- }
- return null;
- }
-
- private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
- Intent fillInIntent) {
- return super.onClickHandler(view, pendingIntent, fillInIntent,
- StackId.FULLSCREEN_WORKSPACE_STACK_ID);
- }
-
- private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
- Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
- RemoteInput[] inputs = null;
- if (tag instanceof RemoteInput[]) {
- inputs = (RemoteInput[]) tag;
- }
-
- if (inputs == null) {
- return false;
- }
-
- RemoteInput input = null;
-
- for (RemoteInput i : inputs) {
- if (i.getAllowFreeFormInput()) {
- input = i;
- }
- }
-
- if (input == null) {
- return false;
- }
-
- ViewParent p = view.getParent();
- RemoteInputView riv = null;
- while (p != null) {
- if (p instanceof View) {
- View pv = (View) p;
- if (pv.isRootNamespace()) {
- riv = (RemoteInputView) pv.findViewWithTag(RemoteInputView.VIEW_TAG);
- break;
- }
- }
- p = p.getParent();
- }
- ExpandableNotificationRow row = null;
- while (p != null) {
- if (p instanceof ExpandableNotificationRow) {
- row = (ExpandableNotificationRow) p;
- break;
- }
- p = p.getParent();
- }
-
- if (riv == null || row == null) {
- return false;
- }
-
- row.setUserExpanded(true);
-
- if (!mAllowLockscreenRemoteInput) {
- final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
- if (isLockscreenPublicMode(userId)) {
- onLockedRemoteInput(row, view);
- return true;
- }
- if (mUserManager.getUserInfo(userId).isManagedProfile()
- && mKeyguardManager.isDeviceLocked(userId)) {
- onLockedWorkRemoteInput(userId, row, view);
- return true;
- }
- }
-
- int width = view.getWidth();
- if (view instanceof TextView) {
- // Center the reveal on the text which might be off-center from the TextView
- TextView tv = (TextView) view;
- if (tv.getLayout() != null) {
- int innerWidth = (int) tv.getLayout().getLineWidth(0);
- innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
- width = Math.min(width, innerWidth);
- }
- }
- int cx = view.getLeft() + width / 2;
- int cy = view.getTop() + view.getHeight() / 2;
- int w = riv.getWidth();
- int h = riv.getHeight();
- int r = Math.max(
- Math.max(cx + cy, cx + (h - cy)),
- Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
- riv.setRevealParameters(cx, cy, r);
- riv.setPendingIntent(pendingIntent);
- riv.setRemoteInput(inputs, input);
- riv.focusAnimated();
-
- return true;
- }
-
- };
-
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- updateCurrentProfilesCache();
- if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
-
- updateLockscreenNotificationSetting();
-
- userSwitched(mCurrentUserId);
- } else if (Intent.ACTION_USER_ADDED.equals(action)) {
- updateCurrentProfilesCache();
- } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- List<ActivityManager.RecentTaskInfo> recentTask = null;
- try {
- recentTask = ActivityManager.getService().getRecentTasks(1,
- ActivityManager.RECENT_WITH_EXCLUDED
- | ActivityManager.RECENT_INCLUDE_PROFILES,
- mCurrentUserId).getList();
- } catch (RemoteException e) {
- // Abandon hope activity manager not running.
- }
- if (recentTask != null && recentTask.size() > 0) {
- UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId);
- if (user != null && user.isManagedProfile()) {
- Toast toast = Toast.makeText(mContext,
- R.string.managed_profile_foreground_toast,
- Toast.LENGTH_SHORT);
- TextView text = (TextView) toast.getView().findViewById(
- android.R.id.message);
- text.setCompoundDrawablesRelativeWithIntrinsicBounds(
- R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
- int paddingPx = mContext.getResources().getDimensionPixelSize(
- R.dimen.managed_profile_toast_padding);
- text.setCompoundDrawablePadding(paddingPx);
- toast.show();
- }
- }
- } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
- NotificationManager noMan = (NotificationManager)
- mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.cancel(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS);
-
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
- if (BANNER_ACTION_SETUP.equals(action)) {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
- mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-
- );
- }
- } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
- final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
- final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
- if (intentSender != null) {
- try {
- mContext.startIntentSender(intentSender, null, 0, 0, 0);
- } catch (IntentSender.SendIntentException e) {
- /* ignore */
- }
- }
- if (notificationKey != null) {
- try {
- mBarService.onNotificationClick(notificationKey);
- } catch (RemoteException e) {
- /* ignore */
- }
- }
- }
- }
- };
-
- private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-
- if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
- isCurrentProfile(getSendingUserId())) {
- mUsersAllowingPrivateNotifications.clear();
- updateLockscreenNotificationSetting();
- updateNotifications();
- } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
- if (userId != mCurrentUserId && isCurrentProfile(userId)) {
- onWorkChallengeChanged();
- }
- }
- }
- };
-
- private final NotificationListenerService mNotificationListener =
- new NotificationListenerService() {
- @Override
- public void onListenerConnected() {
- if (DEBUG) Log.d(TAG, "onListenerConnected");
- final StatusBarNotification[] notifications = getActiveNotifications();
- if (notifications == null) {
- Log.w(TAG, "onListenerConnected unable to get active notifications.");
- return;
- }
- final RankingMap currentRanking = getCurrentRanking();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- for (StatusBarNotification sbn : notifications) {
- addNotification(sbn, currentRanking, null /* oldEntry */);
- }
- }
- });
- }
-
- @Override
- public void onNotificationPosted(final StatusBarNotification sbn,
- final RankingMap rankingMap) {
- if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
- if (sbn != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- processForRemoteInput(sbn.getNotification());
- String key = sbn.getKey();
- mKeysKeptForRemoteInput.remove(key);
- boolean isUpdate = mNotificationData.get(key) != null;
- // In case we don't allow child notifications, we ignore children of
- // notifications that have a summary, since we're not going to show them
- // anyway. This is true also when the summary is canceled,
- // because children are automatically canceled by NoMan in that case.
- if (!ENABLE_CHILD_NOTIFICATIONS
- && mGroupManager.isChildInGroupWithSummary(sbn)) {
- if (DEBUG) {
- Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
- }
-
- // Remove existing notification to avoid stale data.
- if (isUpdate) {
- removeNotification(key, rankingMap);
- } else {
- mNotificationData.updateRanking(rankingMap);
- }
- return;
- }
- if (isUpdate) {
- updateNotification(sbn, rankingMap);
- } else {
- addNotification(sbn, rankingMap, null /* oldEntry */);
- }
- }
- });
- }
- }
-
- @Override
- public void onNotificationRemoved(StatusBarNotification sbn,
- final RankingMap rankingMap) {
- if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
- if (sbn != null) {
- final String key = sbn.getKey();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- removeNotification(key, rankingMap);
- }
- });
- }
- }
-
- @Override
- public void onNotificationRankingUpdate(final RankingMap rankingMap) {
- if (DEBUG) Log.d(TAG, "onRankingUpdate");
- if (rankingMap != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- updateNotificationRanking(rankingMap);
- }
- });
- } }
-
- };
-
- private void updateCurrentProfilesCache() {
- synchronized (mCurrentProfiles) {
- mCurrentProfiles.clear();
- if (mUserManager != null) {
- for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
- mCurrentProfiles.put(user.id, user);
- }
- }
- }
- }
-
- public void start() {
- mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
- mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
- mDisplay = mWindowManager.getDefaultDisplay();
- mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
-
- mNotificationData = new NotificationData(this);
-
- mAccessibilityManager = (AccessibilityManager)
- mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
-
- mDreamManager = IDreamManager.Stub.asInterface(
- ServiceManager.checkService(DreamService.DREAM_SERVICE));
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
- mLockscreenSettingsObserver,
- UserHandle.USER_ALL);
- if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
- false,
- mSettingsObserver,
- UserHandle.USER_ALL);
- }
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
- true,
- mLockscreenSettingsObserver,
- UserHandle.USER_ALL);
-
- mBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-
- mRecents = getComponent(Recents.class);
-
- final Configuration currentConfig = mContext.getResources().getConfiguration();
- mLocale = currentConfig.locale;
- mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
- mFontScale = currentConfig.fontScale;
- mDensity = currentConfig.densityDpi;
-
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mLockPatternUtils = new LockPatternUtils(mContext);
-
- // Connect in to the status bar manager service
- mCommandQueue = getComponent(CommandQueue.class);
- mCommandQueue.addCallbacks(this);
-
- int[] switches = new int[9];
- ArrayList<IBinder> binders = new ArrayList<IBinder>();
- ArrayList<String> iconSlots = new ArrayList<>();
- ArrayList<StatusBarIcon> icons = new ArrayList<>();
- Rect fullscreenStackBounds = new Rect();
- Rect dockedStackBounds = new Rect();
- try {
- mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
- fullscreenStackBounds, dockedStackBounds);
- } catch (RemoteException ex) {
- // If the system process isn't there we're doomed anyway.
- }
-
- createAndAddWindows();
-
- mSettingsObserver.onChange(false); // set up
- disable(switches[0], switches[6], false /* animate */);
- setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
- fullscreenStackBounds, dockedStackBounds);
- topAppWindowChanged(switches[2] != 0);
- // StatusBarManagerService has a back up of IME token and it's restored here.
- setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0);
-
- // Set up the initial icon state
- int N = iconSlots.size();
- int viewIndex = 0;
- for (int i=0; i < N; i++) {
- setIcon(iconSlots.get(i), icons.get(i));
- }
-
- // Set up the initial notification state.
- try {
- mNotificationListener.registerAsSystemService(mContext,
- new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
- UserHandle.USER_ALL);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to register notification listener", e);
- }
-
-
- if (DEBUG) {
- Log.d(TAG, String.format(
- "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
- icons.size(),
- switches[0],
- switches[1],
- switches[2],
- switches[3]
- ));
- }
-
- mCurrentUserId = ActivityManager.getCurrentUser();
- setHeadsUpUser(mCurrentUserId);
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_ADDED);
- filter.addAction(Intent.ACTION_USER_PRESENT);
- mContext.registerReceiver(mBroadcastReceiver, filter);
-
- IntentFilter internalFilter = new IntentFilter();
- internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
- internalFilter.addAction(BANNER_ACTION_CANCEL);
- internalFilter.addAction(BANNER_ACTION_SETUP);
- mContext.registerReceiver(mBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
-
- IntentFilter allUsersFilter = new IntentFilter();
- allUsersFilter.addAction(
- DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
- allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
- mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
- null, null);
- updateCurrentProfilesCache();
-
- IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService("vrmanager"));
- try {
- vrManager.registerListener(mVrStateCallbacks);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register VR mode state listener: " + e);
- }
-
- mNonBlockablePkgs = new HashSet<String>();
- Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray(
- com.android.internal.R.array.config_nonBlockableNotificationPackages));
- }
-
- protected void notifyUserAboutHiddenNotifications() {
- if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) {
- Log.d(TAG, "user hasn't seen notification about hidden notifications");
- if (!mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
- Log.d(TAG, "insecure lockscreen, skipping notification");
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
- return;
- }
- Log.d(TAG, "disabling lockecreen notifications and alerting the user");
- // disable lockscreen notifications until user acts on the banner.
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0);
-
- final String packageName = mContext.getPackageName();
- PendingIntent cancelIntent = PendingIntent.getBroadcast(mContext, 0,
- new Intent(BANNER_ACTION_CANCEL).setPackage(packageName),
- PendingIntent.FLAG_CANCEL_CURRENT);
- PendingIntent setupIntent = PendingIntent.getBroadcast(mContext, 0,
- new Intent(BANNER_ACTION_SETUP).setPackage(packageName),
- PendingIntent.FLAG_CANCEL_CURRENT);
-
- final int colorRes = com.android.internal.R.color.system_notification_accent_color;
- Notification.Builder note = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_android)
- .setContentTitle(mContext.getString(R.string.hidden_notifications_title))
- .setContentText(mContext.getString(R.string.hidden_notifications_text))
- .setPriority(Notification.PRIORITY_HIGH)
- .setOngoing(true)
- .setColor(mContext.getColor(colorRes))
- .setContentIntent(setupIntent)
- .addAction(R.drawable.ic_close,
- mContext.getString(R.string.hidden_notifications_cancel),
- cancelIntent)
- .addAction(R.drawable.ic_settings,
- mContext.getString(R.string.hidden_notifications_setup),
- setupIntent);
- overrideNotificationAppName(mContext, note);
-
- NotificationManager noMan =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.notify(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS, note.build());
- }
- }
-
- public void userSwitched(int newUserId) {
- setHeadsUpUser(newUserId);
- }
-
- protected abstract void setHeadsUpUser(int newUserId);
-
- @Override // NotificationData.Environment
- public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
- final int thisUserId = mCurrentUserId;
- final int notificationUserId = n.getUserId();
- if (DEBUG && MULTIUSER_DEBUG) {
- Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
- n, thisUserId, notificationUserId));
- }
- return isCurrentProfile(notificationUserId);
- }
-
- protected void setNotificationShown(StatusBarNotification n) {
- setNotificationsShown(new String[]{n.getKey()});
- }
-
- protected void setNotificationsShown(String[] keys) {
- try {
- mNotificationListener.setNotificationsShown(keys);
- } catch (RuntimeException e) {
- Log.d(TAG, "failed setNotificationsShown: ", e);
- }
- }
-
- protected boolean isCurrentProfile(int userId) {
- synchronized (mCurrentProfiles) {
- return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
- }
- }
-
- @Override
- public String getCurrentMediaNotificationKey() {
- return null;
- }
-
- @Override
- public NotificationGroupManager getGroupManager() {
- return mGroupManager;
- }
-
- /**
- * Takes the necessary steps to prepare the status bar for starting an activity, then starts it.
- * @param action A dismiss action that is called if it's safe to start the activity.
- * @param afterKeyguardGone Whether the action should be executed after the Keyguard is gone.
- */
- protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) {
- action.onDismiss();
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- final float fontScale = newConfig.fontScale;
- final int density = newConfig.densityDpi;
- if (density != mDensity || mFontScale != fontScale) {
- onDensityOrFontScaleChanged();
- mDensity = density;
- mFontScale = fontScale;
- }
- }
-
- protected void onDensityOrFontScaleChanged() {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- for (int i = 0; i < activeNotifications.size(); i++) {
- Entry entry = activeNotifications.get(i);
- boolean exposedGuts = entry.row.getGuts() == mNotificationGutsExposed;
- entry.row.reInflateViews();
- if (exposedGuts) {
- mNotificationGutsExposed = entry.row.getGuts();
- bindGuts(entry.row);
- }
- inflateViews(entry, mStackScroller);
- }
- }
-
- protected void bindDismissRunnable(final ExpandableNotificationRow row) {
- row.setOnDismissRunnable(() -> performRemoveNotification(row.getStatusBarNotification()));
- }
-
- protected void performRemoveNotification(StatusBarNotification n) {
- final String pkg = n.getPackageName();
- final String tag = n.getTag();
- final int id = n.getId();
- final int userId = n.getUserId();
- try {
- mBarService.onNotificationClear(pkg, tag, id, userId);
- if (FORCE_REMOTE_INPUT_HISTORY
- && mKeysKeptForRemoteInput.contains(n.getKey())) {
- mKeysKeptForRemoteInput.remove(n.getKey());
- }
- removeNotification(n.getKey(), null);
-
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- }
-
-
- protected void applyColorsAndBackgrounds(StatusBarNotification sbn,
- NotificationData.Entry entry) {
-
- if (entry.getContentView().getId()
- != com.android.internal.R.id.status_bar_latest_event_content) {
- // Using custom RemoteViews
- if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
- && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) {
- entry.row.setShowingLegacyBackground(true);
- entry.legacy = true;
- }
- }
-
- entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- }
-
- public boolean isMediaNotification(NotificationData.Entry entry) {
- // TODO: confirm that there's a valid media key
- return entry.getExpandedContentView() != null &&
- entry.getExpandedContentView()
- .findViewById(com.android.internal.R.id.media_actions) != null;
- }
-
- // The (i) button in the guts that links to the system notification settings for that app
- private void startAppNotificationSettingsActivity(String packageName, final int appUid) {
- final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
- intent.putExtra(Settings.EXTRA_APP_UID, appUid);
- startNotificationGutsIntent(intent, appUid);
- }
-
- private void startNotificationGutsIntent(final Intent intent, final int appUid) {
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- AsyncTask.execute(new Runnable() {
- public void run() {
- TaskStackBuilder.create(mContext)
- .addNextIntentWithParentStack(intent)
- .startActivities(getActivityOptions(),
- new UserHandle(UserHandle.getUserId(appUid)));
- }
- });
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
- return true;
- }
- }, false /* afterKeyguardGone */);
- }
-
- private void bindGuts(final ExpandableNotificationRow row) {
- row.inflateGuts();
- final StatusBarNotification sbn = row.getStatusBarNotification();
- PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier());
- row.setTag(sbn.getPackageName());
- final NotificationGuts guts = row.getGuts();
- guts.setClosedListener((NotificationGuts g) -> {
- if (!row.isRemoved()) {
- mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
- }
- mNotificationGutsExposed = null;
- });
-
- final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-
- final String pkg = sbn.getPackageName();
- final NotificationGuts.OnSettingsClickListener onSettingsClick =
- (View v, int appUid) -> {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
- guts.resetFalsingCheck();
- startAppNotificationSettingsActivity(pkg, appUid);
- };
- final View.OnClickListener onDoneClick =
- (View v) -> {
- // If the user has security enabled, show challenge if the setting is changed.
- if (guts.hasImportanceChanged()
- && isLockscreenPublicMode(sbn.getUser().getIdentifier())
- && (mState == StatusBarState.KEYGUARD
- || mState == StatusBarState.SHADE_LOCKED)) {
- OnDismissAction dismissAction = new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- closeControls(row, guts, v);
- return true;
- }
- };
- onLockedNotificationImportanceChange(dismissAction);
- } else {
- closeControls(row, guts, v);
- }
- };
- guts.bindNotification(pmUser, iNotificationManager, sbn, onSettingsClick, onDoneClick,
- mNonBlockablePkgs);
- }
-
- private void closeControls(
- ExpandableNotificationRow row, NotificationGuts guts, View done) {
- guts.resetFalsingCheck();
-
- int[] rowLocation = new int[2];
- int[] doneLocation = new int[2];
- row.getLocationOnScreen(rowLocation);
- done.getLocationOnScreen(doneLocation);
-
- final int centerX = done.getWidth() / 2;
- final int centerY = done.getHeight() / 2;
- final int x = doneLocation[0] - rowLocation[0] + centerX;
- final int y = doneLocation[1] - rowLocation[1] + centerY;
- dismissPopups(x, y);
- }
-
- protected SwipeHelper.LongPressListener getNotificationLongClicker() {
- return new SwipeHelper.LongPressListener() {
- @Override
- public boolean onLongPress(View v, final int x, final int y) {
- if (!(v instanceof ExpandableNotificationRow)) {
- return false;
- }
- if (v.getWindowToken() == null) {
- Log.e(TAG, "Trying to show notification guts, but not attached to window");
- return false;
- }
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- bindGuts(row);
-
- // Assume we are a status_bar_notification_row
- final NotificationGuts guts = row.getGuts();
- if (guts == null) {
- // This view has no guts. Examples are the more card or the dismiss all view
- return false;
- }
-
- // Already showing?
- if (guts.getVisibility() == View.VISIBLE) {
- dismissPopups(x, y);
- return false;
- }
-
- MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_CONTROLS);
-
- // ensure that it's laid but not visible until actually laid out
- guts.setVisibility(View.INVISIBLE);
- // Post to ensure the the guts are properly laid out.
- guts.post(new Runnable() {
- public void run() {
- if (row.getWindowToken() == null) {
- Log.e(TAG, "Trying to show notification guts, but not attached to "
- + "window");
- return;
- }
- dismissPopups(-1 /* x */, -1 /* y */, false /* resetGear */,
- false /* animate */);
- guts.setVisibility(View.VISIBLE);
- final double horz = Math.max(guts.getWidth() - x, x);
- final double vert = Math.max(guts.getHeight() - y, y);
- final float r = (float) Math.hypot(horz, vert);
- final Animator a
- = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- a.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- // Move the notification view back over the gear
- row.resetTranslation();
- }
- });
- a.start();
- guts.setExposed(true /* exposed */,
- mState == StatusBarState.KEYGUARD /* needsFalsingProtection */);
- row.closeRemoteInput();
- mStackScroller.onHeightChanged(row, true /* needsAnimation */);
- mNotificationGutsExposed = guts;
- }
- });
- return true;
- }
- };
- }
-
- /**
- * Returns the exposed NotificationGuts or null if none are exposed.
- */
- public NotificationGuts getExposedGuts() {
- return mNotificationGutsExposed;
- }
-
- public void dismissPopups() {
- dismissPopups(-1 /* x */, -1 /* y */, true /* resetGear */, false /* animate */);
- }
-
- private void dismissPopups(int x, int y) {
- dismissPopups(x, y, true /* resetGear */, false /* animate */);
- }
-
- public void dismissPopups(int x, int y, boolean resetGear, boolean animate) {
- if (mNotificationGutsExposed != null) {
- mNotificationGutsExposed.closeControls(x, y, true /* save */);
- }
- if (resetGear) {
- mStackScroller.resetExposedGearView(animate, true /* force */);
- }
- }
-
- @Override
- public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) {
- int msg = MSG_SHOW_RECENT_APPS;
- mHandler.removeMessages(msg);
- mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, fromHome ? 1 : 0).sendToTarget();
- }
-
- @Override
- public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- int msg = MSG_HIDE_RECENT_APPS;
- mHandler.removeMessages(msg);
- mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0,
- triggeredFromHomeKey ? 1 : 0).sendToTarget();
- }
-
- @Override
- public void toggleRecentApps() {
- toggleRecents();
- }
-
- @Override
- public void toggleSplitScreen() {
- toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
- }
-
- @Override
- public void preloadRecentApps() {
- int msg = MSG_PRELOAD_RECENT_APPS;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- @Override
- public void cancelPreloadRecentApps() {
- int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- @Override
- public void dismissKeyboardShortcutsMenu() {
- int msg = MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- @Override
- public void toggleKeyboardShortcutsMenu(int deviceId) {
- int msg = MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU;
- mHandler.removeMessages(msg);
- mHandler.obtainMessage(msg, deviceId, 0).sendToTarget();
- }
-
- /** Jumps to the next affiliated task in the group. */
- public void showNextAffiliatedTask() {
- int msg = MSG_SHOW_NEXT_AFFILIATED_TASK;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- /** Jumps to the previous affiliated task in the group. */
- public void showPreviousAffiliatedTask() {
- int msg = MSG_SHOW_PREV_AFFILIATED_TASK;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- protected H createHandler() {
- return new H();
- }
-
- protected void sendCloseSystemWindows(String reason) {
- try {
- ActivityManager.getService().closeSystemDialogs(reason);
- } catch (RemoteException e) {
- }
- }
-
- protected abstract View getStatusBarView();
-
- /**
- * Toggle docking the app window
- *
- * @param metricsDockAction the action to log when docking is successful, or -1 to not log
- * anything on successful docking
- * @param metricsUndockAction the action to log when undocking, or -1 to not log anything when
- * undocking
- * @return true if toggle split screen was successful
- */
- protected abstract boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction);
-
- /** Proxy for RecentsComponent */
-
- protected void showRecents(boolean triggeredFromAltTab, boolean fromHome) {
- if (mRecents != null) {
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
- mRecents.showRecents(triggeredFromAltTab, fromHome);
- }
- }
-
- protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- if (mRecents != null) {
- mRecents.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
- }
- }
-
- protected void toggleRecents() {
- if (mRecents != null) {
- mRecents.toggleRecents(mDisplay);
- }
- }
-
- protected void preloadRecents() {
- if (mRecents != null) {
- mRecents.preloadRecents();
- }
- }
-
- protected void toggleKeyboardShortcuts(int deviceId) {
- KeyboardShortcuts.toggle(mContext, deviceId);
- }
-
- protected void dismissKeyboardShortcuts() {
- KeyboardShortcuts.dismiss();
- }
-
- protected void cancelPreloadingRecents() {
- if (mRecents != null) {
- mRecents.cancelPreloadingRecents();
- }
- }
-
- protected void showRecentsNextAffiliatedTask() {
- if (mRecents != null) {
- mRecents.showNextAffiliatedTask();
- }
- }
-
- protected void showRecentsPreviousAffiliatedTask() {
- if (mRecents != null) {
- mRecents.showPrevAffiliatedTask();
- }
- }
-
- /**
- * If there is an active heads-up notification and it has a fullscreen intent, fire it now.
- */
- public abstract void maybeEscalateHeadsUp();
-
- /**
- * Save the current "public" (locked and secure) state of the lockscreen.
- */
- public void setLockscreenPublicMode(boolean publicMode, int userId) {
- mLockscreenPublicMode.put(userId, publicMode);
- }
-
- public boolean isLockscreenPublicMode(int userId) {
- return mLockscreenPublicMode.get(userId, false);
- }
-
- protected void onWorkChallengeChanged() {}
-
- /**
- * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
- * "public" (secure & locked) mode?
- */
- public boolean userAllowsNotificationsInPublic(int userHandle) {
- if (userHandle == UserHandle.USER_ALL) {
- return true;
- }
-
- if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowed = 0 != Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
- mUsersAllowingNotifications.append(userHandle, allowed);
- return allowed;
- }
-
- return mUsersAllowingNotifications.get(userHandle);
- }
-
- /**
- * Has the given user chosen to allow their private (full) notifications to be shown even
- * when the lockscreen is in "public" (secure & locked) mode?
- */
- public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
- if (userHandle == UserHandle.USER_ALL) {
- return true;
- }
-
- if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle);
- final boolean allowed = allowedByUser && allowedByDpm;
- mUsersAllowingPrivateNotifications.append(userHandle, allowed);
- return allowed;
- }
-
- return mUsersAllowingPrivateNotifications.get(userHandle);
- }
-
- private boolean adminAllowsUnredactedNotifications(int userHandle) {
- if (userHandle == UserHandle.USER_ALL) {
- return true;
- }
- final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
- userHandle);
- return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
- }
-
- /**
- * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
- * If so, notifications should be hidden.
- */
- @Override // NotificationData.Environment
- public boolean shouldHideNotifications(int userId) {
- return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
- || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
- }
-
- /**
- * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
- * package-specific override.
- */
- @Override // NotificationDate.Environment
- public boolean shouldHideNotifications(String key) {
- return isLockscreenPublicMode(mCurrentUserId)
- && mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET;
- }
-
- /**
- * Returns true if we're on a secure lockscreen.
- */
- @Override // NotificationData.Environment
- public boolean isSecurelyLocked(int userId) {
- return isLockscreenPublicMode(userId);
- }
-
- public void onNotificationClear(StatusBarNotification notification) {
- try {
- mBarService.onNotificationClear(
- notification.getPackageName(),
- notification.getTag(),
- notification.getId(),
- notification.getUserId());
- } catch (android.os.RemoteException ex) {
- // oh well
- }
- }
-
- /**
- * Called when the notification panel layouts
- */
- public void onPanelLaidOut() {
- if (mState == StatusBarState.KEYGUARD) {
- // Since the number of notifications is determined based on the height of the view, we
- // need to update them.
- int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
- int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
- if (maxBefore != maxNotifications) {
- updateRowStates();
- }
- }
- }
-
- protected void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {}
-
- protected void onLockedRemoteInput(ExpandableNotificationRow row, View clickedView) {}
-
- protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
- View clicked) {}
-
- @Override
- public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
- }
-
- protected class H extends Handler {
- public void handleMessage(Message m) {
- switch (m.what) {
- case MSG_SHOW_RECENT_APPS:
- showRecents(m.arg1 > 0, m.arg2 != 0);
- break;
- case MSG_HIDE_RECENT_APPS:
- hideRecents(m.arg1 > 0, m.arg2 > 0);
- break;
- case MSG_TOGGLE_RECENTS_APPS:
- toggleRecents();
- break;
- case MSG_PRELOAD_RECENT_APPS:
- preloadRecents();
- break;
- case MSG_CANCEL_PRELOAD_RECENT_APPS:
- cancelPreloadingRecents();
- break;
- case MSG_SHOW_NEXT_AFFILIATED_TASK:
- showRecentsNextAffiliatedTask();
- break;
- case MSG_SHOW_PREV_AFFILIATED_TASK:
- showRecentsPreviousAffiliatedTask();
- break;
- case MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU:
- toggleKeyboardShortcuts(m.arg1);
- break;
- case MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU:
- dismissKeyboardShortcuts();
- break;
- }
- }
- }
-
- protected void workAroundBadLayerDrawableOpacity(View v) {
- }
-
- protected boolean inflateViews(Entry entry, ViewGroup parent) {
- PackageManager pmUser = getPackageManagerForUser(mContext,
- entry.notification.getUser().getIdentifier());
-
- final StatusBarNotification sbn = entry.notification;
- try {
- entry.cacheContentViews(mContext, null);
- } catch (RuntimeException e) {
- Log.e(TAG, "Unable to get notification remote views", e);
- return false;
- }
-
- final RemoteViews contentView = entry.cachedContentView;
- final RemoteViews bigContentView = entry.cachedBigContentView;
- final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
- final RemoteViews publicContentView = entry.cachedPublicContentView;
- final RemoteViews ambientContentView = entry.cachedAmbientContentView;
-
- if (contentView == null) {
- Log.v(TAG, "no contentView for: " + sbn.getNotification());
- return false;
- }
-
- if (DEBUG) {
- Log.v(TAG, "publicContentView: " + publicContentView);
- }
-
- ExpandableNotificationRow row;
-
- // Stash away previous user expansion state so we can restore it at
- // the end.
- boolean hasUserChangedExpansion = false;
- boolean userExpanded = false;
- boolean userLocked = false;
-
- if (entry.row != null) {
- row = entry.row;
- hasUserChangedExpansion = row.hasUserChangedExpansion();
- userExpanded = row.isUserExpanded();
- userLocked = row.isUserLocked();
- entry.reset();
- if (hasUserChangedExpansion) {
- row.setUserExpanded(userExpanded);
- }
- } else {
- // create the row view
- LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
- parent, false);
- row.setExpansionLogger(this, entry.notification.getKey());
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setRemoteInputController(mRemoteInputController);
- row.setOnExpandClickListener(this);
-
- // Get the app name.
- // Note that Notification.Builder#bindHeaderAppName has similar logic
- // but since this field is used in the guts, it must be accurate.
- // Therefore we will only show the application label, or, failing that, the
- // package name. No substitutions.
- final String pkg = sbn.getPackageName();
- String appname = pkg;
- try {
- final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS);
- if (info != null) {
- appname = String.valueOf(pmUser.getApplicationLabel(info));
- }
- } catch (NameNotFoundException e) {
- // Do nothing
- }
- row.setAppName(appname);
- }
-
- workAroundBadLayerDrawableOpacity(row);
- bindDismissRunnable(row);
-
- // NB: the large icon is now handled entirely by the template
-
- // bind the click event to the content area
- NotificationContentView contentContainer = row.getPrivateLayout();
- NotificationContentView contentContainerPublic = row.getPublicLayout();
-
- row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- if (ENABLE_REMOTE_INPUT) {
- row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
- }
-
- mNotificationClicker.register(row, sbn);
-
- // set up the adaptive layout
- View contentViewLocal = null;
- View bigContentViewLocal = null;
- View headsUpContentViewLocal = null;
- View publicViewLocal = null;
- View ambientViewLocal = null;
- try {
- contentViewLocal = contentView.apply(
- sbn.getPackageContext(mContext),
- contentContainer,
- mOnClickHandler);
- if (bigContentView != null) {
- bigContentViewLocal = bigContentView.apply(
- sbn.getPackageContext(mContext),
- contentContainer,
- mOnClickHandler);
- }
- if (headsUpContentView != null) {
- headsUpContentViewLocal = headsUpContentView.apply(
- sbn.getPackageContext(mContext),
- contentContainer,
- mOnClickHandler);
- }
- if (publicContentView != null) {
- publicViewLocal = publicContentView.apply(
- sbn.getPackageContext(mContext),
- contentContainerPublic, mOnClickHandler);
- }
- if (ambientContentView != null) {
- ambientViewLocal = ambientContentView.apply(
- sbn.getPackageContext(mContext),
- contentContainer, mOnClickHandler);
- }
-
- if (contentViewLocal != null) {
- contentViewLocal.setIsRootNamespace(true);
- contentContainer.setContractedChild(contentViewLocal);
- }
- if (bigContentViewLocal != null) {
- bigContentViewLocal.setIsRootNamespace(true);
- contentContainer.setExpandedChild(bigContentViewLocal);
- }
- if (headsUpContentViewLocal != null) {
- headsUpContentViewLocal.setIsRootNamespace(true);
- contentContainer.setHeadsUpChild(headsUpContentViewLocal);
- }
- if (publicViewLocal != null) {
- publicViewLocal.setIsRootNamespace(true);
- contentContainerPublic.setContractedChild(publicViewLocal);
- }
-
- if (ambientViewLocal != null) {
- ambientViewLocal.setIsRootNamespace(true);
- contentContainer.setAmbientChild(ambientViewLocal);
- }
- }
- catch (RuntimeException e) {
- final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
- Log.e(TAG, "couldn't inflate view for notification " + ident, e);
- return false;
- }
-
- // Extract target SDK version.
- try {
- ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
- entry.targetSdk = info.targetSdkVersion;
- } catch (NameNotFoundException ex) {
- Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
- }
- entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
-
- entry.row = row;
- entry.row.setOnActivatedListener(this);
- entry.row.setExpandable(bigContentViewLocal != null);
-
- applyColorsAndBackgrounds(sbn, entry);
-
- // Restore previous flags.
- if (hasUserChangedExpansion) {
- // Note: setUserExpanded() conveniently ignores calls with
- // userExpanded=true if !isExpandable().
- row.setUserExpanded(userExpanded);
- }
- row.setUserLocked(userLocked);
- row.onNotificationUpdated(entry);
- return true;
- }
-
- /**
- * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
- * via first-class API.
- *
- * TODO: Remove once enough apps specify remote inputs on their own.
- */
- private void processForRemoteInput(Notification n) {
- if (!ENABLE_REMOTE_INPUT) return;
-
- if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
- (n.actions == null || n.actions.length == 0)) {
- Notification.Action viableAction = null;
- Notification.WearableExtender we = new Notification.WearableExtender(n);
-
- List<Notification.Action> actions = we.getActions();
- final int numActions = actions.size();
-
- for (int i = 0; i < numActions; i++) {
- Notification.Action action = actions.get(i);
- if (action == null) {
- continue;
- }
- RemoteInput[] remoteInputs = action.getRemoteInputs();
- if (remoteInputs == null) {
- continue;
- }
- for (RemoteInput ri : remoteInputs) {
- if (ri.getAllowFreeFormInput()) {
- viableAction = action;
- break;
- }
- }
- if (viableAction != null) {
- break;
- }
- }
-
- if (viableAction != null) {
- Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
- rebuilder.setActions(viableAction);
- rebuilder.build(); // will rewrite n
- }
- }
- }
-
- public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
- if (!isDeviceProvisioned()) return;
-
- final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
- final boolean afterKeyguardGone = intent.isActivity()
- && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
- mCurrentUserId);
- dismissKeyguardThenExecute(new OnDismissAction() {
- public boolean onDismiss() {
- new Thread() {
- @Override
- public void run() {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- try {
- intent.send(null, 0, null, null, null, null, getActivityOptions());
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending intent failed: " + e);
-
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity()) {
- mAssistManager.hideAssist();
- }
- }
- }.start();
-
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed */);
- visibilityChanged(false);
-
- return true;
- }
- }, afterKeyguardGone);
- }
-
- public void addPostCollapseAction(Runnable r) {
- }
-
- public boolean isCollapsing() {
- return false;
- }
-
- private final class NotificationClicker implements View.OnClickListener {
- 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;
- }
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- final StatusBarNotification sbn = row.getStatusBarNotification();
- if (sbn == null) {
- Log.e(TAG, "NotificationClicker called on an unclickable notification,");
- return;
- }
-
- // Check if the notification is displaying the gear, if so slide notification back
- if (row.getSettingsRow() != null && row.getSettingsRow().isVisible()) {
- row.animateTranslateNotification(0);
- return;
- }
-
- Notification notification = sbn.getNotification();
- final PendingIntent intent = notification.contentIntent != null
- ? notification.contentIntent
- : notification.fullScreenIntent;
- final String notificationKey = sbn.getKey();
-
- // Mark notification for one frame.
- row.setJustClicked(true);
- DejankUtils.postAfterTraversal(new Runnable() {
- @Override
- public void run() {
- row.setJustClicked(false);
- }
- });
-
- final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
- final boolean afterKeyguardGone = intent.isActivity()
- && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
- mCurrentUserId);
- dismissKeyguardThenExecute(new OnDismissAction() {
- public boolean onDismiss() {
- if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
- // Release the HUN notification to the shade.
-
- if (isPanelFullyCollapsed()) {
- HeadsUpManager.setIsClickedNotification(row, true);
- }
- //
- // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
- // become canceled shortly by NoMan, but we can't assume that.
- mHeadsUpManager.releaseImmediately(notificationKey);
- }
- StatusBarNotification parentToCancel = null;
- if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
- StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn)
- .getStatusBarNotification();
- if (shouldAutoCancel(summarySbn)) {
- parentToCancel = summarySbn;
- }
- }
- final StatusBarNotification parentToCancelFinal = parentToCancel;
- new Thread() {
- @Override
- public void run() {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- if (intent != null) {
- // If we are launching a work activity and require to launch
- // separate work challenge, we defer the activity action and cancel
- // notification until work challenge is unlocked.
- if (intent.isActivity()) {
- final int userId = intent.getCreatorUserHandle()
- .getIdentifier();
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
- && mKeyguardManager.isDeviceLocked(userId)) {
- boolean canBypass = false;
- try {
- canBypass = ActivityManager.getService()
- .canBypassWorkChallenge(intent);
- } catch (RemoteException e) {
- }
- // For direct-boot aware activities, they can be shown when
- // the device is still locked without triggering the work
- // challenge.
- if ((!canBypass) && startWorkChallengeIfNecessary(userId,
- intent.getIntentSender(), notificationKey)) {
- // Show work challenge, do not run PendingIntent and
- // remove notification
- return;
- }
- }
- }
- try {
- intent.send(null, 0, null, null, null, null,
- getActivityOptions());
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending contentIntent failed: " + e);
-
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity()) {
- mAssistManager.hideAssist();
- }
- }
-
- try {
- mBarService.onNotificationClick(notificationKey);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- if (parentToCancelFinal != null) {
- // We have to post it to the UI thread for synchronization
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Runnable removeRunnable = new Runnable() {
- @Override
- public void run() {
- performRemoveNotification(parentToCancelFinal);
- }
- };
- if (isCollapsing()) {
- // To avoid lags we're only performing the remove
- // after the shade was collapsed
- addPostCollapseAction(removeRunnable);
- } else {
- removeRunnable.run();
- }
- }
- });
- }
- }
- }.start();
-
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed */);
- visibilityChanged(false);
-
- return true;
- }
- }, afterKeyguardGone);
- }
-
- private boolean shouldAutoCancel(StatusBarNotification sbn) {
- int flags = sbn.getNotification().flags;
- if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
- return false;
- }
- if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- return false;
- }
- return true;
- }
-
- public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
- Notification notification = sbn.getNotification();
- if (notification.contentIntent != null || notification.fullScreenIntent != null) {
- row.setOnClickListener(this);
- } else {
- row.setOnClickListener(null);
- }
- }
- }
-
- public void animateCollapsePanels(int flags, boolean force) {
- }
-
- public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
- }
-
- protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender,
- String notificationKey) {
- final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null,
- null, userId);
- if (newIntent == null) {
- return false;
- }
- final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
- callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender);
- callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey);
- callBackIntent.setPackage(mContext.getPackageName());
-
- PendingIntent callBackPendingIntent = PendingIntent.getBroadcast(
- mContext,
- 0,
- callBackIntent,
- PendingIntent.FLAG_CANCEL_CURRENT |
- PendingIntent.FLAG_ONE_SHOT |
- PendingIntent.FLAG_IMMUTABLE);
- newIntent.putExtra(
- Intent.EXTRA_INTENT,
- callBackPendingIntent.getIntentSender());
- try {
- ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent);
- } catch (RemoteException ex) {
- // ignore
- }
- return true;
- }
-
- protected Bundle getActivityOptions() {
- // Anything launched from the notification shade should always go into the
- // fullscreen stack.
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID);
- return options.toBundle();
- }
-
- protected void visibilityChanged(boolean visible) {
- if (mVisible != visible) {
- mVisible = visible;
- if (!visible) {
- dismissPopups();
- }
- }
- updateVisibleToUser();
- }
-
- protected void updateVisibleToUser() {
- boolean oldVisibleToUser = mVisibleToUser;
- mVisibleToUser = mVisible && mDeviceInteractive;
-
- if (oldVisibleToUser != mVisibleToUser) {
- handleVisibleToUserChanged(mVisibleToUser);
- }
- }
-
- /**
- * The LEDs are turned off when the notification panel is shown, even just a little bit.
- * See also PhoneStatusBar.setPanelExpanded for another place where we attempt to do this.
- */
- protected void handleVisibleToUserChanged(boolean visibleToUser) {
- try {
- if (visibleToUser) {
- boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
- boolean clearNotificationEffects =
- !isPanelFullyCollapsed() &&
- (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationData.getActiveNotifications().size();
- if (pinnedHeadsUp && isPanelFullyCollapsed()) {
- notificationLoad = 1;
- } else {
- MetricsLogger.histogram(mContext, "note_load", notificationLoad);
- }
- mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
- } else {
- mBarService.onPanelHidden();
- }
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- }
-
- /**
- * Clear Buzz/Beep/Blink.
- */
- public void clearNotificationEffects() {
- try {
- mBarService.clearNotificationEffects();
- } catch (RemoteException e) {
- // Won't fail unless the world has ended.
- }
- }
-
- public abstract boolean isPanelFullyCollapsed();
-
- /**
- * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
- * about the failure.
- *
- * WARNING: this will call back into us. Don't hold any locks.
- */
- void handleNotificationError(StatusBarNotification n, String message) {
- removeNotification(n.getKey(), null);
- try {
- mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
- n.getInitialPid(), message, n.getUserId());
- } catch (RemoteException ex) {
- // The end is nigh.
- }
- }
-
- protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
- NotificationData.Entry entry = mNotificationData.remove(key, ranking);
- if (entry == null) {
- Log.w(TAG, "removeNotification for unknown key: " + key);
- return null;
- }
- updateNotifications();
- return entry.notification;
- }
-
- protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
- if (DEBUG) {
- Log.d(TAG, "createNotificationViews(notification=" + sbn);
- }
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
- try {
- entry.createIcons(mContext, sbn);
- } catch (NotificationData.IconException exception) {
- handleNotificationError(sbn, exception.getMessage());
- }
-
- // Construct the expanded view.
- if (!inflateViews(entry, mStackScroller)) {
- handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
- return null;
- }
- return entry;
- }
-
- protected void addNotificationViews(Entry entry, RankingMap ranking) {
- if (entry == null) {
- return;
- }
- // Add the expanded view and icon.
- mNotificationData.add(entry, ranking);
- updateNotifications();
- }
-
- /**
- * @param recompute wheter the number should be recomputed
- * @return The number of notifications we show on Keyguard.
- */
- protected abstract int getMaxKeyguardNotifications(boolean recompute);
-
- /**
- * Updates expanded, dimmed and locked states of notification rows.
- */
- protected void updateRowStates() {
- final int N = mStackScroller.getChildCount();
-
- int visibleNotifications = 0;
- boolean onKeyguard = mState == StatusBarState.KEYGUARD;
- int maxNotifications = -1;
- if (onKeyguard) {
- maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
- }
- mStackScroller.setMaxDisplayedNotifications(maxNotifications);
- Stack<ExpandableNotificationRow> stack = new Stack<>();
- for (int i = N - 1; i >= 0; i--) {
- View child = mStackScroller.getChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- stack.push((ExpandableNotificationRow) child);
- }
- while(!stack.isEmpty()) {
- ExpandableNotificationRow row = stack.pop();
- NotificationData.Entry entry = row.getEntry();
- boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification);
- if (onKeyguard) {
- row.setOnKeyguard(true);
- } else {
- row.setOnKeyguard(false);
- row.setSystemExpanded(visibleNotifications == 0 && !childNotification);
- }
- entry.row.setShowAmbient(isDozing());
- int userId = entry.notification.getUserId();
- boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
- entry.notification) && !entry.row.isRemoved();
- boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
- if (suppressedSummary
- || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications)
- || (onKeyguard && !showOnKeyguard)) {
- entry.row.setVisibility(View.GONE);
- } else {
- boolean wasGone = entry.row.getVisibility() == View.GONE;
- if (wasGone) {
- entry.row.setVisibility(View.VISIBLE);
- }
- if (!childNotification && !entry.row.isRemoved()) {
- if (wasGone) {
- // notify the scroller of a child addition
- mStackScroller.generateAddAnimation(entry.row,
- !showOnKeyguard /* fromMoreCard */);
- }
- visibleNotifications++;
- }
- }
- if (row.isSummaryWithChildren()) {
- List<ExpandableNotificationRow> notificationChildren =
- row.getNotificationChildren();
- int size = notificationChildren.size();
- for (int i = size - 1; i >= 0; i--) {
- stack.push(notificationChildren.get(i));
- }
- }
- }
-
- mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
- mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
- mStackScroller.changeViewPosition(mNotificationShelf, mStackScroller.getChildCount() - 3);
- }
-
- public boolean isDozing() {
- return false;
- }
-
- public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
- return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
- }
-
- protected void setZenMode(int mode) {
- if (!isDeviceProvisioned()) return;
- mZenMode = mode;
- updateNotifications();
- }
-
- // extended in PhoneStatusBar
- protected void setShowLockscreenNotifications(boolean show) {
- mShowLockscreenNotifications = show;
- }
-
- protected void setLockScreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
- mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
- }
-
- private void updateLockscreenNotificationSetting() {
- final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- 1,
- mCurrentUserId) != 0;
- final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
- null /* admin */, mCurrentUserId);
- final boolean allowedByDpm = (dpmFlags
- & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
-
- setShowLockscreenNotifications(show && allowedByDpm);
-
- if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
- final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
- 0,
- mCurrentUserId) != 0;
- final boolean remoteInputDpm =
- (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
-
- setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm);
- } else {
- setLockScreenAllowRemoteInput(false);
- }
- }
-
- protected abstract void setAreThereNotifications();
- protected abstract void updateNotifications();
-
- public abstract void addNotification(StatusBarNotification notification,
- RankingMap ranking, Entry oldEntry);
- protected abstract void updateNotificationRanking(RankingMap ranking);
- public abstract void removeNotification(String key, RankingMap ranking);
-
- public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
- if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
- final String key = notification.getKey();
- Entry entry = mNotificationData.get(key);
- if (entry == null) {
- return;
- } else {
- mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
- mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
- }
-
- Notification n = notification.getNotification();
- mNotificationData.updateRanking(ranking);
-
- boolean applyInPlace;
- try {
- applyInPlace = entry.cacheContentViews(mContext, notification.getNotification());
- } catch (RuntimeException e) {
- Log.e(TAG, "Unable to get notification remote views", e);
- applyInPlace = false;
- }
- boolean shouldPeek = shouldPeek(entry, notification);
- boolean alertAgain = alertAgain(entry, n);
- if (DEBUG) {
- Log.d(TAG, "applyInPlace=" + applyInPlace
- + " shouldPeek=" + shouldPeek
- + " alertAgain=" + alertAgain);
- }
-
- final StatusBarNotification oldNotification = entry.notification;
- entry.notification = notification;
- mGroupManager.onEntryUpdated(entry, oldNotification);
-
- boolean updateSuccessful = false;
- try {
- if (applyInPlace) {
- if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
- try {
- entry.updateIcons(mContext, n);
- updateNotificationViews(entry, notification);
- updateSuccessful = true;
- } catch (RuntimeException e) {
- // It failed to apply cleanly.
- Log.w(TAG, "Couldn't reapply views for package " +
- notification.getPackageName(), e);
- }
- }
- if (!updateSuccessful) {
- entry.updateIcons(mContext, n);
- if (!inflateViews(entry, mStackScroller)) {
- handleNotificationError(notification, "Couldn't update remote views for: "
- + notification);
- }
- }
- } catch (NotificationData.IconException e) {
- handleNotificationError(notification, e.getMessage());
- }
- updateHeadsUp(key, entry, shouldPeek, alertAgain);
- updateNotifications();
-
- if (!notification.isClearable()) {
- // The user may have performed a dismiss action on the notification, since it's
- // not clearable we should snap it back.
- mStackScroller.snapViewIfNeeded(entry.row);
- }
-
- if (DEBUG) {
- // Is this for you?
- boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
- Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
- }
-
- setAreThereNotifications();
- }
-
- protected abstract void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
- boolean alertAgain);
-
- private void updateNotificationViews(Entry entry, StatusBarNotification sbn) {
- final RemoteViews contentView = entry.cachedContentView;
- final RemoteViews bigContentView = entry.cachedBigContentView;
- final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
- final RemoteViews publicContentView = entry.cachedPublicContentView;
-
- // Reapply the RemoteViews
- contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
- if (bigContentView != null && entry.getExpandedContentView() != null) {
- bigContentView.reapply(sbn.getPackageContext(mContext),
- entry.getExpandedContentView(),
- mOnClickHandler);
- }
- View headsUpChild = entry.getHeadsUpContentView();
- if (headsUpContentView != null && headsUpChild != null) {
- headsUpContentView.reapply(sbn.getPackageContext(mContext),
- headsUpChild, mOnClickHandler);
- }
- if (publicContentView != null && entry.getPublicContentView() != null) {
- publicContentView.reapply(sbn.getPackageContext(mContext),
- entry.getPublicContentView(), mOnClickHandler);
- }
- // update the contentIntent
- mNotificationClicker.register(entry.row, sbn);
-
- entry.row.onNotificationUpdated(entry);
- entry.row.resetHeight();
- }
-
- protected void updatePublicContentView(Entry entry,
- StatusBarNotification sbn) {
- final RemoteViews publicContentView = entry.cachedPublicContentView;
- View inflatedView = entry.getPublicContentView();
- if (entry.autoRedacted && publicContentView != null && inflatedView != null) {
- final boolean disabledByPolicy =
- !adminAllowsUnredactedNotifications(entry.notification.getUserId());
- String notificationHiddenText = mContext.getString(disabledByPolicy
- ? com.android.internal.R.string.notification_hidden_by_policy_text
- : com.android.internal.R.string.notification_hidden_text);
- TextView titleView = (TextView) inflatedView.findViewById(android.R.id.title);
- if (titleView != null
- && !titleView.getText().toString().equals(notificationHiddenText)) {
- publicContentView.setTextViewText(android.R.id.title, notificationHiddenText);
- publicContentView.reapply(sbn.getPackageContext(mContext),
- inflatedView, mOnClickHandler);
- entry.row.onNotificationUpdated(entry);
- }
- }
- }
-
- protected void notifyHeadsUpScreenOff() {
- maybeEscalateHeadsUp();
- }
-
- private boolean alertAgain(Entry oldEntry, Notification newNotification) {
- return oldEntry == null || !oldEntry.hasInterrupted()
- || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
- }
-
- protected boolean shouldPeek(Entry entry) {
- return shouldPeek(entry, entry.notification);
- }
-
- protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
- if (!mUseHeadsUp || isDeviceInVrMode()) {
- return false;
- }
-
- if (mNotificationData.shouldFilterOut(sbn)) {
- if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
- return false;
- }
-
- boolean inUse = mPowerManager.isScreenOn();
- try {
- inUse = inUse && !mDreamManager.isDreaming();
- } catch (RemoteException e) {
- Log.d(TAG, "failed to query dream manager", e);
- }
-
- if (!inUse && !isDozing()) {
- if (DEBUG) {
- Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
- }
- return false;
- }
-
- if (mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
- return false;
- }
-
- if (entry.hasJustLaunchedFullScreenIntent()) {
- if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
- return false;
- }
-
- if (isSnoozedPackage(sbn)) {
- if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
- return false;
- }
-
- if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) {
- if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
- return false;
- }
-
- if (sbn.getNotification().fullScreenIntent != null) {
- if (mAccessibilityManager.isTouchExplorationEnabled()) {
- if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
- return false;
- } else {
- // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
- return !mStatusBarKeyguardViewManager.isShowing()
- || mStatusBarKeyguardViewManager.isOccluded();
- }
- }
-
- return true;
- }
-
- protected abstract boolean isSnoozedPackage(StatusBarNotification sbn);
-
- public void setInteracting(int barWindow, boolean interacting) {
- // hook for subclasses
- }
-
- public void setBouncerShowing(boolean bouncerShowing) {
- mBouncerShowing = bouncerShowing;
- }
-
- /**
- * @return Whether the security bouncer from Keyguard is showing.
- */
- public boolean isBouncerShowing() {
- return mBouncerShowing;
- }
-
- public void destroy() {
- mContext.unregisterReceiver(mBroadcastReceiver);
- try {
- mNotificationListener.unregisterAsSystemService();
- } catch (RemoteException e) {
- // Ignore.
- }
- }
-
- /**
- * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then
- * return PackageManager for mContext
- */
- public static PackageManager getPackageManagerForUser(Context context, int userId) {
- Context contextForUser = context;
- // UserHandle defines special userId as negative values, e.g. USER_ALL
- if (userId >= 0) {
- try {
- // Create a context for the correct user so if a package isn't installed
- // for user 0 we can still load information about the package.
- contextForUser =
- context.createPackageContextAsUser(context.getPackageName(),
- Context.CONTEXT_RESTRICTED,
- new UserHandle(userId));
- } catch (NameNotFoundException e) {
- // Shouldn't fail to find the package name for system ui.
- }
- }
- return contextForUser.getPackageManager();
- }
-
- @Override
- public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- try {
- mBarService.onNotificationExpansionChanged(key, userAction, expanded);
- } catch (RemoteException e) {
- // Ignore.
- }
- }
-
- public boolean isKeyguardSecure() {
- if (mStatusBarKeyguardViewManager == null) {
- // startKeyguard() hasn't been called yet, so we don't know.
- // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this
- // value onVisibilityChanged().
- Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false",
- new Throwable());
- return false;
- }
- return mStatusBarKeyguardViewManager.isSecure();
- }
-
- @Override
- public void showAssistDisclosure() {
- if (mAssistManager != null) {
- mAssistManager.showDisclosure();
- }
- }
-
- @Override
- public void startAssist(Bundle args) {
- if (mAssistManager != null) {
- mAssistManager.startAssist(args);
- }
- }
-}
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/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 93c48f8..d1245b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -22,6 +22,7 @@
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -50,9 +51,12 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.notification.HybridNotificationView;
+import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.stack.AnimationProperties;
import com.android.systemui.statusbar.stack.ExpandableViewState;
@@ -118,7 +122,7 @@
private int mNotificationColor;
private ExpansionLogger mLogger;
private String mLoggingKey;
- private NotificationSettingsIconRow mSettingsIconRow;
+ private NotificationMenuRow mMenuRow;
private NotificationGuts mGuts;
private NotificationData.Entry mEntry;
private StatusBarNotification mStatusBarNotification;
@@ -130,7 +134,7 @@
private boolean mChildrenExpanded;
private boolean mIsSummaryWithChildren;
private NotificationChildrenContainer mChildrenContainer;
- private ViewStub mSettingsIconRowStub;
+ private ViewStub mMenuRowStub;
private ViewStub mGutsStub;
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
@@ -201,7 +205,10 @@
private boolean mShowAmbient;
private boolean mIsLastChild;
private Runnable mOnDismissRunnable;
+ private boolean mIsLowPriority;
+ private boolean mIsColorized;
+ @Override
public boolean isGroupExpansionChanging() {
if (isChildInGroup()) {
return mNotificationParent.isGroupExpansionChanging();
@@ -292,6 +299,7 @@
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(entry);
}
+ mIsColorized = mStatusBarNotification.getNotification().isColorized();
mShowingPublicInitialized = false;
updateNotificationColor();
if (mIsSummaryWithChildren) {
@@ -309,6 +317,18 @@
mPublicLayout.updateExpandButtons(true);
updateLimits();
updateIconVisibilities();
+ updateShelfIconColor();
+ }
+
+ private void updateShelfIconColor() {
+ StatusBarIconView expandedIcon = mEntry.expandedIcon;
+ boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
+ boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
+ NotificationColorUtil.getInstance(mContext));
+ if (colorize) {
+ int color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded());
+ expandedIcon.setImageTintList(ColorStateList.valueOf(color));
+ }
}
private void updateLimits() {
@@ -371,8 +391,8 @@
public void setAppName(String appName) {
mAppName = appName;
- if (mSettingsIconRow != null) {
- mSettingsIconRow.setAppName(mAppName);
+ if (mMenuRow != null) {
+ mMenuRow.setAppName(mAppName);
}
}
@@ -403,6 +423,7 @@
row.setIsChildInGroup(false, null);
}
+ @Override
public boolean isChildInGroup() {
return mNotificationParent != null;
}
@@ -416,7 +437,7 @@
* @param parent the new parent notification
*/
public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {;
- boolean childInGroup = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
+ boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
mNotificationParent = childInGroup ? parent : null;
mPrivateLayout.setIsChildInGroup(childInGroup);
resetBackgroundAlpha();
@@ -440,7 +461,7 @@
@Override
protected boolean handleSlideBack() {
- if (mSettingsIconRow != null && mSettingsIconRow.isVisible()) {
+ if (mMenuRow != null && mMenuRow.isVisible()) {
animateTranslateNotification(0 /* targetLeft */);
return true;
}
@@ -664,6 +685,13 @@
mHeadsUpManager = headsUpManager;
}
+ public void setGutsView(MenuItem item) {
+ if (mGuts != null) {
+ item.gutsContent.setInteractionListener(mGuts);
+ mGuts.setGutsContent(item.gutsContent);
+ }
+ }
+
public void reInflateViews() {
initDimens();
if (mIsSummaryWithChildren) {
@@ -680,16 +708,16 @@
mGuts.setVisibility(oldGuts.getVisibility());
addView(mGuts, index);
}
- if (mSettingsIconRow != null) {
- View oldSettings = mSettingsIconRow;
- int settingsIndex = indexOfChild(oldSettings);
- removeView(oldSettings);
- mSettingsIconRow = (NotificationSettingsIconRow) LayoutInflater.from(mContext).inflate(
- R.layout.notification_settings_icon_row, this, false);
- mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
- mSettingsIconRow.setAppName(mAppName);
- mSettingsIconRow.setVisibility(oldSettings.getVisibility());
- addView(mSettingsIconRow, settingsIndex);
+ if (mMenuRow != null) {
+ View oldMenu = mMenuRow;
+ int menuIndex = indexOfChild(oldMenu);
+ removeView(oldMenu);
+ mMenuRow = (NotificationMenuRow) LayoutInflater.from(mContext).inflate(
+ R.layout.notification_menu_row, this, false);
+ mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
+ mMenuRow.setAppName(mAppName);
+ mMenuRow.setVisibility(oldMenu.getVisibility());
+ addView(mMenuRow, menuIndex);
}
for (NotificationContentView l : mLayouts) {
@@ -943,6 +971,14 @@
return mPrivateLayout.getTranslationY();
}
+ public void setIsLowPriority(boolean isLowPriority) {
+ mIsLowPriority = isLowPriority;
+ mPrivateLayout.setIsLowPriority(isLowPriority);
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setIsLowPriority(isLowPriority);
+ }
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -1010,21 +1046,19 @@
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
-
mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
for (NotificationContentView l : mLayouts) {
l.setExpandClickListener(mExpandClickListener);
l.setContainingNotification(this);
}
-
- mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub);
- mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+ mMenuRowStub = (ViewStub) findViewById(R.id.menu_row_stub);
+ mMenuRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
- mSettingsIconRow = (NotificationSettingsIconRow) inflated;
- mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
- mSettingsIconRow.setAppName(mAppName);
+ mMenuRow = (NotificationMenuRow) inflated;
+ mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
+ mMenuRow.setAppName(mAppName);
}
});
mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
@@ -1043,6 +1077,7 @@
@Override
public void onInflate(ViewStub stub, View inflated) {
mChildrenContainer = (NotificationChildrenContainer) inflated;
+ mChildrenContainer.setIsLowPriority(mIsLowPriority);
mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this);
mChildrenContainer.onNotificationUpdated();
mTranslateableViews.add(mChildrenContainer);
@@ -1055,7 +1090,7 @@
mTranslateableViews.add(getChildAt(i));
}
// Remove views that don't translate
- mTranslateableViews.remove(mSettingsIconRowStub);
+ mTranslateableViews.remove(mMenuRowStub);
mTranslateableViews.remove(mChildrenContainerStub);
mTranslateableViews.remove(mGutsStub);
}
@@ -1070,8 +1105,8 @@
}
}
invalidateOutline();
- if (mSettingsIconRow != null) {
- mSettingsIconRow.resetState();
+ if (mMenuRow != null) {
+ mMenuRow.resetState(true /* notify */);
}
}
@@ -1098,8 +1133,8 @@
}
}
invalidateOutline();
- if (mSettingsIconRow != null) {
- mSettingsIconRow.updateSettingsIcons(translationX, getMeasuredWidth());
+ if (mMenuRow != null) {
+ mMenuRow.updateMenuAlpha(translationX, getMeasuredWidth());
}
}
@@ -1136,8 +1171,8 @@
@Override
public void onAnimationEnd(Animator anim) {
- if (!cancelled && mSettingsIconRow != null && leftTarget == 0) {
- mSettingsIconRow.resetState();
+ if (!cancelled && mMenuRow != null && leftTarget == 0) {
+ mMenuRow.resetState(true /* notify */);
mTranslateAnim = null;
}
}
@@ -1147,17 +1182,17 @@
}
public float getSpaceForGear() {
- if (mSettingsIconRow != null) {
- return mSettingsIconRow.getSpaceForGear();
+ if (mMenuRow != null) {
+ return mMenuRow.getSpaceForMenu();
}
return 0;
}
- public NotificationSettingsIconRow getSettingsRow() {
- if (mSettingsIconRow == null) {
- mSettingsIconRowStub.inflate();
+ public NotificationMenuRow getSettingsRow() {
+ if (mMenuRow == null) {
+ mMenuRowStub.inflate();
}
- return mSettingsIconRow;
+ return mMenuRow;
}
public void inflateGuts() {
@@ -1242,6 +1277,7 @@
*/
public void setUserExpanded(boolean userExpanded) {
setUserExpanded(userExpanded, false /* allowChildExpansion */);
+ updateShelfIconColor();
}
/**
@@ -1301,6 +1337,7 @@
if (expand != mIsSystemExpanded) {
final boolean wasExpanded = isExpanded();
mIsSystemExpanded = expand;
+ updateShelfIconColor();
notifyHeightChanged(false /* needsAnimation */);
logExpansionEvent(false, wasExpanded);
if (mIsSummaryWithChildren) {
@@ -1378,12 +1415,13 @@
}
}
+ @Override
public boolean isGroupExpanded() {
return mGroupManager.isGroupExpanded(mStatusBarNotification);
}
private void onChildrenCountChanged() {
- mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+ mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS
&& mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
@@ -1431,8 +1469,8 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateMaxHeights();
- if (mSettingsIconRow != null) {
- mSettingsIconRow.updateVerticalLocation();
+ if (mMenuRow != null) {
+ mMenuRow.updateVerticalLocation();
}
updateContentShiftHeight();
}
@@ -1479,6 +1517,7 @@
mSensitiveHiddenInGeneral = hideSensitive;
}
+ @Override
public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
mHideSensitiveForIntrinsicHeight = hideSensitive;
if (mIsSummaryWithChildren) {
@@ -1491,6 +1530,7 @@
}
}
+ @Override
public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
long duration) {
boolean oldShowingPublic = mShowingPublic;
@@ -1555,6 +1595,7 @@
}
}
+ @Override
public boolean mustStayOnScreen() {
return mIsHeadsUp;
}
@@ -1827,10 +1868,16 @@
} else if (isUserLocked()) {
return mChildrenContainer.getGroupExpandFraction();
}
+ } else if (isColorized()) {
+ return -1.0f;
}
return 0.0f;
}
+ private boolean isColorized() {
+ return mIsColorized;
+ }
+
@Override
protected boolean disallowSingleClick(MotionEvent event) {
float x = event.getX();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 37a900e..6a1f470 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -404,7 +404,9 @@
}
/**
- * @return an amount between 0 and 1 of increased padding that this child needs
+ * @return an amount between -1 and 1 of increased padding that this child needs. 1 means it
+ * needs a full increased padding while -1 means it needs no padding at all. For 0.0f the normal
+ * padding is applied.
*/
public float getIncreasedPaddingAmount() {
return 0.0f;
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/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index b45cde8..a1299e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -129,6 +129,7 @@
private boolean mHeadsUpAnimatingAway;
private boolean mIconsVisible;
private int mClipBottomAmount;
+ private boolean mIsLowPriority;
public NotificationContentView(Context context, AttributeSet attrs) {
@@ -163,20 +164,31 @@
if (mExpandedChild != null) {
int size = Math.min(maxSize, mNotificationMaxHeight);
ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
+ boolean useExactly = false;
if (layoutParams.height >= 0) {
// An actual height is set
size = Math.min(maxSize, layoutParams.height);
+ useExactly = true;
}
int spec = size == Integer.MAX_VALUE
? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
- : MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+ : MeasureSpec.makeMeasureSpec(size, useExactly
+ ? MeasureSpec.EXACTLY
+ : MeasureSpec.AT_MOST);
mExpandedChild.measure(widthMeasureSpec, spec);
maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
}
if (mContractedChild != null) {
int heightSpec;
int size = Math.min(maxSize, mSmallHeight);
- if (shouldContractedBeFixedSize()) {
+ ViewGroup.LayoutParams layoutParams = mContractedChild.getLayoutParams();
+ boolean useExactly = false;
+ if (layoutParams.height >= 0) {
+ // An actual height is set
+ size = Math.min(size, layoutParams.height);
+ useExactly = true;
+ }
+ if (shouldContractedBeFixedSize() || useExactly) {
heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
} else {
heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
@@ -202,12 +214,15 @@
if (mHeadsUpChild != null) {
int size = Math.min(maxSize, mHeadsUpHeight);
ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
+ boolean useExactly = false;
if (layoutParams.height >= 0) {
// An actual height is set
size = Math.min(size, layoutParams.height);
+ useExactly = true;
}
mHeadsUpChild.measure(widthMeasureSpec,
- MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+ MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
+ : MeasureSpec.AT_MOST));
maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
}
if (mSingleLineView != null) {
@@ -225,12 +240,15 @@
if (mAmbientChild != null) {
int size = Math.min(maxSize, mNotificationAmbientHeight);
ViewGroup.LayoutParams layoutParams = mAmbientChild.getLayoutParams();
+ boolean useExactly = false;
if (layoutParams.height >= 0) {
// An actual height is set
size = Math.min(size, layoutParams.height);
+ useExactly = true;
}
mAmbientChild.measure(widthMeasureSpec,
- MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+ MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
+ : MeasureSpec.AT_MOST));
maxChildHeight = Math.max(maxChildHeight, mAmbientChild.getMeasuredHeight());
}
int ownHeight = Math.min(maxChildHeight, maxSize);
@@ -606,7 +624,7 @@
}
public int getMinHeight(boolean likeGroupExpanded) {
- if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
+ if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded() || mIsLowPriority) {
return mContractedChild.getHeight();
} else {
return mSingleLineView.getHeight();
@@ -868,7 +886,7 @@
height = mContentHeight;
}
int expandedVisualType = getVisualTypeForHeight(height);
- int collapsedVisualType = mIsChildInGroup && !isGroupExpanded()
+ int collapsedVisualType = mIsChildInGroup && !isGroupExpanded() && !mIsLowPriority
? VISIBLE_TYPE_SINGLELINE
: getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
return mTransformationStartVisibleType == collapsedVisualType
@@ -889,7 +907,7 @@
if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) {
return VISIBLE_TYPE_EXPANDED;
}
- if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
+ if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded() && !mIsLowPriority) {
return VISIBLE_TYPE_SINGLELINE;
}
@@ -974,19 +992,19 @@
mStatusBarNotification = entry.notification;
mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
updateSingleLineView();
- applyRemoteInput(entry);
if (mContractedChild != null) {
- mContractedWrapper.notifyContentUpdated(entry.notification);
+ mContractedWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
if (mExpandedChild != null) {
- mExpandedWrapper.notifyContentUpdated(entry.notification);
+ mExpandedWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
if (mHeadsUpChild != null) {
- mHeadsUpWrapper.notifyContentUpdated(entry.notification);
+ mHeadsUpWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
if (mAmbientChild != null) {
- mAmbientWrapper.notifyContentUpdated(entry.notification);
+ mAmbientWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
+ applyRemoteInput(entry);
updateShowingLegacyBackground();
mForceSelectNextLayout = true;
setDark(mDark, false /* animate */, 0 /* delay */);
@@ -1028,7 +1046,8 @@
View bigContentView = mExpandedChild;
if (bigContentView != null) {
mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
- mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput);
+ mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput,
+ mExpandedWrapper);
} else {
mExpandedRemoteInput = null;
}
@@ -1042,7 +1061,7 @@
View headsUpContentView = mHeadsUpChild;
if (headsUpContentView != null) {
mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput,
- mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput);
+ mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput, mHeadsUpWrapper);
} else {
mHeadsUpRemoteInput = null;
}
@@ -1056,7 +1075,7 @@
private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
boolean hasRemoteInput, PendingIntent existingPendingIntent,
- RemoteInputView cachedView) {
+ RemoteInputView cachedView, NotificationViewWrapper wrapper) {
View actionContainerCandidate = view.findViewById(
com.android.internal.R.id.actions_container);
if (actionContainerCandidate instanceof FrameLayout) {
@@ -1095,6 +1114,8 @@
mContext.getColor(R.color.remote_input_text_enabled),
mContext.getColor(R.color.remote_input_hint)));
+ existing.setWrapper(wrapper);
+
if (existingPendingIntent != null || existing.isActive()) {
// The current action could be gone, or the pending intent no longer valid.
// If we find a matching action in the new notification, focus, otherwise close.
@@ -1282,4 +1303,8 @@
}
}
}
+
+ public void setIsLowPriority(boolean isLowPriority) {
+ mIsLowPriority = isLowPriority;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 458daf1..bbfcf31 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;
@@ -24,6 +25,7 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.view.View;
@@ -33,12 +35,14 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
import java.util.Objects;
/**
@@ -55,6 +59,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
@@ -69,6 +74,7 @@
public RemoteViews cachedPublicContentView;
public RemoteViews cachedAmbientContentView;
public CharSequence remoteInputText;
+ public List<SnoozeCriterion> snoozeCriteria;
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
@@ -115,14 +121,16 @@
return row.getPublicLayout().getContractedChild();
}
- public boolean cacheContentViews(Context ctx, Notification updatedNotification) {
+ public boolean cacheContentViews(Context ctx, Notification updatedNotification,
+ boolean isLowPriority) {
boolean applyInPlace = false;
if (updatedNotification != null) {
final Notification.Builder updatedNotificationBuilder
= Notification.Builder.recoverBuilder(ctx, updatedNotification);
- final RemoteViews newContentView = updatedNotificationBuilder.createContentView();
- final RemoteViews newBigContentView =
- updatedNotificationBuilder.createBigContentView();
+ final RemoteViews newContentView = createContentView(updatedNotificationBuilder,
+ isLowPriority);
+ final RemoteViews newBigContentView = createBigContentView(
+ updatedNotificationBuilder, isLowPriority);
final RemoteViews newHeadsUpContentView =
updatedNotificationBuilder.createHeadsUpContentView();
final RemoteViews newPublicNotification
@@ -150,8 +158,8 @@
final Notification.Builder builder
= Notification.Builder.recoverBuilder(ctx, notification.getNotification());
- cachedContentView = builder.createContentView();
- cachedBigContentView = builder.createBigContentView();
+ cachedContentView = createContentView(builder, isLowPriority);
+ cachedBigContentView = createBigContentView(builder, isLowPriority);
cachedHeadsUpContentView = builder.createHeadsUpContentView();
cachedPublicContentView = builder.makePublicContentView();
cachedAmbientContentView = builder.makeAmbientNotification();
@@ -161,6 +169,28 @@
return applyInPlace;
}
+ private RemoteViews createBigContentView(Notification.Builder builder,
+ boolean isLowPriority) {
+ RemoteViews bigContentView = builder.createBigContentView();
+ if (bigContentView != null) {
+ return bigContentView;
+ }
+ if (isLowPriority) {
+ RemoteViews contentView = builder.createContentView();
+ Notification.Builder.makeHeaderExpanded(contentView);
+ return contentView;
+ }
+ return null;
+ }
+
+ private RemoteViews createContentView(Notification.Builder builder,
+ boolean isAmbient) {
+ if (isAmbient) {
+ return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
+ }
+ return builder.createContentView();
+ }
+
// Returns true if the RemoteViews are the same.
private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
return (a == null && b == null) ||
@@ -254,8 +284,9 @@
}
}
- public int getContrastedColor(Context context) {
- int rawColor = notification.getNotification().color;
+ public int getContrastedColor(Context context, boolean ambient) {
+ int rawColor = ambient ? Notification.COLOR_DEFAULT :
+ notification.getNotification().color;
if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
return mCachedContrastColor;
}
@@ -429,6 +460,22 @@
return null;
}
+ public List<SnoozeCriterion> getSnoozeCriteria(String key) {
+ if (mRankingMap != null) {
+ mRankingMap.getRanking(key, mTmpRanking);
+ return mTmpRanking.getSnoozeCriteria();
+ }
+ 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 +489,8 @@
entry.notification.setOverrideGroupKey(overrideGroupKey);
mGroupManager.onEntryUpdated(entry, oldSbn);
}
+ entry.channel = getChannel(entry.key);
+ entry.snoozeCriteria = getSnoozeCriteria(entry.key);
}
}
}
@@ -470,7 +519,7 @@
Collections.sort(mSortedAndFiltered, mRankingComparator);
}
- boolean shouldFilterOut(StatusBarNotification sbn) {
+ public boolean shouldFilterOut(StatusBarNotification sbn) {
if (!(mEnvironment.isDeviceProvisioned() ||
showNotificationEvenIfUnprovisioned(sbn))) {
return true;
@@ -487,7 +536,7 @@
return true;
}
- if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+ if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index c7adb60..f6056dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -40,6 +40,7 @@
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.CompoundButton;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
@@ -53,6 +54,8 @@
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.Set;
@@ -60,7 +63,8 @@
/**
* The guts of a notification revealed when performing a long press.
*/
-public class NotificationGuts extends LinearLayout {
+public class NotificationGuts extends FrameLayout
+ implements NotificationMenuRowProvider.GutsInteractionListener {
private static final String TAG = "NotificationGuts";
private static final long CLOSE_GUTS_DELAY = 8000;
@@ -69,28 +73,14 @@
private int mClipBottomAmount;
private int mActualHeight;
private boolean mExposed;
- private INotificationManager mINotificationManager;
- private int mStartingUserImportance;
- private StatusBarNotification mStatusBarNotification;
-
- private ImageView mAutoButton;
- private TextView mImportanceSummary;
- private TextView mImportanceTitle;
- private boolean mAuto;
-
- private View mImportanceGroup;
- private View mChannelDisabled;
- private Switch mChannelEnabledSwitch;
- private RadioButton mMinImportanceButton;
- private RadioButton mLowImportanceButton;
- private RadioButton mDefaultImportanceButton;
- private RadioButton mHighImportanceButton;
private Handler mHandler;
private Runnable mFalsingCheck;
private boolean mNeedsFalsingProtection;
private OnGutsClosedListener mListener;
+ private GutsContent mGutsContent;
+
public interface OnGutsClosedListener {
public void onGutsClosed(NotificationGuts guts);
}
@@ -107,11 +97,22 @@
}
}
};
- final TypedArray ta =
- context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Theme, 0, 0);
+ final TypedArray ta = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Theme, 0, 0);
ta.recycle();
}
+ public NotificationGuts(Context context) {
+ this(context, null);
+
+ }
+
+ public void setGutsContent(GutsContent content) {
+ mGutsContent = content;
+ removeAllViews();
+ addView(mGutsContent.getContentView());
+ }
+
public void resetFalsingCheck() {
mHandler.removeCallbacks(mFalsingCheck);
if (mNeedsFalsingProtection && mExposed) {
@@ -169,213 +170,23 @@
void onClick(View v, int appUid);
}
- void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
- final StatusBarNotification sbn, OnSettingsClickListener onSettingsClick,
- OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
- mINotificationManager = iNotificationManager;
- mStatusBarNotification = sbn;
- final NotificationChannel channel = sbn.getNotificationChannel();
- mStartingUserImportance = channel.getImportance();
-
- final String pkg = sbn.getPackageName();
- int appUid = -1;
- String appname = pkg;
- Drawable pkgicon = null;
- try {
- final ApplicationInfo info = pm.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
- appUid = info.uid;
- appname = String.valueOf(pm.getApplicationLabel(info));
- pkgicon = pm.getApplicationIcon(info);
- }
- } catch (PackageManager.NameNotFoundException e) {
- // app is gone, just show package name and generic icon
- pkgicon = pm.getDefaultActivityIcon();
- }
-
- // If this is the placeholder channel, don't use our channel-specific text.
- String appNameText;
- CharSequence channelNameText;
- if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- appNameText = appname;
- channelNameText = mContext.getString(R.string.notification_header_default_channel);
- } else {
- appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
- channelNameText = channel.getName();
- }
- ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
- ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
-
- // Settings button.
- final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
- if (appUid >= 0 && onSettingsClick != null) {
- final int appUidF = appUid;
- settingsButton.setOnClickListener(
- (View view) -> { onSettingsClick.onClick(view, appUidF); });
- settingsButton.setText(R.string.notification_more_settings);
- } else {
- settingsButton.setVisibility(View.GONE);
- }
-
- // Done button.
- final TextView doneButton = (TextView) findViewById(R.id.done);
- doneButton.setText(R.string.notification_done);
- doneButton.setOnClickListener(onDoneClick);
-
- boolean nonBlockable = false;
- try {
- final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
- nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
- } catch (PackageManager.NameNotFoundException e) {
- // unlikely.
- }
- if (nonBlockablePkgs != null) {
- nonBlockable |= nonBlockablePkgs.contains(pkg);
- }
-
- final View importanceButtons = findViewById(R.id.importance_buttons);
- bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
-
- // Importance Text (hardcoded to 4 importance levels)
- final ViewGroup importanceTextGroup =
- (ViewGroup) findViewById(R.id.importance_buttons_text);
- final int size = importanceTextGroup.getChildCount();
- for (int i = 0; i < size; i++) {
- int importanceNameResId = 0;
- int importanceDescResId = 0;
- switch (i) {
- case 0:
- importanceNameResId = R.string.high_importance;
- importanceDescResId = R.string.notification_importance_high;
- break;
- case 1:
- importanceNameResId = R.string.default_importance;
- importanceDescResId = R.string.notification_importance_default;
- break;
- case 2:
- importanceNameResId = R.string.low_importance;
- importanceDescResId = R.string.notification_importance_low;
- break;
- case 3:
- importanceNameResId = R.string.min_importance;
- importanceDescResId = R.string.notification_importance_min;
- break;
- default:
- Log.e(TAG, "Too many importance groups in this layout.");
- break;
- }
- final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
- ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
- ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
- }
-
- // Top-level importance group
- mImportanceGroup = findViewById(R.id.importance);
- mChannelDisabled = findViewById(R.id.channel_disabled);
- updateImportanceGroup();
- }
-
- public boolean hasImportanceChanged() {
- return mStartingUserImportance != getSelectedImportance();
- }
-
- private void saveImportance() {
- int selectedImportance = getSelectedImportance();
- if (selectedImportance == mStartingUserImportance) {
- return;
- }
- final NotificationChannel channel = mStatusBarNotification.getNotificationChannel();
- MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
- selectedImportance - mStartingUserImportance);
- channel.setImportance(selectedImportance);
- try {
- mINotificationManager.updateNotificationChannelForPackage(
- mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
- channel);
- } catch (RemoteException e) {
- // :(
- }
- }
-
- private int getSelectedImportance() {
- if (!mChannelEnabledSwitch.isChecked()) {
- return NotificationManager.IMPORTANCE_NONE;
- } else if (mMinImportanceButton.isChecked()) {
- return NotificationManager.IMPORTANCE_MIN;
- } else if (mLowImportanceButton.isChecked()) {
- return NotificationManager.IMPORTANCE_LOW;
- } else if (mDefaultImportanceButton.isChecked()) {
- return NotificationManager.IMPORTANCE_DEFAULT;
- } else if (mHighImportanceButton.isChecked()) {
- return NotificationManager.IMPORTANCE_HIGH;
- } else {
- return NotificationManager.IMPORTANCE_UNSPECIFIED;
- }
- }
-
- private void bindToggles(final View importanceButtons, final int importance,
- final boolean nonBlockable) {
- // Enabled Switch
- mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
- mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
- mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
-
- // Importance Buttons
- mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
- mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
- mDefaultImportanceButton =
- (RadioButton) importanceButtons.findViewById(R.id.default_importance);
- mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
-
- // Set to current importance setting
- switch (importance) {
- case NotificationManager.IMPORTANCE_UNSPECIFIED:
- case NotificationManager.IMPORTANCE_NONE:
- break;
- case NotificationManager.IMPORTANCE_MIN:
- mMinImportanceButton.setChecked(true);
- break;
- case NotificationManager.IMPORTANCE_LOW:
- mLowImportanceButton.setChecked(true);
- break;
- case NotificationManager.IMPORTANCE_DEFAULT:
- mDefaultImportanceButton.setChecked(true);
- break;
- case NotificationManager.IMPORTANCE_HIGH:
- case NotificationManager.IMPORTANCE_MAX:
- mHighImportanceButton.setChecked(true);
- break;
- }
-
- // Callback when checked.
- mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
- resetFalsingCheck();
- updateImportanceGroup();
- });
- ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
- (buttonView, isChecked) -> { resetFalsingCheck(); });
- }
-
- private void updateImportanceGroup() {
- final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
- mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
- mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
- }
-
public void closeControls(int x, int y, boolean saveImportance) {
- if (saveImportance) {
- saveImportance();
- }
if (getWindowToken() == null) {
if (mListener != null) {
mListener.onGutsClosed(this);
}
return;
}
+ if (mGutsContent == null || !mGutsContent.handleCloseControls()) {
+ animateClose(x, y);
+ }
+ setExposed(false, mNeedsFalsingProtection);
+ if (mListener != null) {
+ mListener.onGutsClosed(this);
+ }
+ }
+
+ private void animateClose(int x, int y) {
if (x == -1 || y == -1) {
x = (getLeft() + getRight()) / 2;
y = (getTop() + getHeight() / 2);
@@ -395,10 +206,6 @@
}
});
a.start();
- setExposed(false, mNeedsFalsingProtection);
- if (mListener != null) {
- mListener.onGutsClosed(this);
- }
}
public void setActualHeight(int actualHeight) {
@@ -443,4 +250,14 @@
public boolean isExposed() {
return mExposed;
}
+
+ @Override
+ public void onInteraction(View view) {
+ resetFalsingCheck();
+ }
+
+ @Override
+ public void closeGuts(View view) {
+ closeControls(-1 /* x */, -1 /* y */, true /* notify */);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
new file mode 100644
index 0000000..bdbc9b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -0,0 +1,320 @@
+package com.android.systemui.statusbar;
+
+/*
+ * 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
+ */
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.SeekBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
+import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+import java.util.Set;
+
+/**
+ * The guts of a notification revealed when performing a long press.
+ */
+public class NotificationInfo extends LinearLayout implements GutsContent {
+ private static final String TAG = "InfoGuts";
+
+ private INotificationManager mINotificationManager;
+ private int mStartingUserImportance;
+ private StatusBarNotification mStatusBarNotification;
+ private NotificationChannel mNotificationChannel;
+
+ private ImageView mAutoButton;
+ private TextView mImportanceSummary;
+ private TextView mImportanceTitle;
+ private boolean mAuto;
+
+ private View mImportanceGroup;
+ private View mChannelDisabled;
+ private Switch mChannelEnabledSwitch;
+ private RadioButton mMinImportanceButton;
+ private RadioButton mLowImportanceButton;
+ private RadioButton mDefaultImportanceButton;
+ private RadioButton mHighImportanceButton;
+
+ private GutsInteractionListener mGutsInteractionListener;
+
+ public NotificationInfo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public interface OnSettingsClickListener {
+ void onClick(View v, int appUid);
+ }
+
+ public void bindNotification(final PackageManager pm,
+ final INotificationManager iNotificationManager,
+ final StatusBarNotification sbn, final NotificationChannel channel,
+ OnSettingsClickListener onSettingsClick,
+ OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
+ mINotificationManager = iNotificationManager;
+ mNotificationChannel = channel;
+ mStatusBarNotification = sbn;
+ mStartingUserImportance = channel.getImportance();
+
+ final String pkg = sbn.getPackageName();
+ int appUid = -1;
+ String appname = pkg;
+ Drawable pkgicon = null;
+ try {
+ final ApplicationInfo info = pm.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ appUid = info.uid;
+ appname = String.valueOf(pm.getApplicationLabel(info));
+ pkgicon = pm.getApplicationIcon(info);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // app is gone, just show package name and generic icon
+ pkgicon = pm.getDefaultActivityIcon();
+ }
+
+ // If this is the placeholder channel, don't use our channel-specific text.
+ String appNameText;
+ CharSequence channelNameText;
+ if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ appNameText = appname;
+ channelNameText = mContext.getString(R.string.notification_header_default_channel);
+ } else {
+ appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
+ channelNameText = channel.getName();
+ }
+ ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
+ ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
+
+ // Settings button.
+ final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
+ if (appUid >= 0 && onSettingsClick != null) {
+ final int appUidF = appUid;
+ settingsButton.setOnClickListener(
+ (View view) -> {
+ onSettingsClick.onClick(view, appUidF);
+ });
+ settingsButton.setText(R.string.notification_more_settings);
+ } else {
+ settingsButton.setVisibility(View.GONE);
+ }
+
+ // Done button.
+ final TextView doneButton = (TextView) findViewById(R.id.done);
+ doneButton.setText(R.string.notification_done);
+ doneButton.setOnClickListener(onDoneClick);
+
+ boolean nonBlockable = false;
+ try {
+ final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
+ nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
+ } catch (PackageManager.NameNotFoundException e) {
+ // unlikely.
+ }
+ if (nonBlockablePkgs != null) {
+ nonBlockable |= nonBlockablePkgs.contains(pkg);
+ }
+
+ final View importanceButtons = findViewById(R.id.importance_buttons);
+ bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
+
+ // Importance Text (hardcoded to 4 importance levels)
+ final ViewGroup importanceTextGroup = (ViewGroup) findViewById(
+ R.id.importance_buttons_text);
+ final int size = importanceTextGroup.getChildCount();
+ for (int i = 0; i < size; i++) {
+ int importanceNameResId = 0;
+ int importanceDescResId = 0;
+ switch (i) {
+ case 0:
+ importanceNameResId = R.string.high_importance;
+ importanceDescResId = R.string.notification_importance_high;
+ break;
+ case 1:
+ importanceNameResId = R.string.default_importance;
+ importanceDescResId = R.string.notification_importance_default;
+ break;
+ case 2:
+ importanceNameResId = R.string.low_importance;
+ importanceDescResId = R.string.notification_importance_low;
+ break;
+ case 3:
+ importanceNameResId = R.string.min_importance;
+ importanceDescResId = R.string.notification_importance_min;
+ break;
+ default:
+ Log.e(TAG, "Too many importance groups in this layout.");
+ break;
+ }
+ final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
+ ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
+ ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
+ }
+
+ // Top-level importance group
+ mImportanceGroup = findViewById(R.id.importance);
+ mChannelDisabled = findViewById(R.id.channel_disabled);
+ updateImportanceGroup();
+ }
+
+ public boolean hasImportanceChanged() {
+ return mStartingUserImportance != getSelectedImportance();
+ }
+
+ public void saveImportance() {
+ int selectedImportance = getSelectedImportance();
+ if (selectedImportance == mStartingUserImportance) {
+ return;
+ }
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
+ selectedImportance - mStartingUserImportance);
+ mNotificationChannel.setImportance(selectedImportance);
+ try {
+ mINotificationManager.updateNotificationChannelForPackage(
+ mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
+ mNotificationChannel);
+ } catch (RemoteException e) {
+ // :(
+ }
+ }
+
+ private int getSelectedImportance() {
+ if (!mChannelEnabledSwitch.isChecked()) {
+ return NotificationManager.IMPORTANCE_NONE;
+ } else if (mMinImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_MIN;
+ } else if (mLowImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_LOW;
+ } else if (mDefaultImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_DEFAULT;
+ } else if (mHighImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_HIGH;
+ } else {
+ return NotificationManager.IMPORTANCE_UNSPECIFIED;
+ }
+ }
+
+ private void bindToggles(final View importanceButtons, final int importance,
+ final boolean nonBlockable) {
+ // Enabled Switch
+ mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
+ mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
+ mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
+
+ // Importance Buttons
+ mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
+ mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
+ mDefaultImportanceButton = (RadioButton) importanceButtons
+ .findViewById(R.id.default_importance);
+ mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
+
+ // Set to current importance setting
+ switch (importance) {
+ case NotificationManager.IMPORTANCE_UNSPECIFIED:
+ case NotificationManager.IMPORTANCE_NONE:
+ break;
+ case NotificationManager.IMPORTANCE_MIN:
+ mMinImportanceButton.setChecked(true);
+ break;
+ case NotificationManager.IMPORTANCE_LOW:
+ mLowImportanceButton.setChecked(true);
+ break;
+ case NotificationManager.IMPORTANCE_DEFAULT:
+ mDefaultImportanceButton.setChecked(true);
+ break;
+ case NotificationManager.IMPORTANCE_HIGH:
+ case NotificationManager.IMPORTANCE_MAX:
+ mHighImportanceButton.setChecked(true);
+ break;
+ }
+
+ // Callback when checked.
+ mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ mGutsInteractionListener.onInteraction(NotificationInfo.this);
+ updateImportanceGroup();
+ });
+ ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ mGutsInteractionListener.onInteraction(NotificationInfo.this);
+ });
+ }
+
+ private void updateImportanceGroup() {
+ final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
+ mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
+ mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
+ }
+
+ public void closeControls() {
+ if (mGutsInteractionListener != null) {
+ mGutsInteractionListener.closeGuts(this);
+ }
+ }
+
+ @Override
+ public void setInteractionListener(GutsInteractionListener listener) {
+ mGutsInteractionListener = listener;
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public boolean handleCloseControls() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
new file mode 100644
index 0000000..fad63dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.OnMenuClickListener;
+
+public class NotificationMenuRow extends FrameLayout
+ implements PluginListener<NotificationMenuRowProvider>, View.OnClickListener {
+
+ private static final int ICON_ALPHA_ANIM_DURATION = 200;
+
+ private ExpandableNotificationRow mParent;
+ private OnMenuClickListener mListener;
+ private NotificationMenuRowProvider mMenuProvider;
+ private ArrayList<MenuItem> mMenuItems = new ArrayList<>();
+
+ private ValueAnimator mFadeAnimator;
+ private boolean mMenuFadedIn = false;
+ private boolean mAnimating = false;
+ private boolean mOnLeft = true;
+ private boolean mDismissing = false;
+ private boolean mSnapping = false;
+ private boolean mIconsPlaced = false;
+
+ private int[] mIconLocation = new int[2];
+ private int[] mParentLocation = new int[2];
+
+ private float mHorizSpaceForIcon;
+ private int mVertSpaceForIcons;
+
+ private int mIconPadding;
+ private int mIconTint;
+
+ private float mAlpha = 0f;
+
+ public NotificationMenuRow(Context context) {
+ this(context, null);
+ }
+
+ public NotificationMenuRow(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs);
+ PluginManager.getInstance(getContext()).addPluginListener(
+ NotificationMenuRowProvider.ACTION, this,
+ NotificationMenuRowProvider.VERSION, false /* Allow multiple */);
+ mMenuItems.addAll(getDefaultNotificationMenuItems());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ final Resources res = getResources();
+ mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
+ mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
+ mIconTint = res.getColor(R.color.notification_gear_color);
+ updateMenu(false /* notify */);
+ }
+
+ public static MenuItem getLongpressMenuItem(Context context) {
+ Resources res = context.getResources();
+ Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
+ String settingsDescription = res.getString(R.string.notification_menu_gear_description);
+ NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate(
+ R.layout.notification_info, null, false);
+ MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
+ return settings;
+ }
+
+ public ArrayList<MenuItem> getDefaultNotificationMenuItems() {
+ ArrayList<MenuItem> items = new ArrayList<MenuItem>();
+ Resources res = getResources();
+
+ Drawable snoozeIcon = res.getDrawable(R.drawable.ic_snooze);
+ NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(mContext)
+ .inflate(R.layout.notification_snooze, null, false);
+ String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
+ MenuItem snooze = new MenuItem(snoozeIcon, snoozeDescription, content);
+ items.add(snooze);
+
+ Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
+ String settingsDescription = res.getString(R.string.notification_menu_gear_description);
+ NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(mContext).inflate(
+ R.layout.notification_info, null, false);
+ MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
+ items.add(settings);
+ return items;
+ }
+
+ private void updateMenu(boolean notify) {
+ removeAllViews();
+ mMenuItems.clear();
+ if (mMenuProvider != null) {
+ mMenuItems.addAll(mMenuProvider.getMenuItems(getContext()));
+ }
+ mMenuItems.addAll(getDefaultNotificationMenuItems());
+ for (int i = 0; i < mMenuItems.size(); i++) {
+ final View v = createMenuView(mMenuItems.get(i));
+ mMenuItems.get(i).menuView = v;
+ }
+ resetState(notify);
+ }
+
+ private View createMenuView(MenuItem item) {
+ AlphaOptimizedImageView iv = new AlphaOptimizedImageView(getContext());
+ addView(iv);
+ iv.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+ iv.setImageDrawable(item.icon);
+ iv.setOnClickListener(this);
+ iv.setColorFilter(mIconTint);
+ iv.setAlpha(mAlpha);
+ FrameLayout.LayoutParams lp = (LayoutParams) iv.getLayoutParams();
+ lp.width = (int) mHorizSpaceForIcon;
+ lp.height = (int) mHorizSpaceForIcon;
+ return iv;
+ }
+
+ public void resetState(boolean notify) {
+ setMenuAlpha(0f);
+ mIconsPlaced = false;
+ mMenuFadedIn = false;
+ mAnimating = false;
+ mSnapping = false;
+ mDismissing = false;
+ setMenuLocation(mOnLeft ? 1 : -1 /* on left */);
+ if (mListener != null && notify) {
+ mListener.onMenuReset(mParent);
+ }
+ }
+
+ public void setMenuClickListener(OnMenuClickListener listener) {
+ mListener = listener;
+ }
+
+ public void setNotificationRowParent(ExpandableNotificationRow parent) {
+ mParent = parent;
+ setMenuLocation(mOnLeft ? 1 : -1);
+ }
+
+ public void setAppName(String appName) {
+ Resources res = getResources();
+ final int count = mMenuItems.size();
+ for (int i = 0; i < count; i++) {
+ MenuItem item = mMenuItems.get(i);
+ String description = String.format(
+ res.getString(R.string.notification_menu_accessibility),
+ appName, item.menuDescription);
+ item.menuView.setContentDescription(description);
+ }
+ }
+
+ public ExpandableNotificationRow getNotificationParent() {
+ return mParent;
+ }
+
+ public void setMenuAlpha(float alpha) {
+ mAlpha = alpha;
+ if (alpha == 0) {
+ mMenuFadedIn = false; // Can fade in again once it's gone.
+ setVisibility(View.INVISIBLE);
+ } else {
+ setVisibility(View.VISIBLE);
+ }
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).setAlpha(mAlpha);
+ }
+ }
+
+ /**
+ * Returns whether the menu is displayed on the left side of the view or not.
+ */
+ public boolean isMenuOnLeft() {
+ return mOnLeft;
+ }
+
+ /**
+ * Returns the horizontal space in pixels required to display the menu.
+ */
+ public float getSpaceForMenu() {
+ return mHorizSpaceForIcon * getChildCount();
+ }
+
+ /**
+ * Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is
+ * visible.
+ */
+ public boolean isVisible() {
+ return mAlpha > 0;
+ }
+
+ public void cancelFadeAnimator() {
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ }
+
+ public void updateMenuAlpha(final float transX, final float size) {
+ if (mAnimating || !mMenuFadedIn) {
+ // Don't adjust when animating, or if the menu hasn't been shown yet.
+ return;
+ }
+
+ final float fadeThreshold = size * 0.3f;
+ final float absTrans = Math.abs(transX);
+ float desiredAlpha = 0;
+
+ if (absTrans == 0) {
+ desiredAlpha = 0;
+ } else if (absTrans <= fadeThreshold) {
+ desiredAlpha = 1;
+ } else {
+ desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
+ }
+ setMenuAlpha(desiredAlpha);
+ }
+
+ public void fadeInMenu(final boolean fromLeft, final float transX,
+ final float notiThreshold) {
+ if (mDismissing || mAnimating) {
+ return;
+ }
+ if (isMenuLocationChange(transX)) {
+ setMenuAlpha(0f);
+ }
+ setMenuLocation((int) transX);
+ mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1);
+ mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float absTrans = Math.abs(transX);
+
+ boolean pastGear = (fromLeft && transX <= notiThreshold)
+ || (!fromLeft && absTrans <= notiThreshold);
+ if (pastGear && !mMenuFadedIn) {
+ setMenuAlpha((float) animation.getAnimatedValue());
+ }
+ }
+ });
+ mFadeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mAnimating = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ // TODO should animate back to 0f from current alpha
+ setMenuAlpha(0f);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimating = false;
+ mMenuFadedIn = mAlpha == 1;
+ }
+ });
+ mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
+ mFadeAnimator.setDuration(ICON_ALPHA_ANIM_DURATION);
+ mFadeAnimator.start();
+ }
+
+ public void updateVerticalLocation() {
+ if (mParent == null || mMenuItems.size() == 0) {
+ return;
+ }
+ int parentHeight = mParent.getCollapsedHeight();
+ float translationY;
+ if (parentHeight < mVertSpaceForIcons) {
+ translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
+ } else {
+ translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
+ }
+ setTranslationY(translationY);
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ mIconsPlaced = false;
+ setMenuLocation(mOnLeft ? 1 : -1);
+ }
+
+ public void setMenuLocation(int translation) {
+ boolean onLeft = translation > 0;
+ if ((mIconsPlaced && onLeft == mOnLeft) || mSnapping || mParent == null) {
+ // Do nothing
+ return;
+ }
+ final boolean isRtl = mParent.isLayoutRtl();
+ final int count = getChildCount();
+ final int width = getWidth();
+ for (int i = 0; i < count; i++) {
+ final View v = getChildAt(i);
+ final float left = isRtl
+ ? -(width - mHorizSpaceForIcon * (i + 1))
+ : i * mHorizSpaceForIcon;
+ final float right = isRtl
+ ? -i * mHorizSpaceForIcon
+ : width - (mHorizSpaceForIcon * (i + 1));
+ v.setTranslationX(onLeft ? left : right);
+ }
+ mOnLeft = onLeft;
+ mIconsPlaced = true;
+ }
+
+ public boolean isMenuLocationChange(float translation) {
+ boolean onLeft = translation > mIconPadding;
+ boolean onRight = translation < -mIconPadding;
+ if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
+ return true;
+ }
+ return false;
+ }
+
+ public void setDismissing() {
+ mDismissing = true;
+ }
+
+ public void setSnapping(boolean snapping) {
+ mSnapping = snapping;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mListener == null) {
+ // Nothing to do
+ return;
+ }
+ v.getLocationOnScreen(mIconLocation);
+ mParent.getLocationOnScreen(mParentLocation);
+ final int centerX = (int) (mHorizSpaceForIcon / 2);
+ final int centerY = (int) (v.getTranslationY() * 2 + v.getHeight()) / 2;
+ final int x = mIconLocation[0] - mParentLocation[0] + centerX;
+ final int y = mIconLocation[1] - mParentLocation[1] + centerY;
+ final int index = indexOfChild(v);
+ mListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
+ }
+
+ @Override
+ public void onPluginConnected(NotificationMenuRowProvider plugin, Context pluginContext) {
+ mMenuProvider = plugin;
+ updateMenu(false /* notify */);
+ }
+
+ @Override
+ public void onPluginDisconnected(NotificationMenuRowProvider plugin) {
+ mMenuProvider = null;
+ updateMenu(false /* notify */);
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
deleted file mode 100644
index 4315647..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-
-public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener {
-
- private static final int GEAR_ALPHA_ANIM_DURATION = 200;
-
- public interface SettingsIconRowListener {
- /**
- * Called when the gear behind a notification is touched.
- */
- public void onGearTouched(ExpandableNotificationRow row, int x, int y);
-
- /**
- * Called when a notification is slid back over the gear.
- */
- public void onSettingsIconRowReset(ExpandableNotificationRow row);
- }
-
- private ExpandableNotificationRow mParent;
- private AlphaOptimizedImageView mGearIcon;
- private float mHorizSpaceForGear;
- private SettingsIconRowListener mListener;
-
- private ValueAnimator mFadeAnimator;
- private boolean mSettingsFadedIn = false;
- private boolean mAnimating = false;
- private boolean mOnLeft = true;
- private boolean mDismissing = false;
- private boolean mSnapping = false;
- private boolean mIconPlaced = false;
-
- private int[] mGearLocation = new int[2];
- private int[] mParentLocation = new int[2];
- private int mVertSpaceForGear;
-
- public NotificationSettingsIconRow(Context context) {
- this(context, null);
- }
-
- public NotificationSettingsIconRow(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mGearIcon = (AlphaOptimizedImageView) findViewById(R.id.gear_icon);
- mGearIcon.setOnClickListener(this);
- setOnClickListener(this);
- mHorizSpaceForGear =
- getResources().getDimensionPixelOffset(R.dimen.notification_gear_width);
- mVertSpaceForGear = getResources().getDimensionPixelOffset(R.dimen.notification_min_height);
- resetState();
- }
-
- public void resetState() {
- setGearAlpha(0f);
- mIconPlaced = false;
- mSettingsFadedIn = false;
- mAnimating = false;
- mSnapping = false;
- mDismissing = false;
- setIconLocation(true /* on left */);
- if (mListener != null) {
- mListener.onSettingsIconRowReset(mParent);
- }
- }
-
- public void setGearListener(SettingsIconRowListener listener) {
- mListener = listener;
- }
-
- public void setNotificationRowParent(ExpandableNotificationRow parent) {
- mParent = parent;
- setIconLocation(mOnLeft);
- }
-
- public void setAppName(String appName) {
- Resources res = getResources();
- String description = String.format(res.getString(R.string.notification_gear_accessibility),
- appName);
- mGearIcon.setContentDescription(description);
- }
-
- public ExpandableNotificationRow getNotificationParent() {
- return mParent;
- }
-
- public void setGearAlpha(float alpha) {
- if (alpha == 0) {
- mSettingsFadedIn = false; // Can fade in again once it's gone.
- setVisibility(View.INVISIBLE);
- } else {
- setVisibility(View.VISIBLE);
- }
- mGearIcon.setAlpha(alpha);
- }
-
- /**
- * Returns whether the icon is on the left side of the view or not.
- */
- public boolean isIconOnLeft() {
- return mOnLeft;
- }
-
- /**
- * Returns the horizontal space in pixels required to display the gear behind a notification.
- */
- public float getSpaceForGear() {
- return mHorizSpaceForGear;
- }
-
- /**
- * Indicates whether the gear is visible at 1 alpha. Does not indicate
- * if entire view is visible.
- */
- public boolean isVisible() {
- return mGearIcon.getAlpha() > 0;
- }
-
- public void cancelFadeAnimator() {
- if (mFadeAnimator != null) {
- mFadeAnimator.cancel();
- }
- }
-
- public void updateSettingsIcons(final float transX, final float size) {
- if (mAnimating || !mSettingsFadedIn) {
- // Don't adjust when animating, or if the gear hasn't been shown yet.
- return;
- }
-
- final float fadeThreshold = size * 0.3f;
- final float absTrans = Math.abs(transX);
- float desiredAlpha = 0;
-
- if (absTrans == 0) {
- desiredAlpha = 0;
- } else if (absTrans <= fadeThreshold) {
- desiredAlpha = 1;
- } else {
- desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
- }
- setGearAlpha(desiredAlpha);
- }
-
- public void fadeInSettings(final boolean fromLeft, final float transX,
- final float notiThreshold) {
- if (mDismissing || mAnimating) {
- return;
- }
- if (isIconLocationChange(transX)) {
- setGearAlpha(0f);
- }
- setIconLocation(transX > 0 /* fromLeft */);
- mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
- mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- final float absTrans = Math.abs(transX);
-
- boolean pastGear = (fromLeft && transX <= notiThreshold)
- || (!fromLeft && absTrans <= notiThreshold);
- if (pastGear && !mSettingsFadedIn) {
- setGearAlpha((float) animation.getAnimatedValue());
- }
- }
- });
- mFadeAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mAnimating = true;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- // TODO should animate back to 0f from current alpha
- mGearIcon.setAlpha(0f);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimating = false;
- mSettingsFadedIn = mGearIcon.getAlpha() == 1;
- }
- });
- mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION);
- mFadeAnimator.start();
- }
-
- public void updateVerticalLocation() {
- if (mParent == null) {
- return;
- }
- int parentHeight = mParent.getCollapsedHeight();
- if (parentHeight < mVertSpaceForGear) {
- mGearIcon.setTranslationY((parentHeight / 2) - (mGearIcon.getHeight() / 2));
- } else {
- mGearIcon.setTranslationY((mVertSpaceForGear - mGearIcon.getHeight()) / 2);
- }
- }
-
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- setIconLocation(mOnLeft);
- }
-
- public void setIconLocation(boolean onLeft) {
- if ((mIconPlaced && onLeft == mOnLeft) || mSnapping || mParent == null
- || mGearIcon.getWidth() == 0) {
- // Do nothing
- return;
- }
- final boolean isRtl = mParent.isLayoutRtl();
- final float left = isRtl
- ? -(mParent.getWidth() - mHorizSpaceForGear)
- : 0;
- final float right = isRtl
- ? 0
- : mParent.getWidth() - mHorizSpaceForGear;
- final float centerX = ((mHorizSpaceForGear - mGearIcon.getWidth()) / 2);
- setTranslationX(onLeft ? left + centerX : right + centerX);
- mOnLeft = onLeft;
- mIconPlaced = true;
- }
-
- public boolean isIconLocationChange(float translation) {
- boolean onLeft = translation > mGearIcon.getPaddingStart();
- boolean onRight = translation < -mGearIcon.getPaddingStart();
- if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
- return true;
- }
- return false;
- }
-
- public void setDismissing() {
- mDismissing = true;
- }
-
- public void setSnapping(boolean snapping) {
- mSnapping = snapping;
- }
-
- @Override
- public void onClick(View v) {
- if (v.getId() == R.id.gear_icon) {
- if (mListener != null) {
- mGearIcon.getLocationOnScreen(mGearLocation);
- mParent.getLocationOnScreen(mParentLocation);
-
- final int centerX = (int) (mHorizSpaceForGear / 2);
- final int centerY =
- (int) (mGearIcon.getTranslationY() * 2 + mGearIcon.getHeight())/ 2;
- final int x = mGearLocation[0] - mParentLocation[0] + centerX;
- final int y = mGearLocation[1] - mParentLocation[1] + centerY;
- mListener.onGearTouched(mParent, x, y);
- }
- } else {
- // Do nothing when the background is touched.
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
new file mode 100644
index 0000000..1992b6c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -0,0 +1,211 @@
+package com.android.systemui.statusbar;
+/*
+ * 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
+ */
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioGroup;
+import android.widget.RadioGroup.OnCheckedChangeListener;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.systemui.R;
+
+public class NotificationSnooze extends LinearLayout
+ implements NotificationMenuRowProvider.SnoozeGutsContent, View.OnClickListener {
+
+ private static final int MAX_ASSISTANT_SUGGESTIONS = 2;
+ private GutsInteractionListener mGutsInteractionListener;
+ private SnoozeListener mSnoozeListener;
+ private StatusBarNotification mSbn;
+
+ private TextView mSelectedOptionText;
+ private TextView mUndoButton;
+ private ViewGroup mSnoozeOptionView;
+ private List<SnoozeOption> mSnoozeOptions;
+
+ private SnoozeOption mSelectedOption;
+
+ public NotificationSnooze(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ // Create the different options based on list
+ mSnoozeOptions = getDefaultSnoozeOptions();
+ createOptionViews();
+
+ // Snackbar
+ mSelectedOptionText = (TextView) findViewById(R.id.snooze_option_default);
+ mSelectedOptionText.setOnClickListener(this);
+ mUndoButton = (TextView) findViewById(R.id.undo);
+ mUndoButton.setOnClickListener(this);
+
+ // Default to first option in list
+ setSelected(mSnoozeOptions.get(0));
+ }
+
+ public void setSnoozeOptions(final List<SnoozeCriterion> snoozeList) {
+ if (snoozeList == null) {
+ return;
+ }
+ mSnoozeOptions.clear();
+ mSnoozeOptions = getDefaultSnoozeOptions();
+ final int count = Math.min(MAX_ASSISTANT_SUGGESTIONS, snoozeList.size());
+ for (int i = 0; i < count; i++) {
+ SnoozeCriterion sc = snoozeList.get(i);
+ mSnoozeOptions.add(new SnoozeOption(sc, 0, sc.getExplanation(), sc.getConfirmation()));
+ }
+ createOptionViews();
+ }
+
+ private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+ ArrayList<SnoozeOption> options = new ArrayList<>();
+ options.add(createOption(R.string.snooze_option_15_min, 15));
+ options.add(createOption(R.string.snooze_option_30_min, 30));
+ options.add(createOption(R.string.snooze_option_1_hour, 60));
+ return options;
+ }
+
+ private SnoozeOption createOption(int descriptionResId, int minutes) {
+ Resources res = getResources();
+ String resultText = String.format(
+ res.getString(R.string.snoozed_for_time), res.getString(descriptionResId));
+ return new SnoozeOption(null, minutes, res.getString(descriptionResId), resultText);
+ }
+
+ private void createOptionViews() {
+ mSnoozeOptionView = (ViewGroup) findViewById(R.id.snooze_options);
+ mSnoozeOptionView.removeAllViews();
+ mSnoozeOptionView.setVisibility(View.GONE);
+ final Resources res = getResources();
+ final int textSize = res.getDimensionPixelSize(R.dimen.snooze_option_text_size);
+ final int p = res.getDimensionPixelSize(R.dimen.snooze_option_padding);
+
+ // Add all the options
+ for (int i = 0; i < mSnoozeOptions.size(); i++) {
+ SnoozeOption option = mSnoozeOptions.get(i);
+ TextView tv = new TextView(getContext());
+ tv.setTextColor(Color.WHITE);
+ tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ tv.setPadding(p, p, p, p);
+ mSnoozeOptionView.addView(tv);
+ tv.setText(option.description);
+ tv.setTag(option);
+ tv.setOnClickListener(this);
+ }
+
+ // Add the undo option as final item
+ TextView tv = new TextView(getContext());
+ tv.setTextColor(Color.WHITE);
+ tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ tv.setPadding(p, p, p, p);
+ mSnoozeOptionView.addView(tv);
+ tv.setText(R.string.snooze_option_dont_snooze);
+ tv.setOnClickListener(this);
+ }
+
+ private void showSnoozeOptions(boolean show) {
+ mSelectedOptionText.setVisibility(show ? View.GONE : View.VISIBLE);
+ mUndoButton.setVisibility(show ? View.GONE : View.VISIBLE);
+ mSnoozeOptionView.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
+ private void setSelected(SnoozeOption option) {
+ mSelectedOption = option;
+ mSelectedOptionText.setText(option.confirmation);
+ showSnoozeOptions(false);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mGutsInteractionListener != null) {
+ mGutsInteractionListener.onInteraction(this);
+ }
+ final int id = v.getId();
+ final SnoozeOption tag = (SnoozeOption) v.getTag();
+ if (tag != null) {
+ setSelected(tag);
+ } else if (id == R.id.snooze_option_default) {
+ // Show more snooze options
+ showSnoozeOptions(true);
+ } else {
+ undoSnooze();
+ }
+ }
+
+ private void undoSnooze() {
+ mSelectedOption = null;
+ mGutsInteractionListener.closeGuts(this);
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public void setStatusBarNotification(StatusBarNotification sbn) {
+ mSbn = sbn;
+ }
+
+ @Override
+ public void setInteractionListener(GutsInteractionListener listener) {
+ mGutsInteractionListener = listener;
+ }
+
+ @Override
+ public void setSnoozeListener(SnoozeListener listener) {
+ mSnoozeListener = listener;
+ }
+
+ @Override
+ public boolean handleCloseControls() {
+ // When snooze is closed (i.e. there was interaction outside of the notification)
+ // then we commit the snooze action.
+ if (mSnoozeListener != null && mSelectedOption != null) {
+ mSnoozeListener.snoozeNotification(mSbn, mSelectedOption);
+ return true;
+ } else {
+ // Reset the view once it's closed
+ setSelected(mSnoozeOptions.get(0));
+ showSnoozeOptions(false);
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 68d5cd4..9a76ad6 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,24 +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;
- }
-
- 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();
@@ -212,9 +208,13 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mVpnVisible = mSecurityController.isVpnEnabled();
+ mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
for (PhoneState state : mPhoneStates) {
- mMobileSignalGroup.addView(state.mMobileGroup);
+ if (state.mMobileGroup.getParent() == null) {
+ mMobileSignalGroup.addView(state.mMobileGroup);
+ }
}
int endPadding = mMobileSignalGroup.getChildCount() > 0 ? mMobileSignalGroupEndPadding : 0;
@@ -224,15 +224,16 @@
apply();
applyIconTint();
- mNC.addCallback(this);
+ 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();
}
@@ -251,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();
}
});
@@ -263,6 +264,7 @@
boolean activityIn, boolean activityOut, String description) {
mWifiVisible = statusIcon.visible && !mBlockWifi;
mWifiStrengthId = statusIcon.icon;
+ mWifiBadgeId = statusIcon.iconOverlay;
mWifiDescription = statusIcon.contentDescription;
apply();
@@ -271,7 +273,7 @@
@Override
public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId) {
+ String description, boolean isWide, int subId, boolean roaming) {
PhoneState state = getState(subId);
if (state == null) {
return;
@@ -282,6 +284,7 @@
state.mMobileDescription = statusIcon.contentDescription;
state.mMobileTypeDescription = typeContentDescription;
state.mIsMobileTypeIconWide = statusType != 0 && isWide;
+ state.mRoaming = roaming;
apply();
}
@@ -403,6 +406,7 @@
mWifi.setImageDrawable(null);
mWifiDark.setImageDrawable(null);
mLastWifiStrengthId = -1;
+ mLastWifiBadgeId = -1;
}
for (PhoneState state : mPhoneStates) {
@@ -468,10 +472,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);
@@ -533,6 +543,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 {
@@ -540,6 +554,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);
@@ -591,7 +632,8 @@
private String mMobileDescription, mMobileTypeDescription;
private ViewGroup mMobileGroup;
- private ImageView mMobile, mMobileDark, mMobileType;
+ private ImageView mMobile, mMobileDark, mMobileType, mMobileRoaming;
+ public boolean mRoaming;
public PhoneState(int subId, Context context) {
ViewGroup root = (ViewGroup) LayoutInflater.from(context)
@@ -605,6 +647,7 @@
mMobile = (ImageView) root.findViewById(R.id.mobile_signal);
mMobileDark = (ImageView) root.findViewById(R.id.mobile_signal_dark);
mMobileType = (ImageView) root.findViewById(R.id.mobile_type);
+ mMobileRoaming = (ImageView) root.findViewById(R.id.mobile_roaming);
}
public boolean apply(boolean isSecondaryIcon) {
@@ -640,6 +683,7 @@
(mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
+ mMobileRoaming.setVisibility(mRoaming ? View.VISIBLE : View.GONE);
return mMobileVisible;
}
@@ -699,6 +743,8 @@
StatusBarIconController.getDarkIntensity(tintArea, mMobile, darkIntensity),
mMobile, mMobileDark);
setTint(mMobileType, StatusBarIconController.getTint(tintArea, mMobileType, tint));
+ setTint(mMobileRoaming, StatusBarIconController.getTint(tintArea, mMobileRoaming,
+ tint));
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
index dd7c4c7..063252f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
@@ -22,7 +22,7 @@
* A view that can be transformed to and from.
*/
public interface TransformableView {
- int TRANSFORMING_VIEW_HEADER = 0;
+ int TRANSFORMING_VIEW_ICON = 0;
int TRANSFORMING_VIEW_TITLE = 1;
int TRANSFORMING_VIEW_TEXT = 2;
int TRANSFORMING_VIEW_IMAGE = 3;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index cd6c31f..1c89e32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -23,6 +23,7 @@
import android.util.ArraySet;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Interpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -298,5 +299,14 @@
TransformState otherState) {
return false;
}
+
+ /**
+ * Get a custom interpolator for this animation
+ * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY
+ * @param isFrom true if this transformation from the other view
+ */
+ public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) {
+ return null;
+ }
}
}
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..f6a5687 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,20 +33,21 @@
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.StatusBar;
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.
*/
-public class CarStatusBar extends PhoneStatusBar implements
+public class CarStatusBar extends StatusBar implements
CarBatteryController.BatteryViewHandler {
private static final String TAG = "CarStatusBar";
@@ -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..67f8426 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
@@ -15,11 +15,12 @@
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;
-import static com.android.systemui.statusbar.phone.PhoneStatusBar.DEBUG;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
/**
* Controller that monitors signal strength for a device that is connected via bluetooth.
@@ -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/car/FullscreenUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 8e6c153..f8b6dee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -20,7 +20,7 @@
import android.view.ViewStub;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.UserSwitcherController;
/**
@@ -32,7 +32,7 @@
private UserGridView mUserGridView;
private UserSwitcherController mUserSwitcherController;
- public FullscreenUserSwitcher(PhoneStatusBar statusBar,
+ public FullscreenUserSwitcher(StatusBar statusBar,
UserSwitcherController userSwitcherController,
ViewStub containerStub) {
mUserSwitcherController = userSwitcherController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
index d90a21d..137b5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
@@ -29,12 +29,12 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.UserUtil;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.UserSwitcherController;
public class UserGridView extends GridView {
- private PhoneStatusBar mStatusBar;
+ private StatusBar mStatusBar;
private UserSwitcherController mUserSwitcherController;
private Adapter mAdapter;
private int mPendingUserId = UserHandle.USER_NULL;
@@ -43,7 +43,7 @@
super(context, attrs);
}
- public void init(PhoneStatusBar statusBar, UserSwitcherController userSwitcherController) {
+ public void init(StatusBar statusBar, UserSwitcherController userSwitcherController) {
mStatusBar = statusBar;
mUserSwitcherController = userSwitcherController;
mAdapter = new Adapter(mUserSwitcherController);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
new file mode 100644
index 0000000..de4c312
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
+
+import static com.android.systemui.statusbar.TransformableView.TRANSFORMING_VIEW_TITLE;
+import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
+
+/**
+ * A custom transformation that modifies the interpolator
+ */
+public abstract class CustomInterpolatorTransformation
+ extends ViewTransformationHelper.CustomTransformation {
+
+ private final int mViewType;
+
+ public CustomInterpolatorTransformation(int viewType) {
+ mViewType = viewType;
+ }
+
+ @Override
+ public boolean transformTo(TransformState ownState, TransformableView notification,
+ float transformationAmount) {
+ if (!hasCustomTransformation()) {
+ return false;
+ }
+ TransformState otherState = notification.getCurrentState(mViewType);
+ if (otherState == null) {
+ return false;
+ }
+ View view = ownState.getTransformedView();
+ CrossFadeHelper.fadeOut(view, transformationAmount);
+ ownState.transformViewFullyTo(otherState, this, transformationAmount);
+ otherState.recycle();
+ return true;
+ }
+
+ protected boolean hasCustomTransformation() {
+ return true;
+ }
+
+ @Override
+ public boolean transformFrom(TransformState ownState,
+ TransformableView notification, float transformationAmount) {
+ if (!hasCustomTransformation()) {
+ return false;
+ }
+ TransformState otherState = notification.getCurrentState(mViewType);
+ if (otherState == null) {
+ return false;
+ }
+ View view = ownState.getTransformedView();
+ CrossFadeHelper.fadeIn(view, transformationAmount);
+ ownState.transformViewFullyFrom(otherState, this, transformationAmount);
+ otherState.recycle();
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
deleted file mode 100644
index 9501f90..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.util.Pools;
-import android.view.NotificationHeaderView;
-import android.view.View;
-
-import com.android.systemui.statusbar.CrossFadeHelper;
-
-/**
- * A transform state of a text view.
-*/
-public class HeaderTransformState extends TransformState {
-
- private static Pools.SimplePool<HeaderTransformState> sInstancePool
- = new Pools.SimplePool<>(40);
- private View mExpandButton;
- private View mWorkProfileIcon;
- private TransformState mWorkProfileState;
-
- @Override
- public void initFrom(View view) {
- super.initFrom(view);
- if (view instanceof NotificationHeaderView) {
- NotificationHeaderView header = (NotificationHeaderView) view;
- mExpandButton = header.getExpandButton();
- mWorkProfileState = TransformState.obtain();
- mWorkProfileIcon = header.getWorkProfileIcon();
- mWorkProfileState.initFrom(mWorkProfileIcon);
- }
- }
-
- @Override
- public boolean transformViewTo(TransformState otherState, float transformationAmount) {
- // if the transforming notification has a header, we have ensured that it looks the same
- // but the expand button, so lets fade just that one and transform the work profile icon.
- if (!(mTransformedView instanceof NotificationHeaderView)) {
- return false;
- }
- NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
- int childCount = header.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View headerChild = header.getChildAt(i);
- if (headerChild.getVisibility() == View.GONE) {
- continue;
- }
- if (headerChild != mExpandButton) {
- headerChild.setVisibility(View.INVISIBLE);
- } else {
- CrossFadeHelper.fadeOut(mExpandButton, transformationAmount);
- }
- }
- return true;
- }
-
- @Override
- public void transformViewFrom(TransformState otherState, float transformationAmount) {
- // if the transforming notification has a header, we have ensured that it looks the same
- // but the expand button, so lets fade just that one and transform the work profile icon.
- if (!(mTransformedView instanceof NotificationHeaderView)) {
- return;
- }
- NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
- header.setVisibility(View.VISIBLE);
- header.setAlpha(1.0f);
- int childCount = header.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View headerChild = header.getChildAt(i);
- if (headerChild.getVisibility() == View.GONE) {
- continue;
- }
- if (headerChild == mExpandButton) {
- CrossFadeHelper.fadeIn(mExpandButton, transformationAmount);
- } else {
- headerChild.setVisibility(View.VISIBLE);
- if (headerChild == mWorkProfileIcon) {
- mWorkProfileState.transformViewFullyFrom(
- ((HeaderTransformState) otherState).mWorkProfileState,
- transformationAmount);
- }
- }
- }
- return;
- }
-
- public static HeaderTransformState obtain() {
- HeaderTransformState instance = sInstancePool.acquire();
- if (instance != null) {
- return instance;
- }
- return new HeaderTransformState();
- }
-
- @Override
- public void recycle() {
- super.recycle();
- sInstancePool.release(this);
- }
-
- @Override
- protected void reset() {
- super.reset();
- mExpandButton = null;
- mWorkProfileState = null;
- if (mWorkProfileState != null) {
- mWorkProfileState.recycle();
- mWorkProfileState = null;
- }
- }
-
- @Override
- public void setVisible(boolean visible, boolean force) {
- super.setVisible(visible, force);
- if (!(mTransformedView instanceof NotificationHeaderView)) {
- return;
- }
- NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
- int childCount = header.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View headerChild = header.getChildAt(i);
- if (!force && headerChild.getVisibility() == View.GONE) {
- continue;
- }
- headerChild.animate().cancel();
- if (headerChild.getVisibility() != View.GONE) {
- headerChild.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
- if (headerChild == mExpandButton) {
- headerChild.setAlpha(visible ? 1.0f : 0.0f);
- }
- if (headerChild == mWorkProfileIcon) {
- headerChild.setTranslationX(0);
- headerChild.setTranslationY(0);
- }
- }
- }
-
- @Override
- public void prepareFadeIn() {
- super.prepareFadeIn();
- if (!(mTransformedView instanceof NotificationHeaderView)) {
- return;
- }
- NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
- int childCount = header.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View headerChild = header.getChildAt(i);
- if (headerChild.getVisibility() == View.GONE) {
- continue;
- }
- headerChild.animate().cancel();
- headerChild.setVisibility(View.VISIBLE);
- headerChild.setAlpha(1.0f);
- if (headerChild == mWorkProfileIcon) {
- headerChild.setTranslationX(0);
- headerChild.setTranslationY(0);
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
index 6084770..78b967a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
@@ -36,8 +36,8 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
- super.notifyContentUpdated(notification);
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ super.notifyContentUpdated(notification, isLowPriority);
updateImageTag(notification);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
index 3f49125..39db243 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
@@ -41,11 +41,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews(notification);
- super.notifyContentUpdated(notification);
+ super.notifyContentUpdated(notification, isLowPriority);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index 85e87dd..3b18886 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -40,9 +40,6 @@
private final ViewInvertHelper mInvertHelper;
private final Paint mGreyPaint = new Paint();
- private int mBackgroundColor = 0;
- private static final int CUSTOM_BACKGROUND_TAG = R.id.custom_background_color;
- private boolean mShouldInvertDark;
private boolean mShowingLegacyBackground;
protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) {
@@ -106,32 +103,6 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
- super.notifyContentUpdated(notification);
- Drawable background = mView.getBackground();
- mBackgroundColor = 0;
- if (background instanceof ColorDrawable) {
- mBackgroundColor = ((ColorDrawable) background).getColor();
- mView.setBackground(null);
- mView.setTag(CUSTOM_BACKGROUND_TAG, mBackgroundColor);
- } else if (mView.getTag(CUSTOM_BACKGROUND_TAG) != null) {
- mBackgroundColor = (int) mView.getTag(CUSTOM_BACKGROUND_TAG);
- }
- mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor);
- }
-
- private boolean isColorLight(int backgroundColor) {
- return Color.alpha(backgroundColor) == 0
- || ColorUtils.calculateLuminance(backgroundColor) > 0.5;
- }
-
- @Override
- public int getCustomBackgroundColor() {
- // Parent notifications should always use the normal background color
- return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor;
- }
-
- @Override
public void setShowingLegacyBackground(boolean showing) {
super.setShowingLegacyBackground(showing);
mShowingLegacyBackground = showing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 3e4c758..8eab2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -31,8 +31,12 @@
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -42,17 +46,21 @@
import java.util.Stack;
+import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
+
/**
* Wraps a notification header view.
*/
public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
+ private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
+ = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
0, PorterDuff.Mode.SRC_ATOP);
private final int mIconDarkAlpha;
private final int mIconDarkColor = 0xffffffff;
- protected final ViewInvertHelper mInvertHelper;
+ protected final ViewInvertHelper mInvertHelper;
protected final ViewTransformationHelper mTransformationHelper;
protected int mColor;
@@ -60,19 +68,50 @@
private ImageView mExpandButton;
private NotificationHeaderView mNotificationHeader;
+ private TextView mHeaderText;
+ private ImageView mWorkProfileImage;
+ private boolean mIsLowPriority;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(view, row);
mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
mTransformationHelper = new ViewTransformationHelper();
+
+ // we want to avoid that the header clashes with the other text when transforming
+ // low-priority
+ mTransformationHelper.setCustomTransformation(
+ new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) {
+
+ @Override
+ public Interpolator getCustomInterpolator(int interpolationType,
+ boolean isFrom) {
+ boolean isLowPriority = mView instanceof NotificationHeaderView;
+ if (interpolationType == TRANSFORM_Y) {
+ if (isLowPriority && !isFrom
+ || !isLowPriority && isFrom) {
+ return Interpolators.LINEAR_OUT_SLOW_IN;
+ } else {
+ return LOW_PRIORITY_HEADER_CLOSE;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean hasCustomTransformation() {
+ return mIsLowPriority;
+ }
+ }, TRANSFORMING_VIEW_TITLE);
resolveHeaderViews();
updateInvertHelper();
}
protected void resolveHeaderViews() {
mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
+ mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text);
mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
+ mWorkProfileImage = (ImageView) mView.findViewById(com.android.internal.R.id.profile_badge);
mColor = resolveColor(mExpandButton);
mNotificationHeader = (NotificationHeaderView) mView.findViewById(
com.android.internal.R.id.notification_header);
@@ -89,9 +128,9 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
- super.notifyContentUpdated(notification);
-
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ super.notifyContentUpdated(notification, isLowPriority);
+ mIsLowPriority = isLowPriority;
ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
// Reinspect the notification.
@@ -100,6 +139,11 @@
updateTransformedTypes();
addRemainingTransformTypes();
updateCropToPaddingForImageViews();
+ mIcon.setTag(ImageTransformState.ICON_TAG, notification.getNotification().getSmallIcon());
+ // The work profile image is always the same lets just set the icon tag for it not to
+ // animate
+ mWorkProfileImage.setTag(ImageTransformState.ICON_TAG,
+ notification.getNotification().getSmallIcon());
// We need to reset all views that are no longer transforming in case a view was previously
// transformed, but now we decided to transform its container instead.
@@ -154,8 +198,11 @@
protected void updateTransformedTypes() {
mTransformationHelper.reset();
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER,
- mNotificationHeader);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
+ if (mIsLowPriority) {
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
+ mHeaderText);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index 4ce330c..04ee6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -40,11 +40,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews(notification);
- super.notifyContentUpdated(notification);
+ super.notifyContentUpdated(notification, isLowPriority);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index ff2febf..defeab2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -22,7 +22,11 @@
import android.content.Context;
import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
import android.view.View;
+import android.widget.TextView;
+
+import java.util.ArrayList;
/**
* Wraps a notification containing a messaging template
@@ -30,6 +34,7 @@
public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
private View mContractedMessage;
+ private ArrayList<View> mHistoricMessages = new ArrayList<View>();
protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
@@ -44,21 +49,33 @@
&& ((MessagingLinearLayout) container).getChildCount() > 0) {
MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
- // Only consider the first child - transforming to a position other than the first
- // looks bad because we have to move across other messages that are fading in.
- View child = messagingContainer.getChildAt(0);
- if (child.getId() == messagingContainer.getContractedChildId()) {
- mContractedMessage = child;
+ int childCount = messagingContainer.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = messagingContainer.getChildAt(i);
+
+ if (child.getVisibility() == View.GONE
+ && child instanceof TextView
+ && !TextUtils.isEmpty(((TextView) child).getText())) {
+ mHistoricMessages.add(child);
+ }
+
+ // Only consider the first visible child - transforming to a position other than the
+ // first looks bad because we have to move across other messages that are fading in.
+ if (child.getId() == messagingContainer.getContractedChildId()) {
+ mContractedMessage = child;
+ } else if (child.getVisibility() == View.VISIBLE) {
+ break;
+ }
}
}
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews();
- super.notifyContentUpdated(notification);
+ super.notifyContentUpdated(notification, isLowPriority);
}
@Override
@@ -70,4 +87,11 @@
mContractedMessage);
}
}
+
+ @Override
+ public void setRemoteInputVisible(boolean visible) {
+ for (int i = 0; i < mHistoricMessages.size(); i++) {
+ mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index b984c0b..e9956ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -133,11 +133,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveTemplateViews(notification);
- super.notifyContentUpdated(notification);
+ super.notifyContentUpdated(notification, isLowPriority);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 16348dfe..836482a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -19,12 +19,17 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.ColorMatrix;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
+import android.support.v4.graphics.ColorUtils;
import android.view.NotificationHeaderView;
import android.view.View;
import com.android.systemui.Interpolators;
+import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -40,6 +45,8 @@
protected final View mView;
protected final ExpandableNotificationRow mRow;
protected boolean mDark;
+ private int mBackgroundColor = 0;
+ protected boolean mShouldInvertDark;
protected boolean mDarkInitialized = false;
public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
@@ -80,11 +87,24 @@
/**
* Notifies this wrapper that the content of the view might have changed.
- * @param notification
+ * @param notification the notification this is wrapped around
+ * @param isLowPriority is this notification low priority
*/
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
mDarkInitialized = false;
- };
+ Drawable background = mView.getBackground();
+ mBackgroundColor = 0;
+ if (background instanceof ColorDrawable) {
+ mBackgroundColor = ((ColorDrawable) background).getColor();
+ mView.setBackground(null);
+ }
+ mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor);
+ }
+
+ private boolean isColorLight(int backgroundColor) {
+ return Color.alpha(backgroundColor) == 0
+ || ColorUtils.calculateLuminance(backgroundColor) > 0.5;
+ }
protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
@@ -155,7 +175,8 @@
}
public int getCustomBackgroundColor() {
- return 0;
+ // Parent notifications should always use the normal background color
+ return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor;
}
public void setShowingLegacyBackground(boolean showing) {
@@ -163,4 +184,7 @@
public void setContentHeight(int contentHeight, int minHeightHint) {
}
+
+ public void setRemoteInputVisible(boolean visible) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 770ec95..d15ab10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -18,10 +18,10 @@
import android.util.ArraySet;
import android.util.Pools;
-import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -38,10 +38,11 @@
*/
public class TransformState {
+ public static final int TRANSFORM_X = 0x1;
+ public static final int TRANSFORM_Y = 0x10;
+ public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
+
private static final float UNDEFINED = -1f;
- private static final int TRANSOFORM_X = 0x1;
- private static final int TRANSOFORM_Y = 0x10;
- private static final int TRANSOFORM_ALL = TRANSOFORM_X | TRANSOFORM_Y;
private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
@@ -80,25 +81,31 @@
}
public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
- transformViewFrom(otherState, TRANSOFORM_ALL, null, transformationAmount);
+ transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
+ }
+
+ public void transformViewFullyFrom(TransformState otherState,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
+ transformViewFrom(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
}
public void transformViewVerticalFrom(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
- transformViewFrom(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
+ transformViewFrom(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
}
public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) {
- transformViewFrom(otherState, TRANSOFORM_Y, null, transformationAmount);
+ transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
}
private void transformViewFrom(TransformState otherState, int transformationFlags,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
final View transformedView = mTransformedView;
- boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
- boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
+ boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
+ boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
boolean transformScale = transformScale();
// lets animate the positions correctly
if (transformationAmount == 0.0f
@@ -153,14 +160,30 @@
float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
transformationAmount);
if (transformX) {
+ float interpolation = interpolatedValue;
+ if (customTransformation != null) {
+ Interpolator customInterpolator =
+ customTransformation.getCustomInterpolator(TRANSFORM_X, true /* isFrom */);
+ if (customInterpolator != null) {
+ interpolation = customInterpolator.getInterpolation(transformationAmount);
+ }
+ }
transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
0.0f,
- interpolatedValue));
+ interpolation));
}
if (transformY) {
+ float interpolation = interpolatedValue;
+ if (customTransformation != null) {
+ Interpolator customInterpolator =
+ customTransformation.getCustomInterpolator(TRANSFORM_Y, true /* isFrom */);
+ if (customInterpolator != null) {
+ interpolation = customInterpolator.getInterpolation(transformationAmount);
+ }
+ }
transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
0.0f,
- interpolatedValue));
+ interpolation));
}
if (transformScale) {
float transformationStartScaleX = getTransformationStartScaleX();
@@ -207,17 +230,23 @@
}
public void transformViewFullyTo(TransformState otherState, float transformationAmount) {
- transformViewTo(otherState, TRANSOFORM_ALL, null, transformationAmount);
+ transformViewTo(otherState, TRANSFORM_ALL, null, transformationAmount);
+ }
+
+ public void transformViewFullyTo(TransformState otherState,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
+ transformViewTo(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
}
public void transformViewVerticalTo(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
- transformViewTo(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
+ transformViewTo(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
}
public void transformViewVerticalTo(TransformState otherState, float transformationAmount) {
- transformViewTo(otherState, TRANSOFORM_Y, null, transformationAmount);
+ transformViewTo(otherState, TRANSFORM_Y, null, transformationAmount);
}
private void transformViewTo(TransformState otherState, int transformationFlags,
@@ -226,8 +255,8 @@
// lets animate the positions correctly
final View transformedView = mTransformedView;
- boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
- boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
+ boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
+ boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
boolean transformScale = transformScale();
// lets animate the positions correctly
if (transformationAmount == 0.0f) {
@@ -264,23 +293,37 @@
int[] ownPosition = getLaidOutLocationOnScreen();
if (transformX) {
float endX = otherStablePosition[0] - ownPosition[0];
- if (customTransformation != null
- && customTransformation.customTransformTarget(this, otherState)) {
- endX = mTransformationEndX;
+ float interpolation = interpolatedValue;
+ if (customTransformation != null) {
+ if (customTransformation.customTransformTarget(this, otherState)) {
+ endX = mTransformationEndX;
+ }
+ Interpolator customInterpolator =
+ customTransformation.getCustomInterpolator(TRANSFORM_X, false /* isFrom */);
+ if (customInterpolator != null) {
+ interpolation = customInterpolator.getInterpolation(transformationAmount);
+ }
}
transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
endX,
- interpolatedValue));
+ interpolation));
}
if (transformY) {
float endY = otherStablePosition[1] - ownPosition[1];
- if (customTransformation != null
- && customTransformation.customTransformTarget(this, otherState)) {
- endY = mTransformationEndY;
+ float interpolation = interpolatedValue;
+ if (customTransformation != null) {
+ if (customTransformation.customTransformTarget(this, otherState)) {
+ endY = mTransformationEndY;
+ }
+ Interpolator customInterpolator =
+ customTransformation.getCustomInterpolator(TRANSFORM_Y, false /* isFrom */);
+ if (customInterpolator != null) {
+ interpolation = customInterpolator.getInterpolation(transformationAmount);
+ }
}
transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
endY,
- interpolatedValue));
+ interpolation));
}
if (transformScale) {
View otherView = otherState.getTransformedView();
@@ -402,11 +445,6 @@
result.initFrom(view);
return result;
}
- if (view instanceof NotificationHeaderView) {
- HeaderTransformState result = HeaderTransformState.obtain();
- result.initFrom(view);
- return result;
- }
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
result.initFrom(view);
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/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 42d9433..0773108 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -16,11 +16,7 @@
package com.android.systemui.statusbar.phone;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -98,7 +94,7 @@
private DozeScrimController mDozeScrimController;
private KeyguardViewMediator mKeyguardViewMediator;
private ScrimController mScrimController;
- private PhoneStatusBar mPhoneStatusBar;
+ private StatusBar mStatusBar;
private final UnlockMethodCache mUnlockMethodCache;
private final Context mContext;
private boolean mGoingToSleep;
@@ -109,7 +105,7 @@
DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator,
ScrimController scrimController,
- PhoneStatusBar phoneStatusBar,
+ StatusBar statusBar,
UnlockMethodCache unlockMethodCache) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
@@ -119,7 +115,7 @@
mDozeScrimController = dozeScrimController;
mKeyguardViewMediator = keyguardViewMediator;
mScrimController = scrimController;
- mPhoneStatusBar = phoneStatusBar;
+ mStatusBar = statusBar;
mUnlockMethodCache = unlockMethodCache;
}
@@ -218,7 +214,7 @@
break;
case MODE_WAKE_AND_UNLOCK_PULSING:
Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
- mPhoneStatusBar.updateMediaMetaData(false /* metaDataChanged */,
+ mStatusBar.updateMediaMetaData(false /* metaDataChanged */,
true /* allowEnterAnimation */);
// Fall through.
Trace.endSection();
@@ -228,8 +224,8 @@
mDozeScrimController.abortPulsing();
mKeyguardViewMediator.onWakeAndUnlocking();
mScrimController.setWakeAndUnlocking();
- if (mPhoneStatusBar.getNavigationBarView() != null) {
- mPhoneStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
+ if (mStatusBar.getNavigationBarView() != null) {
+ mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
}
Trace.endSection();
break;
@@ -240,7 +236,7 @@
if (mMode != MODE_WAKE_AND_UNLOCK_PULSING) {
mStatusBarWindowManager.setForceDozeBrightness(false);
}
- mPhoneStatusBar.notifyFpAuthModeChanged();
+ mStatusBar.notifyFpAuthModeChanged();
Trace.endSection();
}
@@ -309,7 +305,7 @@
mMode = MODE_NONE;
releaseFingerprintWakeLock();
mStatusBarWindowManager.setForceDozeBrightness(false);
- mPhoneStatusBar.notifyFpAuthModeChanged();
+ mStatusBar.notifyFpAuthModeChanged();
}
public void startKeyguardFadingAway() {
@@ -320,14 +316,14 @@
public void run() {
mStatusBarWindowManager.setForceDozeBrightness(false);
}
- }, PhoneStatusBar.FADE_KEYGUARD_DURATION_PULSING);
+ }, StatusBar.FADE_KEYGUARD_DURATION_PULSING);
}
public void finishKeyguardFadingAway() {
mMode = MODE_NONE;
- if (mPhoneStatusBar.getNavigationBarView() != null) {
- mPhoneStatusBar.getNavigationBarView().setWakeAndUnlocking(false);
+ if (mStatusBar.getNavigationBarView() != null) {
+ mStatusBar.getNavigationBarView().setWakeAndUnlocking(false);
}
- mPhoneStatusBar.notifyFpAuthModeChanged();
+ mStatusBar.notifyFpAuthModeChanged();
}
}
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..6c71d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -19,6 +19,10 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK;
+import static com.android.systemui.tuner.LockscreenFragment.getIntentButton;
+
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.admin.DevicePolicyManager;
@@ -43,6 +47,7 @@
import android.provider.MediaStore;
import android.service.media.CameraPrewarmService;
import android.telecom.TelecomManager;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
@@ -58,6 +63,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,13 +72,16 @@
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;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.PreviewInflater;
+import com.android.systemui.tuner.LockscreenFragment;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
@@ -80,9 +89,10 @@
*/
public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
UnlockMethodCache.OnUnlockMethodChangedListener,
- AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {
+ AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener,
+ Tunable {
- final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
+ final static String TAG = "StatusBar/KeyguardBottomAreaView";
public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
@@ -123,7 +133,7 @@
private PreviewInflater mPreviewInflater;
private KeyguardIndicationController mIndicationController;
private AccessibilityController mAccessibilityController;
- private PhoneStatusBar mPhoneStatusBar;
+ private StatusBar mStatusBar;
private KeyguardAffordanceHelper mAffordanceHelper;
private boolean mUserSetupComplete;
@@ -147,7 +157,13 @@
private Drawable mLeftAssistIcon;
private IntentButton mRightButton = new DefaultRightButton();
+ private IntentButton mRightDefault = mRightButton;
+ private IntentButton mRightPlugin;
+ private String mRightButtonStr;
private IntentButton mLeftButton = new DefaultLeftButton();
+ private IntentButton mLeftDefault = mLeftButton;
+ private IntentButton mLeftPlugin;
+ private String mLeftButtonStr;
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -189,7 +205,7 @@
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (action == ACTION_CLICK) {
if (host == mLockIcon) {
- mPhoneStatusBar.animateCollapsePanels(
+ mStatusBar.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
return true;
} else if (host == mRightAffordanceView) {
@@ -230,6 +246,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
@@ -239,6 +260,8 @@
mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */);
PluginManager.getInstance(getContext()).addPluginListener(LEFT_BUTTON_PLUGIN,
mLeftListener, IntentButtonProvider.VERSION, false /* Only allow one */);
+ TunerService.get(getContext()).addTunable(this, LockscreenFragment.LOCKSCREEN_LEFT_BUTTON,
+ LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON);
}
@Override
@@ -246,6 +269,7 @@
super.onDetachedFromWindow();
PluginManager.getInstance(getContext()).removePluginListener(mRightListener);
PluginManager.getInstance(getContext()).removePluginListener(mLeftListener);
+ TunerService.get(getContext()).removeTunable(this);
}
private void initAccessibility() {
@@ -299,22 +323,8 @@
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;
+ public void setStatusBar(StatusBar statusBar) {
+ mStatusBar = statusBar;
updateCameraVisibility(); // in case onFinishInflate() was called too early
}
@@ -378,13 +388,13 @@
private boolean isCameraDisabledByDpm() {
final DevicePolicyManager dpm =
(DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
- if (dpm != null && mPhoneStatusBar != null) {
+ if (dpm != null && mStatusBar != null) {
try {
final int userId = ActivityManager.getService().getCurrentUser().id;
final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
final boolean disabledBecauseKeyguardSecure =
(disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
- && mPhoneStatusBar.isKeyguardSecure();
+ && mStatusBar.isKeyguardSecure();
return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
} catch (RemoteException e) {
Log.e(TAG, "Can't get userId", e);
@@ -420,7 +430,7 @@
if (!mAccessibilityController.isAccessibilityEnabled()) {
handleTrustCircleClick();
} else {
- mPhoneStatusBar.animateCollapsePanels(
+ mStatusBar.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
}
}
@@ -557,11 +567,13 @@
mAssistManager.launchVoiceAssistFromKeyguard();
}
};
- if (mPhoneStatusBar.isKeyguardCurrentlySecure()) {
+ if (mStatusBar.isKeyguardCurrentlySecure()) {
AsyncTask.execute(runnable);
} else {
- mPhoneStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
- false /* dismissShade */, false /* afterKeyguardGone */, true /* deferred */);
+ boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
+ && TunerService.get(getContext()).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
+ mStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
+ dismissShade, false /* afterKeyguardGone */, true /* deferred */);
}
}
@@ -579,7 +591,9 @@
}
});
} else {
- mActivityStarter.startActivity(mLeftButton.getIntent(), false /* dismissShade */);
+ boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr)
+ && TunerService.get(getContext()).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
+ mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade);
}
}
@@ -761,11 +775,6 @@
mIndicationController = keyguardIndicationController;
}
- public void setAssistManager(AssistManager assistManager) {
- mAssistManager = assistManager;
- updateLeftAffordance();
- }
-
public void updateLeftAffordance() {
updateLeftAffordanceIcon();
updateLeftPreview();
@@ -776,15 +785,33 @@
inflateCameraPreview();
}
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (LockscreenFragment.LOCKSCREEN_LEFT_BUTTON.equals(key)) {
+ mLeftButtonStr = newValue;
+ mLeftIsVoiceAssist = TextUtils.isEmpty(mLeftButtonStr) && mLeftPlugin == null;
+ mLeftButton = getIntentButton(mContext, mLeftButtonStr, mLeftPlugin, mLeftDefault);
+ updateLeftAffordance();
+ } else {
+ mRightButtonStr = newValue;
+ mRightButton = getIntentButton(mContext, mRightButtonStr, mRightPlugin, mRightDefault);
+ updateRightAffordanceIcon();
+ updateCameraVisibility();
+ inflateCameraPreview();
+ }
+ }
+
private void setRightButton(IntentButton button) {
- mRightButton = button;
+ mRightPlugin = button;
+ mRightButton = getIntentButton(mContext, mRightButtonStr, mRightPlugin, mRightDefault);
updateRightAffordanceIcon();
updateCameraVisibility();
inflateCameraPreview();
}
private void setLeftButton(IntentButton button) {
- mLeftButton = button;
+ mLeftPlugin = button;
+ mLeftButton = getIntentButton(mContext, mLeftButtonStr, mLeftPlugin, mLeftDefault);
mLeftIsVoiceAssist = false;
updateLeftAffordance();
}
@@ -792,26 +819,26 @@
private final PluginListener<IntentButtonProvider> mRightListener =
new PluginListener<IntentButtonProvider>() {
@Override
- public void onPluginConnected(IntentButtonProvider plugin) {
+ public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
setRightButton(plugin.getIntentButton());
}
@Override
public void onPluginDisconnected(IntentButtonProvider plugin) {
- setRightButton(new DefaultRightButton());
+ setRightButton(null);
}
};
private final PluginListener<IntentButtonProvider> mLeftListener =
new PluginListener<IntentButtonProvider>() {
@Override
- public void onPluginConnected(IntentButtonProvider plugin) {
+ public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
setLeftButton(plugin.getIntentButton());
}
@Override
public void onPluginDisconnected(IntentButtonProvider plugin) {
- setLeftButton(new DefaultLeftButton());
+ setLeftButton(null);
}
};
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/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index af6e259..99b3aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -51,7 +51,7 @@
private static final String TAG = "LockscreenWallpaper";
- private final PhoneStatusBar mBar;
+ private final StatusBar mBar;
private final WallpaperManager mWallpaperManager;
private final Handler mH;
private final KeyguardUpdateMonitor mUpdateMonitor;
@@ -64,7 +64,7 @@
private UserHandle mSelectedUser;
private AsyncTask<Void, Void, LoaderResult> mLoader;
- public LockscreenWallpaper(Context ctx, PhoneStatusBar bar, Handler h) {
+ public LockscreenWallpaper(Context ctx, StatusBar bar, Handler h) {
mBar = bar;
mH = h;
mWallpaperManager = (WallpaperManager) ctx.getSystemService(Context.WALLPAPER_SERVICE);
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 c0f245c..62b536e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -20,8 +20,8 @@
import static android.app.StatusBarManager.windowStateToString;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.PhoneStatusBar.DEBUG_WINDOW_STATE;
-import static com.android.systemui.statusbar.phone.PhoneStatusBar.dumpBarTransitions;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
+import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -45,6 +45,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.support.annotation.VisibleForTesting;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.Log;
@@ -63,8 +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.SystemUIApplication;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
@@ -102,7 +104,7 @@
protected AccessibilityManager mAccessibilityManager;
private int mDisabledFlags1;
- private PhoneStatusBar mPhoneStatusBar;
+ private StatusBar mStatusBar;
private Recents mRecents;
private Divider mDivider;
private WindowManager mWindowManager;
@@ -123,16 +125,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);
+ mStatusBar = SysUiServiceProvider.getComponent(getContext(), StatusBar.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()
@@ -258,7 +261,7 @@
if (mNavigationBarView != null) {
mNavigationBarView.setNavigationIconHints(hints);
}
- mPhoneStatusBar.checkBarModes();
+ mStatusBar.checkBarModes();
}
@Override
@@ -296,21 +299,21 @@
/**
* Calls appTransitionStarting for the nav bar regardless of whether keyguard is going away.
- * public so PhoneStatusBar can force this when needed.
+ * public so StatusBar can force this when needed.
*/
public void doAppTransitionStarting(long startTime, long duration) {
mNavigationBarView.getLightTransitionsController().appTransitionStarting(startTime,
duration);
}
- // Injected from PhoneStatusBar at creation.
+ // Injected from StatusBar at creation.
public void setCurrentSysuiVisibility(int systemUiVisibility) {
mSystemUiVisibility = systemUiVisibility;
- mNavigationBarMode = mPhoneStatusBar.computeBarMode(0, mSystemUiVisibility,
+ mNavigationBarMode = mStatusBar.computeBarMode(0, mSystemUiVisibility,
View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
View.NAVIGATION_BAR_TRANSPARENT);
checkNavBarModes();
- mPhoneStatusBar.touchAutoHide();
+ mStatusBar.touchAutoHide();
mLightBarController.onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */,
true /* nbModeChanged */, mNavigationBarMode);
}
@@ -327,7 +330,7 @@
// update navigation bar mode
final int nbMode = getView() == null
- ? -1 : mPhoneStatusBar.computeBarMode(oldVal, newVal,
+ ? -1 : mStatusBar.computeBarMode(oldVal, newVal,
View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
View.NAVIGATION_BAR_TRANSPARENT);
nbModeChanged = nbMode != -1;
@@ -336,7 +339,7 @@
mNavigationBarMode = nbMode;
checkNavBarModes();
}
- mPhoneStatusBar.touchAutoHide();
+ mStatusBar.touchAutoHide();
}
}
@@ -366,7 +369,7 @@
}
private boolean shouldDisableNavbarGestures() {
- return !mPhoneStatusBar.isDeviceProvisioned()
+ return !mStatusBar.isDeviceProvisioned()
|| (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
}
@@ -399,10 +402,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) {
@@ -418,7 +417,7 @@
TelecomManager telecomManager =
getContext().getSystemService(TelecomManager.class);
if (telecomManager != null && telecomManager.isRinging()) {
- if (mPhoneStatusBar.isKeyguardShowing()) {
+ if (mStatusBar.isKeyguardShowing()) {
Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
"No heads up");
mHomeBlockedThisTouch = true;
@@ -428,32 +427,29 @@
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- mPhoneStatusBar.awakenDreams();
+ mStatusBar.awakenDreams();
break;
}
return false;
}
private void onVerticalChanged(boolean isVertical) {
- if (mAssistManager != null) {
- // TODO: Clean this up.
- mAssistManager.onConfigurationChanged();
- }
- mPhoneStatusBar.setQsScrimEnabled(!isVertical);
+ mStatusBar.setQsScrimEnabled(!isVertical);
}
private boolean onNavigationTouch(View v, MotionEvent event) {
- mPhoneStatusBar.checkUserAutohide(v, event);
+ mStatusBar.checkUserAutohide(v, event);
return false;
}
- private boolean onHomeLongClick(View v) {
+ @VisibleForTesting
+ boolean onHomeLongClick(View v) {
if (shouldDisableNavbarGestures()) {
return false;
}
MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS);
mAssistManager.startAssist(new Bundle() /* args */);
- mPhoneStatusBar.awakenDreams();
+ mStatusBar.awakenDreams();
if (mNavigationBarView != null) {
mNavigationBarView.abortCurrentGesture();
}
@@ -481,7 +477,7 @@
LatencyTracker.getInstance(getContext()).onActionStart(
LatencyTracker.ACTION_TOGGLE_RECENTS);
}
- mPhoneStatusBar.awakenDreams();
+ mStatusBar.awakenDreams();
mCommandQueue.toggleRecentApps();
}
@@ -549,20 +545,15 @@
private boolean onLongPressRecents() {
if (mRecents == null || !ActivityManager.supportsMultiWindow()
- || !mDivider.getView().getSnapAlgorithm()
- .isSplitScreenFeasible()) {
+ || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()) {
return false;
}
- return mPhoneStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
+ return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
}
- // ----- Methods that PhoneStatusBar talks to (should be minimized) -----
-
- public void setAssistManager(AssistManager assistManager) {
- mAssistManager = assistManager;
- }
+ // ----- Methods that StatusBar talks to (should be minimized) -----
public void setLightBarController(LightBarController lightBarController) {
mLightBarController = lightBarController;
@@ -592,7 +583,7 @@
}
public void checkNavBarModes() {
- mPhoneStatusBar.checkBarMode(mNavigationBarMode,
+ mStatusBar.checkBarMode(mNavigationBarMode,
mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
}
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..9b4867e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -17,6 +17,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.Display;
@@ -47,6 +48,8 @@
private static final String TAG = "NavBarInflater";
public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
+ public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
+ public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
public static final String MENU_IME = "menu_ime";
public static final String BACK = "back";
@@ -55,6 +58,8 @@
public static final String NAVSPACE = "space";
public static final String CLIPBOARD = "clipboard";
public static final String KEY = "key";
+ public static final String LEFT = "left";
+ public static final String RIGHT = "right";
public static final String GRAVITY_SEPARATOR = ";";
public static final String BUTTON_SEPARATOR = ",";
@@ -130,7 +135,8 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+ TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
+ NAV_BAR_RIGHT);
PluginManager.getInstance(getContext()).addPluginListener(NavBarButtonProvider.ACTION, this,
NavBarButtonProvider.VERSION, true /* Allow multiple */);
}
@@ -148,6 +154,9 @@
clearViews();
inflateLayout(newValue);
}
+ } else if (NAV_BAR_LEFT.equals(key) || NAV_BAR_RIGHT.equals(key)) {
+ clearViews();
+ inflateLayout(mCurrentLayout);
}
}
@@ -268,6 +277,13 @@
boolean landscape) {
View v = null;
String button = extractButton(buttonSpec);
+ if (LEFT.equals(button)) {
+ buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_LEFT, NAVSPACE);
+ button = extractButton(buttonSpec);
+ } else if (RIGHT.equals(button)) {
+ buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_RIGHT, MENU_IME);
+ button = extractButton(buttonSpec);
+ }
// Let plugins go first so they can override a standard view if they want.
for (NavBarButtonProvider provider : mPlugins) {
v = provider.createView(buttonSpec, parent);
@@ -291,7 +307,14 @@
v = inflater.inflate(R.layout.custom_key, parent, false);
((KeyButtonView) v).setCode(code);
if (uri != null) {
- ((KeyButtonView) v).loadAsync(uri);
+ if (uri.contains(":")) {
+ ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
+ } else if (uri.contains("/")) {
+ int index = uri.indexOf('/');
+ String pkg = uri.substring(0, index);
+ int id = Integer.parseInt(uri.substring(index + 1));
+ ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
+ }
}
}
return v;
@@ -365,7 +388,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..5e988fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -28,7 +28,6 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
@@ -53,7 +52,6 @@
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
-import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
@@ -553,8 +551,8 @@
}
@Override
- public void onDockedStackMinimizedChanged(boolean minimized, long animDuration)
- throws RemoteException {
+ public void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
+ boolean isHomeStackResizable) throws RemoteException {
}
@Override
@@ -765,7 +763,7 @@
}
@Override
- public void onPluginConnected(NavGesture plugin) {
+ public void onPluginConnected(NavGesture plugin, Context context) {
mGestureHelper = plugin.getGestureHelper();
updateTaskSwitchHelper();
}
@@ -784,7 +782,7 @@
final Point size = new Point();
mDisplay.getRealSize(size);
- pw.println(String.format(" this: " + PhoneStatusBar.viewInfo(this)
+ pw.println(String.format(" this: " + StatusBar.viewInfo(this)
+ " " + visibilityToString(getVisibility())));
getWindowVisibleDisplayFrame(r);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 345dcbd..0386398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -33,7 +33,7 @@
private int mIconHPadding;
private int mIconTint = Color.WHITE;
- private PhoneStatusBar mPhoneStatusBar;
+ private StatusBar mStatusBar;
protected View mNotificationIconArea;
private NotificationIconContainer mNotificationIcons;
private NotificationIconContainer mShelfIcons;
@@ -41,8 +41,8 @@
private NotificationStackScrollLayout mNotificationScrollLayout;
private Context mContext;
- public NotificationIconAreaController(Context context, PhoneStatusBar phoneStatusBar) {
- mPhoneStatusBar = phoneStatusBar;
+ public NotificationIconAreaController(Context context, StatusBar statusBar) {
+ mStatusBar = statusBar;
mNotificationColorUtil = NotificationColorUtil.getInstance(context);
mContext = context;
@@ -64,11 +64,11 @@
mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
R.id.notificationIcons);
- NotificationShelf shelf = mPhoneStatusBar.getNotificationShelf();
+ NotificationShelf shelf = mStatusBar.getNotificationShelf();
mShelfIcons = shelf.getShelfIcons();
shelf.setCollapsedIcons(mNotificationIcons);
- mNotificationScrollLayout = mPhoneStatusBar.getNotificationScrollLayout();
+ mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
}
public void onDensityOrFontScaleChanged(Context context) {
@@ -124,7 +124,7 @@
}
protected int getHeight() {
- return mPhoneStatusBar.getStatusBarHeight();
+ return mStatusBar.getStatusBarHeight();
}
protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
@@ -133,7 +133,7 @@
&& !NotificationData.showNotificationEvenIfUnprovisioned(entry.notification)) {
return false;
}
- if (!PhoneStatusBar.isTopLevelChild(entry)) {
+ if (!StatusBar.isTopLevelChild(entry)) {
return false;
}
if (entry.row.getVisibility() == View.GONE) {
@@ -154,18 +154,6 @@
NotificationShelf.SHOW_AMBIENT_ICONS);
applyNotificationIconsTint();
- ArrayList<NotificationData.Entry> activeNotifications
- = notificationData.getActiveNotifications();
- for (int i = 0; i < activeNotifications.size(); i++) {
- NotificationData.Entry entry = activeNotifications.get(i);
- boolean isPreL = Boolean.TRUE.equals(entry.expandedIcon.getTag(R.id.icon_is_pre_L));
- boolean colorize = !isPreL
- || NotificationUtils.isGrayscale(entry.expandedIcon, mNotificationColorUtil);
- if (colorize) {
- int color = entry.getContrastedColor(mContext);
- entry.expandedIcon.setImageTintList(ColorStateList.valueOf(color));
- }
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 3bdd5e5..c527296 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -92,7 +92,7 @@
public static final long DOZE_ANIMATION_DURATION = 700;
- private KeyguardAffordanceHelper mAfforanceHelper;
+ private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private KeyguardStatusBarView mKeyguardStatusBar;
private QS mQs;
@@ -219,7 +219,7 @@
mFalsingManager = FalsingManager.getInstance(context);
}
- public void setStatusBar(PhoneStatusBar bar) {
+ public void setStatusBar(StatusBar bar) {
mStatusBar = bar;
}
@@ -239,8 +239,8 @@
mNotificationStackScroller.setOnEmptySpaceClickListener(this);
mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
- mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
- mKeyguardBottomArea.setAffordanceHelper(mAfforanceHelper);
+ mAffordanceHelper = new KeyguardAffordanceHelper(this, getContext());
+ mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
mLastOrientation = getResources().getConfiguration().orientation;
mQsFrame = (FrameLayout) findViewById(R.id.qs_frame);
@@ -516,7 +516,7 @@
mBlockTouches = false;
mUnlockIconActive = false;
if (!mLaunchingAffordance) {
- mAfforanceHelper.reset(false);
+ mAffordanceHelper.reset(false);
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
}
closeQs();
@@ -763,7 +763,7 @@
if ((!mIsExpanding || mHintAnimationRunning)
&& !mQsExpanded
&& mStatusBar.getBarState() != StatusBarState.SHADE) {
- mAfforanceHelper.onTouchEvent(event);
+ mAffordanceHelper.onTouchEvent(event);
}
if (mOnlyAffordanceInThisMotion) {
return true;
@@ -881,7 +881,7 @@
@Override
protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
- return !mAfforanceHelper.isOnAffordanceIcon(x, y);
+ return !mAffordanceHelper.isOnAffordanceIcon(x, y);
}
private void onQsTouch(MotionEvent event) {
@@ -1722,7 +1722,7 @@
}
if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
|| mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
- mAfforanceHelper.animateHideLeftRightIcon();
+ mAffordanceHelper.animateHideLeftRightIcon();
}
mNotificationStackScroller.onPanelTrackingStarted();
}
@@ -1739,7 +1739,7 @@
if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
|| mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
if (!mHintAnimationRunning) {
- mAfforanceHelper.reset(true);
+ mAffordanceHelper.reset(true);
}
}
if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
@@ -1785,7 +1785,7 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- mAfforanceHelper.onConfigurationChanged();
+ mAffordanceHelper.onConfigurationChanged();
if (newConfig.orientation != mLastOrientation) {
resetVerticalPanelPosition();
}
@@ -1806,7 +1806,7 @@
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
if (layoutDirection != mOldLayoutDirection) {
- mAfforanceHelper.onRtlPropertiesChanged();
+ mAffordanceHelper.onRtlPropertiesChanged();
mOldLayoutDirection = layoutDirection;
}
}
@@ -1938,7 +1938,7 @@
return;
}
mHintAnimationRunning = true;
- mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() {
+ mAffordanceHelper.startHintAnimation(rightIcon, new Runnable() {
@Override
public void run() {
mHintAnimationRunning = false;
@@ -2351,7 +2351,7 @@
} else {
animate = false;
}
- mAfforanceHelper.launchAffordance(animate, getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ mAffordanceHelper.launchAffordance(animate, getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}
public void onAffordanceLaunchEnded() {
@@ -2397,7 +2397,7 @@
? null : resolveInfo.activityInfo.packageName;
return packageToLaunch != null &&
(keyguardIsShowing || !isForegroundApp(packageToLaunch)) &&
- !mAfforanceHelper.isSwipingInProgress();
+ !mAffordanceHelper.isSwipingInProgress();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 18e394e..5f67468 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -60,7 +60,7 @@
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
}
- protected PhoneStatusBar mStatusBar;
+ protected StatusBar mStatusBar;
protected HeadsUpManager mHeadsUpManager;
private float mPeekHeight;
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/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c80b3ad..7e08812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -30,10 +30,10 @@
public class PhoneStatusBarView extends PanelBar {
private static final String TAG = "PhoneStatusBarView";
- private static final boolean DEBUG = PhoneStatusBar.DEBUG;
+ private static final boolean DEBUG = StatusBar.DEBUG;
private static final boolean DEBUG_GESTURES = false;
- PhoneStatusBar mBar;
+ StatusBar mBar;
boolean mIsFullyOpenedPanel = false;
private final PhoneStatusBarTransitions mBarTransitions;
@@ -59,7 +59,7 @@
return mBarTransitions;
}
- public void setBar(PhoneStatusBar bar) {
+ public void setBar(StatusBar bar) {
mBar = bar;
}
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..dd567e8 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 {
@@ -90,84 +71,34 @@
public static final String TILES_SETTING = Secure.QS_TILES;
private final Context mContext;
- private final PhoneStatusBar mStatusBar;
+ private final StatusBar 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) {
+ public QSTileHost(Context context, StatusBar statusBar,
+ 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 08a91bb..c0e9653 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -21,10 +21,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
+import android.icu.text.NumberFormat;
import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;
+import android.util.SparseBooleanArray;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -32,21 +37,27 @@
import android.widget.TextView;
import android.widget.Toast;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
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.qs.QSPanel;
import com.android.systemui.plugins.qs.QS.Callback;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QuickQSPanel;
import com.android.systemui.qs.TouchAnimator;
import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.SignalClusterView;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -54,7 +65,8 @@
import com.android.systemui.tuner.TunerService;
public class QuickStatusBarHeader extends BaseStatusBarHeader implements
- NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener {
+ NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
+ BatteryStateChangeCallback, SignalCallback {
private static final String TAG = "QuickStatusBarHeader";
@@ -62,19 +74,20 @@
private ActivityStarter mActivityStarter;
private NextAlarmController mNextAlarmController;
+ private UserInfoController mUserInfoController;
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
private TextView mAlarmStatus;
private View mAlarmStatusCollapsed;
+ private ViewGroup mDateTimeGroup;
+ private ViewGroup mDateTimeAlarmGroup;
private QSPanel mQsPanel;
private boolean mExpanded;
private boolean mAlarmShowing;
- private ViewGroup mDateTimeGroup;
- private ViewGroup mDateTimeAlarmGroup;
private TextView mEmergencyOnly;
protected ExpandableIndicator mExpandIndicator;
@@ -95,6 +108,9 @@
protected View mEdit;
private boolean mShowFullAlarm;
private float mDateTimeTranslation;
+ private TextView mBatteryLevel;
+ private SparseBooleanArray mRoamingsBySubId = new SparseBooleanArray();
+ private boolean mIsRoaming;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -108,7 +124,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);
@@ -130,6 +147,8 @@
mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
mAlarmStatus.setOnClickListener(this);
+ mBatteryLevel = (TextView) findViewById(R.id.battery_level);
+
mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
mMultiUserAvatar = (ImageView) mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
@@ -139,6 +158,19 @@
((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);
+
+ mNextAlarmController = Dependency.get(NextAlarmController.class);
+ mUserInfoController = Dependency.get(UserInfoController.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
}
@Override
@@ -240,9 +272,6 @@
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
- mHost.getUserInfoController().removeCallback(this);
- mHost.getNetworkController().removeEmergencyListener(this);
- mHost.getUserInfoController().removeCallback(this);
super.onDetachedFromWindow();
}
@@ -276,7 +305,7 @@
protected void updateVisibilities() {
updateAlarmVisibilities();
updateDateTimePosition();
- mEmergencyOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly
+ mEmergencyOnly.setVisibility(mExpanded && (mShowEmergencyCallsOnly || mIsRoaming)
? View.VISIBLE : View.INVISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
@@ -287,23 +316,26 @@
}
private void updateDateTimePosition() {
- mDateTimeAlarmGroup.setTranslationY(mShowEmergencyCallsOnly
+ mDateTimeAlarmGroup.setTranslationY(mShowEmergencyCallsOnly || mIsRoaming
? mExpansionAmount * mDateTimeTranslation : 0);
}
private void updateListeners() {
if (mListening) {
mNextAlarmController.addCallback(this);
+ mUserInfoController.addCallback(this);
+ if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
+ Dependency.get(NetworkController.class).addEmergencyListener(this);
+ Dependency.get(NetworkController.class).addCallback(this);
+ }
} else {
mNextAlarmController.removeCallback(this);
+ mUserInfoController.removeCallback(this);
+ Dependency.get(NetworkController.class).removeEmergencyListener(this);
+ Dependency.get(NetworkController.class).removeCallback(this);
}
}
- @Override
- public void setActivityStarter(ActivityStarter activityStarter) {
- mActivityStarter = activityStarter;
- }
-
public void setQSPanel(final QSPanel qsPanel) {
mQsPanel = qsPanel;
setupHost(qsPanel.getHost());
@@ -314,17 +346,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);
- }
}
@Override
@@ -334,7 +358,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.
@@ -347,7 +371,7 @@
}
startSettingsActivity();
- }));
+ });
} else {
startSettingsActivity();
}
@@ -362,18 +386,6 @@
true /* dismissShade */);
}
- public void setNextAlarmController(NextAlarmController nextAlarmController) {
- mNextAlarmController = nextAlarmController;
- }
-
- public void setBatteryController(BatteryController batteryController) {
- // Don't care
- }
-
- public void setUserInfoController(UserInfoController userInfoController) {
- userInfoController.addCallback(this);
- }
-
@Override
public void setCallback(Callback qsPanelCallback) {
mHeaderQsPanel.setCallback(qsPanelCallback);
@@ -391,6 +403,39 @@
}
@Override
+ public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
+ mBatteryLevel.setText(percentage);
+ }
+
+ @Override
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ // Don't care.
+ }
+
+ public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
+ int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
+ String description, boolean isWide, int subId, boolean roaming) {
+ mRoamingsBySubId.put(subId, roaming);
+ boolean isRoaming = calculateRoaming();
+ if (mIsRoaming != isRoaming) {
+ mIsRoaming = isRoaming;
+ mEmergencyOnly.setText(mIsRoaming ? R.string.accessibility_data_connection_roaming
+ : com.android.internal.R.string.emergency_calls_only);
+ if (mExpanded) {
+ updateEverything();
+ }
+ }
+ }
+
+ private boolean calculateRoaming() {
+ for (int i = 0; i < mRoamingsBySubId.size(); i++) {
+ if (mRoamingsBySubId.valueAt(i)) return true;
+ }
+ return false;
+ }
+
+ @Override
public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
mMultiUserAvatar.setImageDrawable(picture);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 191718e..45fd7ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.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,12 +72,9 @@
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.Message;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -119,15 +117,16 @@
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;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-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;
@@ -136,8 +135,10 @@
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.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
@@ -148,7 +149,6 @@
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BackDropView;
-import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.DragDownHelper;
@@ -157,39 +157,35 @@
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationContentView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationInfo;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
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.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.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
@@ -199,17 +195,102 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
+import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
+import android.app.ActivityManager.StackId;
+import android.app.INotificationManager;
+import android.app.KeyguardManager;
+import android.app.NotificationChannel;
+import android.app.RemoteInput;
+import android.app.TaskStackBuilder;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.os.Build;
+import android.os.Handler;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.service.notification.NotificationListenerService;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.view.IWindowManager;
+import android.view.ViewAnimationUtils;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.RecentsComponent;
+import com.android.systemui.SwipeHelper;
+import com.android.systemui.SystemUI;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeGutsContent;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+import com.android.systemui.util.NotificationChannels;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.Stack;
+
+public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
- OnHeadsUpChangedListener, VisualStabilityManager.Callback {
- static final String TAG = "PhoneStatusBar";
- public static final boolean DEBUG = BaseStatusBar.DEBUG;
+ OnHeadsUpChangedListener, VisualStabilityManager.Callback, SnoozeListener,
+ CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
+ ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
+ ExpandableNotificationRow.OnExpandClickListener {
+ public static final boolean MULTIUSER_DEBUG = false;
+
+ public static final boolean ENABLE_REMOTE_INPUT =
+ SystemProperties.getBoolean("debug.enable_remote_input", true);
+ public static final boolean ENABLE_CHILD_NOTIFICATIONS
+ = SystemProperties.getBoolean("debug.child_notifs", true);
+ public static final boolean FORCE_REMOTE_INPUT_HISTORY =
+ SystemProperties.getBoolean("debug.force_remoteinput_history", false);
+ private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
+
+ protected static final int MSG_SHOW_RECENT_APPS = 1019;
+ protected static final int MSG_HIDE_RECENT_APPS = 1020;
+ protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
+ protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
+ protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
+ protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
+ protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
+ protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
+ protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
+
+ protected static final boolean ENABLE_HEADS_UP = true;
+ protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+
+ private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
+ // Should match the values in PhoneWindowManager
+ public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+ public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
+
+ private static final String BANNER_ACTION_CANCEL =
+ "com.android.systemui.statusbar.banner_action_cancel";
+ private static final String BANNER_ACTION_SETUP =
+ "com.android.systemui.statusbar.banner_action_setup";
+ private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
+ = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
+ static final String TAG = "StatusBar";
+ public static final boolean DEBUG = false;
public static final boolean SPEW = false;
public static final boolean DUMPTRUCK = true; // extra dumpsys info
public static final boolean DEBUG_GESTURES = false;
@@ -301,34 +382,24 @@
FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement;
}
+ /**
+ * The {@link StatusBarState} of the status bar.
+ */
+ protected int mState;
+ protected boolean mBouncerShowing;
+ protected boolean mShowLockscreenNotifications;
+ protected boolean mAllowLockscreenRemoteInput;
+
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;
int mNaturalBarHeight = -1;
- Display mDisplay;
Point mCurrentDisplaySize = new Point();
protected StatusBarWindowView mStatusBarWindow;
@@ -408,23 +479,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;
@@ -433,9 +497,7 @@
if (mKeyguardBottomArea != null) {
mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
}
- if (mNetworkController != null) {
- mNetworkController.setUserSetupComplete(mUserSetup);
- }
+ updateQsExpansionEnabled();
}
if (mIconPolicy != null) {
mIconPolicy.setCurrentUserSetup(mUserSetup);
@@ -443,6 +505,7 @@
}
};
+ protected H mHandler = createHandler();
final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -636,6 +699,12 @@
}
};
+ private KeyguardUserSwitcher mKeyguardUserSwitcher;
+ private UserSwitcherController mUserSwitcherController;
+ private NetworkController mNetworkController;
+ private KeyguardMonitorImpl mKeyguardMonitor;
+ private BatteryController mBatteryController;
+
private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
final int N = array.size();
for (int i = 0 ; i < N; i++) {
@@ -669,22 +738,163 @@
@Override
public void start() {
- mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
- .getDefaultDisplay();
+ 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);
+
+ mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ mDisplay = mWindowManager.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);
- putComponent(PhoneStatusBar.class, this);
+ DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
+ putComponent(StatusBar.class, this);
- super.start(); // calls createAndAddWindows()
+ // start old BaseStatusBar.start().
+ mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
+ mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+
+ mNotificationData = new NotificationData(this);
+
+ mAccessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+ mDreamManager = IDreamManager.Stub.asInterface(
+ ServiceManager.checkService(DreamService.DREAM_SERVICE));
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+
+ mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+ mSettingsObserver);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+ mLockscreenSettingsObserver,
+ UserHandle.USER_ALL);
+ if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
+ false,
+ mSettingsObserver,
+ UserHandle.USER_ALL);
+ }
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+ true,
+ mLockscreenSettingsObserver,
+ UserHandle.USER_ALL);
+
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+
+ mRecents = getComponent(Recents.class);
+
+ final Configuration currentConfig = mContext.getResources().getConfiguration();
+ mLocale = currentConfig.locale;
+ mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
+ mFontScale = currentConfig.fontScale;
+ mDensity = currentConfig.densityDpi;
+
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mLockPatternUtils = new LockPatternUtils(mContext);
+
+ // Connect in to the status bar manager service
+ mCommandQueue = getComponent(CommandQueue.class);
+ mCommandQueue.addCallbacks(this);
+
+ int[] switches = new int[9];
+ ArrayList<IBinder> binders = new ArrayList<IBinder>();
+ ArrayList<String> iconSlots = new ArrayList<>();
+ ArrayList<StatusBarIcon> icons = new ArrayList<>();
+ Rect fullscreenStackBounds = new Rect();
+ Rect dockedStackBounds = new Rect();
+ try {
+ mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
+ fullscreenStackBounds, dockedStackBounds);
+ } catch (RemoteException ex) {
+ // If the system process isn't there we're doomed anyway.
+ }
+
+ createAndAddWindows();
+
+ mSettingsObserver.onChange(false); // set up
+ disable(switches[0], switches[6], false /* animate */);
+ setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
+ fullscreenStackBounds, dockedStackBounds);
+ topAppWindowChanged(switches[2] != 0);
+ // StatusBarManagerService has a back up of IME token and it's restored here.
+ setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0);
+
+ // Set up the initial icon state
+ int N = iconSlots.size();
+ int viewIndex = 0;
+ for (int i=0; i < N; i++) {
+ setIcon(iconSlots.get(i), icons.get(i));
+ }
+
+ // Set up the initial notification state.
+ try {
+ mNotificationListener.registerAsSystemService(mContext,
+ new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
+ UserHandle.USER_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to register notification listener", e);
+ }
+
+
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
+ icons.size(),
+ switches[0],
+ switches[1],
+ switches[2],
+ switches[3]
+ ));
+ }
+
+ mCurrentUserId = ActivityManager.getCurrentUser();
+ setHeadsUpUser(mCurrentUserId);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_USER_ADDED);
+ filter.addAction(Intent.ACTION_USER_PRESENT);
+ mContext.registerReceiver(mBaseBroadcastReceiver, filter);
+
+ IntentFilter internalFilter = new IntentFilter();
+ internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
+ internalFilter.addAction(BANNER_ACTION_CANCEL);
+ internalFilter.addAction(BANNER_ACTION_SETUP);
+ mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
+
+ IntentFilter allUsersFilter = new IntentFilter();
+ allUsersFilter.addAction(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
+ mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
+ null, null);
+ updateCurrentProfilesCache();
+
+ IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService("vrmanager"));
+ try {
+ vrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+ }
+
+ mNonBlockablePkgs = new HashSet<String>();
+ Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_nonBlockableNotificationPackages));
+ // end old BaseStatusBar.start().
mMediaSessionManager
= (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -692,9 +902,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
@@ -715,12 +923,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() {
@@ -794,15 +1001,13 @@
// 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;
mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
R.id.notification_stack_scroller);
mStackScroller.setLongPressListener(getNotificationLongClicker());
- mStackScroller.setPhoneStatusBar(this);
+ mStackScroller.setStatusBar(this);
mStackScroller.setGroupManager(mGroupManager);
mStackScroller.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setOnGroupChangeListener(mStackScroller);
@@ -826,9 +1031,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);
@@ -838,7 +1042,7 @@
createIconController();
- mBatteryController = createBatteryController();
+ // TODO: Find better place for this callback.
mBatteryController.addCallback(new BatteryStateChangeCallback() {
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
@@ -847,13 +1051,14 @@
mDozeServiceHost.firePowerSaveChanged(isPowerSave);
}
}
+
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
// noop
}
});
- mLightBarController = new LightBarController(mIconController, mBatteryController);
+ mLightBarController = new LightBarController(mIconController);
if (mNavigationBar != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
@@ -882,36 +1087,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.setStatusBar(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();
}
@@ -920,15 +1101,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;
@@ -938,21 +1117,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();
@@ -1013,7 +1180,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");
@@ -1024,19 +1192,9 @@
return mStatusBarView;
}
- public Handler getTimeTickHandler() {
- return mTimeTickHandler;
- }
-
- public static Handler getTimeTickHandler(Context context) {
- return ((SystemUIApplication) context.getApplicationContext())
- .getComponent(PhoneStatusBar.class).getTimeTickHandler();
- }
-
protected void createNavigationBar() {
mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
mNavigationBar = (NavigationBarFragment) fragment;
- mNavigationBar.setAssistManager(mAssistManager);
if (mLightBarController != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
@@ -1062,10 +1220,6 @@
}
}
- protected BatteryController createBatteryController() {
- return new BatteryControllerImpl(mContext);
- }
-
private void inflateShelf() {
mNotificationShelf =
(NotificationShelf) LayoutInflater.from(mContext).inflate(
@@ -1076,9 +1230,21 @@
mNotificationShelf.setStatusBarState(mState);
}
- @Override
protected void onDensityOrFontScaleChanged() {
- super.onDensityOrFontScaleChanged();
+ // start old BaseStatusBar.onDensityOrFontScaleChanged().
+ ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
+ for (int i = 0; i < activeNotifications.size(); i++) {
+ Entry entry = activeNotifications.get(i);
+ boolean exposedGuts = mNotificationGutsExposed != null
+ && entry.row.getGuts() == mNotificationGutsExposed;
+ entry.row.reInflateViews();
+ if (exposedGuts) {
+ mNotificationGutsExposed = entry.row.getGuts();
+ bindGuts(entry.row, mGutsMenuItem);
+ }
+ inflateViews(entry, mStackScroller);
+ }
+ // end old BaseStatusBar.onDensityOrFontScaleChanged().
mScrimController.onDensityOrFontScaleChanged();
mStatusBarView.onDensityOrFontScaleChanged();
if (mBrightnessMirrorController != null) {
@@ -1091,10 +1257,10 @@
inflateEmptyShadeView();
updateEmptyShadeView();
mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged();
- mUserInfoController.onDensityOrFontScaleChanged();
- if (mUserSwitcherController != null) {
- mUserSwitcherController.onDensityOrFontScaleChanged();
- }
+ // TODO: Bring these out of StatusBar.
+ ((UserInfoControllerImpl) Dependency.get(UserInfoController.class))
+ .onDensityOrFontScaleChanged();
+ Dependency.get(UserSwitcherController.class).onDensityOrFontScaleChanged();
if (mKeyguardUserSwitcher != null) {
mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
}
@@ -1124,8 +1290,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;
}
@@ -1156,11 +1320,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) {
@@ -1168,15 +1328,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
@@ -1252,16 +1403,20 @@
}
}
- @Override
protected void setZenMode(int mode) {
- super.setZenMode(mode);
+ // start old BaseStatusBar.setZenMode().
+ if (isDeviceProvisioned()) {
+ mZenMode = mode;
+ updateNotifications();
+ }
+ // end old BaseStatusBar.setZenMode().
if (mIconPolicy != null) {
mIconPolicy.setZenMode(mode);
}
}
protected void startKeyguard() {
- Trace.beginSection("PhoneStatusBar#startKeyguard");
+ Trace.beginSection("StatusBar#startKeyguard");
KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
mFingerprintUnlockController = new FingerprintUnlockController(mContext,
mStatusBarWindowManager, mDozeScrimController, keyguardViewMediator,
@@ -1271,6 +1426,8 @@
mFingerprintUnlockController);
mKeyguardIndicationController.setStatusBarKeyguardViewManager(
mStatusBarKeyguardViewManager);
+ mKeyguardIndicationController.setUserInfoController(
+ Dependency.get(UserInfoController.class));
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mIconPolicy.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
@@ -1299,7 +1456,6 @@
Trace.endSection();
}
- @Override
protected View getStatusBarView() {
return mStatusBarView;
}
@@ -1321,7 +1477,6 @@
return mNaturalBarHeight;
}
- @Override
protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
if (mRecents == null) {
return false;
@@ -1369,7 +1524,6 @@
return new UserHandle(mCurrentUserId);
}
- @Override
public void addNotification(StatusBarNotification notification, RankingMap ranking,
Entry oldEntry) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
@@ -1432,13 +1586,11 @@
}
}
- @Override
protected void updateNotificationRanking(RankingMap ranking) {
mNotificationData.updateRanking(ranking);
updateNotifications();
}
- @Override
public void removeNotification(String key, RankingMap ranking) {
boolean deferRemoval = false;
if (mHeadsUpManager.isHeadsUp(key)) {
@@ -1480,7 +1632,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());
@@ -1569,13 +1721,28 @@
}
}
- @Override
protected void performRemoveNotification(StatusBarNotification n) {
Entry entry = mNotificationData.get(n.getKey());
if (mRemoteInputController.isRemoteInputActive(entry)) {
mRemoteInputController.removeRemoteInput(entry, null);
}
- super.performRemoveNotification(n);
+ // start old BaseStatusBar.performRemoveNotification.
+ final String pkg = n.getPackageName();
+ final String tag = n.getTag();
+ final int id = n.getId();
+ final int userId = n.getUserId();
+ try {
+ mBarService.onNotificationClear(pkg, tag, id, userId);
+ if (FORCE_REMOTE_INPUT_HISTORY
+ && mKeysKeptForRemoteInput.contains(n.getKey())) {
+ mKeysKeptForRemoteInput.remove(n.getKey());
+ }
+ removeNotification(n.getKey(), null);
+
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ // end old BaseStatusBar.performRemoveNotification.
}
private void updateNotificationShade() {
@@ -1804,17 +1971,14 @@
}
}
- @Override
public void addQsTile(ComponentName tile) {
mQSPanel.getHost().addTile(tile);
}
- @Override
public void remQsTile(ComponentName tile) {
mQSPanel.getHost().removeTile(tile);
}
- @Override
public void clickTile(ComponentName tile) {
mQSPanel.clickTile(tile);
}
@@ -1877,7 +2041,6 @@
return entry.row.getParent() instanceof NotificationStackScrollLayout;
}
- @Override
protected void updateNotifications() {
mNotificationData.filterAndSort();
@@ -1888,7 +2051,6 @@
updateNotifications();
}
- @Override
protected void setAreThereNotifications() {
if (SPEW) {
@@ -2072,7 +2234,7 @@
* Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
*/
public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
- Trace.beginSection("PhoneStatusBar#updateMediaMetaData");
+ Trace.beginSection("StatusBar#updateMediaMetaData");
if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
Trace.endSection();
return;
@@ -2367,9 +2529,8 @@
disable(mDisabledUnmodified1, mDisabledUnmodified2, animate);
}
- @Override
- protected BaseStatusBar.H createHandler() {
- return new PhoneStatusBar.H();
+ protected H createHandler() {
+ return new StatusBar.H();
}
@Override
@@ -2378,6 +2539,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);
}
@@ -2401,7 +2567,6 @@
return getBarState() == StatusBarState.KEYGUARD;
}
- @Override
public boolean isDozing() {
return mDozing;
}
@@ -2495,7 +2660,6 @@
}
- @Override
protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
boolean alertAgain) {
final boolean wasHeadsUp = isHeadsUp(key);
@@ -2512,7 +2676,6 @@
}
}
- @Override
protected void setHeadsUpUser(int newUserId) {
if (mHeadsUpManager != null) {
mHeadsUpManager.setUser(newUserId);
@@ -2523,7 +2686,6 @@
return mHeadsUpManager.isHeadsUp(key);
}
- @Override
protected boolean isSnoozedPackage(StatusBarNotification sbn) {
return mHeadsUpManager.isSnoozed(sbn.getPackageName());
}
@@ -2580,11 +2742,39 @@
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
- private class H extends BaseStatusBar.H {
+ protected class H extends Handler {
@Override
public void handleMessage(Message m) {
- super.handleMessage(m);
switch (m.what) {
+ // start old BaseStatusBar.H handling.
+ case MSG_SHOW_RECENT_APPS:
+ showRecents(m.arg1 > 0, m.arg2 != 0);
+ break;
+ case MSG_HIDE_RECENT_APPS:
+ hideRecents(m.arg1 > 0, m.arg2 > 0);
+ break;
+ case MSG_TOGGLE_RECENTS_APPS:
+ toggleRecents();
+ break;
+ case MSG_PRELOAD_RECENT_APPS:
+ preloadRecents();
+ break;
+ case MSG_CANCEL_PRELOAD_RECENT_APPS:
+ cancelPreloadingRecents();
+ break;
+ case MSG_SHOW_NEXT_AFFILIATED_TASK:
+ showRecentsNextAffiliatedTask();
+ break;
+ case MSG_SHOW_PREV_AFFILIATED_TASK:
+ showRecentsPreviousAffiliatedTask();
+ break;
+ case MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU:
+ toggleKeyboardShortcuts(m.arg1);
+ break;
+ case MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU:
+ dismissKeyboardShortcuts();
+ break;
+ // End old BaseStatusBar.H handling.
case MSG_OPEN_NOTIFICATION_PANEL:
animateExpandNotificationsPanel();
break;
@@ -2601,7 +2791,6 @@
}
}
- @Override
public void maybeEscalateHeadsUp() {
Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries();
for (HeadsUpManager.HeadsUpEntry entry : entries) {
@@ -2701,12 +2890,10 @@
1.0f /* speedUpFactor */);
}
- @Override
public void animateCollapsePanels(int flags, boolean force) {
animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
}
- @Override
public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
}
@@ -3031,7 +3218,6 @@
}
};
- @Override
public void setInteracting(int barWindow, boolean interacting) {
final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting;
mInteractingWindows = interacting
@@ -3224,30 +3410,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 {
@@ -3261,9 +3424,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);
@@ -3279,7 +3439,6 @@
pw.println(BarTransitions.modeToString(transitions.getMode()));
}
- @Override
public void createAndAddWindows() {
addStatusBarWindow();
}
@@ -3462,7 +3621,6 @@
}
}
- @Override
protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) {
dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone);
}
@@ -3482,7 +3640,15 @@
protected void onConfigurationChanged(Configuration newConfig) {
updateResources();
updateDisplaySize(); // populates mDisplayMetrics
- super.onConfigurationChanged(newConfig); // calls refreshLayout
+ // Begin old BaseStatusBar.onConfigurationChanged
+ final float fontScale = newConfig.fontScale;
+ final int density = newConfig.densityDpi;
+ if (density != mDensity || mFontScale != fontScale) {
+ onDensityOrFontScaleChanged();
+ mDensity = density;
+ mFontScale = fontScale;
+ }
+ // End old BaseStatusBar.onConfigurationChanged
if (DEBUG) {
Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
@@ -3490,18 +3656,16 @@
updateRowStates();
mScreenPinningRequest.onConfigurationChanged();
- mNetworkController.onConfigurationChanged();
}
- @Override
public void userSwitched(int newUserId) {
- super.userSwitched(newUserId);
+ // Begin old BaseStatusBar.userSwitched
+ setHeadsUpUser(newUserId);
+ // End old BaseStatusBar.userSwitched
if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
animateCollapsePanels();
updatePublicMode();
updateNotifications();
- resetUserSetupObserver();
- setControllerUsers();
clearCurrentMediaNotification();
setLockscreenUser(newUserId);
}
@@ -3512,26 +3676,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.
*
@@ -3572,14 +3716,40 @@
// Visibility reporting
- @Override
protected void handleVisibleToUserChanged(boolean visibleToUser) {
if (visibleToUser) {
- super.handleVisibleToUserChanged(visibleToUser);
+ handleVisibleToUserChangedImpl(visibleToUser);
startNotificationLogging();
} else {
stopNotificationLogging();
- super.handleVisibleToUserChanged(visibleToUser);
+ handleVisibleToUserChangedImpl(visibleToUser);
+ }
+ }
+
+ /**
+ * The LEDs are turned off when the notification panel is shown, even just a little bit.
+ * See also StatusBar.setPanelExpanded for another place where we attempt to do this.
+ */
+ // Old BaseStatusBar.handleVisibileToUserChanged
+ private void handleVisibleToUserChangedImpl(boolean visibleToUser) {
+ try {
+ if (visibleToUser) {
+ boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+ boolean clearNotificationEffects =
+ !isPanelFullyCollapsed() &&
+ (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
+ int notificationLoad = mNotificationData.getActiveNotifications().size();
+ if (pinnedHeadsUp && isPanelFullyCollapsed()) {
+ notificationLoad = 1;
+ } else {
+ MetricsLogger.histogram(mContext, "note_load", notificationLoad);
+ }
+ mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
+ } else {
+ mBarService.onPanelHidden();
+ }
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
}
}
@@ -3712,32 +3882,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) {
@@ -3778,9 +3940,16 @@
}
}
- @Override
public void destroy() {
- super.destroy();
+ // Begin old BaseStatusBar.destroy().
+ mContext.unregisterReceiver(mBaseBroadcastReceiver);
+ try {
+ mNotificationListener.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+ mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+ // End old BaseStatusBar.destroy().
if (mStatusBarWindow != null) {
mWindowManager.removeViewImmediate(mStatusBarWindow);
mStatusBarWindow = null;
@@ -3789,10 +3958,6 @@
mWindowManager.removeViewImmediate(mNavigationBarView);
mNavigationBarView = null;
}
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- mHandlerThread = null;
- }
mContext.unregisterReceiver(mBroadcastReceiver);
mContext.unregisterReceiver(mDemoReceiver);
mAssistManager.destroy();
@@ -3803,12 +3968,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;
@@ -3890,7 +4054,6 @@
return mState;
}
- @Override
public boolean isPanelFullyCollapsed() {
return mNotificationPanel.isFullyCollapsed();
}
@@ -3939,12 +4102,10 @@
updateMediaMetaData(true /* metaDataChanged */, true);
}
- @Override
public boolean isCollapsing() {
return mNotificationPanel.isCollapsing();
}
- @Override
public void addPostCollapseAction(Runnable r) {
mPostCollapseRunnables.add(r);
}
@@ -4058,7 +4219,7 @@
* @return true if we would like to stay in the shade, false if it should go away entirely
*/
public boolean hideKeyguard() {
- Trace.beginSection("PhoneStatusBar#hideKeyguard");
+ Trace.beginSection("StatusBar#hideKeyguard");
boolean staying = mLeaveOpenOnKeyguardHide;
setBarState(StatusBarState.SHADE);
View viewToClick = null;
@@ -4190,7 +4351,7 @@
}
protected void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) {
- Trace.beginSection("PhoneStatusBar#updateKeyguardState");
+ Trace.beginSection("StatusBar#updateKeyguardState");
if (mState == StatusBarState.KEYGUARD) {
mKeyguardIndicationController.setVisible(true);
mNotificationPanel.resetViews();
@@ -4227,7 +4388,7 @@
}
private void updateDozingState() {
- Trace.beginSection("PhoneStatusBar#updateDozingState");
+ Trace.beginSection("StatusBar#updateDozingState");
boolean animate = !mDozing && mDozeScrimController.isPulsing();
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
@@ -4433,7 +4594,6 @@
}
}
- @Override
protected int getMaxKeyguardNotifications(boolean recompute) {
if (recompute) {
mMaxKeyguardNotifications = Math.max(1,
@@ -4533,29 +4693,53 @@
}
}
- @Override
public void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
mLeaveOpenOnKeyguardHide = true;
dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */);
}
- @Override
protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
mLeaveOpenOnKeyguardHide = true;
showBouncer();
mPendingRemoteInputView = clicked;
}
- @Override
protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender,
String notificationKey) {
// Clear pending remote view, as we do not want to trigger pending remote input view when
// it's called by other code
mPendingWorkRemoteInputView = null;
- return super.startWorkChallengeIfNecessary(userId, intendSender, notificationKey);
+ // Begin old BaseStatusBar.startWorkChallengeIfNecessary.
+ final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null,
+ null, userId);
+ if (newIntent == null) {
+ return false;
+ }
+ final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
+ callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender);
+ callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey);
+ callBackIntent.setPackage(mContext.getPackageName());
+
+ PendingIntent callBackPendingIntent = PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ callBackIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT |
+ PendingIntent.FLAG_ONE_SHOT |
+ PendingIntent.FLAG_IMMUTABLE);
+ newIntent.putExtra(
+ Intent.EXTRA_INTENT,
+ callBackPendingIntent.getIntentSender());
+ try {
+ ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent,
+ null /*options*/);
+ } catch (RemoteException ex) {
+ // ignore
+ }
+ return true;
+ // End old BaseStatusBar.startWorkChallengeIfNecessary.
}
- @Override
protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
View clicked) {
// Collapse notification and show work challenge
@@ -4575,7 +4759,6 @@
return false;
}
- @Override
protected void onWorkChallengeChanged() {
updatePublicMode();
updateNotifications();
@@ -4672,9 +4855,8 @@
return mKeyguardFadingAwayDuration;
}
- @Override
public void setBouncerShowing(boolean bouncerShowing) {
- super.setBouncerShowing(bouncerShowing);
+ mBouncerShowing = bouncerShowing;
mStatusBarView.setBouncerShowing(bouncerShowing);
recomputeDisableFlags(true /* animate */);
}
@@ -4754,12 +4936,12 @@
return !mNotificationData.getActiveNotifications().isEmpty();
}
- public void wakeUpIfDozing(long time, MotionEvent event) {
+ 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();
@@ -4847,7 +5029,7 @@
}
private void updateDozing() {
- Trace.beginSection("PhoneStatusBar#updateDozing");
+ Trace.beginSection("StatusBar#updateDozing");
// When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
|| mFingerprintUnlockController.getMode()
@@ -4983,7 +5165,7 @@
@Override
public boolean isPowerSaveActive() {
- return mBatteryController != null && mBatteryController.isPowerSave();
+ return mBatteryController.isPowerSave();
}
@Override
@@ -4999,8 +5181,2022 @@
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
- PhoneStatusBar.this.startPendingIntentDismissingKeyguard(intent);
+ StatusBar.this.startPendingIntentDismissingKeyguard(intent);
}
}
+
+ public SnoozeListener getSnoozeListener() {
+ return this;
+ }
+
+ @Override
+ public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption) {
+ setNotificationSnoozed(sbn, snoozeOption);
+ }
+
+ // Begin Extra BaseStatusBar methods.
+
+ protected CommandQueue mCommandQueue;
+ protected IStatusBarService mBarService;
+
+ // all notifications
+ protected NotificationData mNotificationData;
+ protected NotificationStackScrollLayout mStackScroller;
+
+ protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
+
+ protected RemoteInputController mRemoteInputController;
+
+ // for heads up notifications
+ protected HeadsUpManager mHeadsUpManager;
+
+ // handling reordering
+ protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
+
+ protected int mCurrentUserId = 0;
+ final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+
+ protected int mLayoutDirection = -1; // invalid
+ protected AccessibilityManager mAccessibilityManager;
+
+ protected boolean mDeviceInteractive;
+
+ protected boolean mVisible;
+ protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
+ protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
+
+ /**
+ * Notifications with keys in this set are not actually around anymore. We kept them around
+ * when they were canceled in response to a remote input interaction. This allows us to show
+ * what you replied and allows you to continue typing into it.
+ */
+ protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
+
+ // mScreenOnFromKeyguard && mVisible.
+ private boolean mVisibleToUser;
+
+ private Locale mLocale;
+ private float mFontScale;
+
+ protected boolean mUseHeadsUp = false;
+ protected boolean mHeadsUpTicker = false;
+ protected boolean mDisableNotificationAlerts = false;
+
+ protected DevicePolicyManager mDevicePolicyManager;
+ protected IDreamManager mDreamManager;
+ protected PowerManager mPowerManager;
+ protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+
+ // public mode, private notifications, etc
+ private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
+ private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
+ private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+
+ private UserManager mUserManager;
+ private int mDensity;
+
+ protected KeyguardManager mKeyguardManager;
+ private LockPatternUtils mLockPatternUtils;
+ private DeviceProvisionedController mDeviceProvisionedController;
+
+ // UI-specific methods
+
+ protected WindowManager mWindowManager;
+ protected IWindowManager mWindowManagerService;
+
+ protected Display mDisplay;
+
+ protected RecentsComponent mRecents;
+
+ protected int mZenMode;
+
+ // which notification is currently being longpress-examined by the user
+ private NotificationGuts mNotificationGutsExposed;
+ private MenuItem mGutsMenuItem;
+
+ private KeyboardShortcuts mKeyboardShortcuts;
+
+ protected NotificationShelf mNotificationShelf;
+ protected DismissView mDismissView;
+ protected EmptyShadeView mEmptyShadeView;
+
+ private NotificationClicker mNotificationClicker = new NotificationClicker();
+
+ protected AssistManager mAssistManager;
+
+ protected boolean mVrMode;
+
+ private Set<String> mNonBlockablePkgs;
+
+ @Override // NotificationData.Environment
+ public boolean isDeviceProvisioned() {
+ return mDeviceProvisionedController.isDeviceProvisioned();
+ }
+
+ private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+ @Override
+ public void onVrStateChanged(boolean enabled) {
+ mVrMode = enabled;
+ }
+ };
+
+ public boolean isDeviceInVrMode() {
+ 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 int mode = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+ setZenMode(mode);
+
+ updateLockscreenNotificationSetting();
+ }
+ };
+
+ private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+ // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
+ mUsersAllowingPrivateNotifications.clear();
+ mUsersAllowingNotifications.clear();
+ // ... and refresh all the notifications
+ updateLockscreenNotificationSetting();
+ updateNotifications();
+ }
+ };
+
+ 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;
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
+ }
+ logActionClick(view);
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ try {
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ final boolean isActivity = pendingIntent.isActivity();
+ if (isActivity) {
+ final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+ final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
+ mContext, pendingIntent.getIntent(), mCurrentUserId);
+ dismissKeyguardThenExecute(new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ try {
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+
+ boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
+
+ // close the shade if it was open
+ if (handled) {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */);
+ visibilityChanged(false);
+ mAssistManager.hideAssist();
+ }
+
+ // Wait for activity start.
+ return handled;
+ }
+ }, afterKeyguardGone);
+ return true;
+ } else {
+ return superOnClickHandler(view, pendingIntent, fillInIntent);
+ }
+ }
+
+ private void logActionClick(View view) {
+ ViewParent parent = view.getParent();
+ String key = getNotificationKeyForParent(parent);
+ if (key == null) {
+ Log.w(TAG, "Couldn't determine notification for click.");
+ return;
+ }
+ int index = -1;
+ // If this is a default template, determine the index of the button.
+ if (view.getId() == com.android.internal.R.id.action0 &&
+ parent != null && parent instanceof ViewGroup) {
+ ViewGroup actionGroup = (ViewGroup) parent;
+ index = actionGroup.indexOfChild(view);
+ }
+ try {
+ mBarService.onNotificationActionClick(key, index);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ private String getNotificationKeyForParent(ViewParent parent) {
+ while (parent != null) {
+ if (parent instanceof ExpandableNotificationRow) {
+ return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey();
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
+ private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
+ Intent fillInIntent) {
+ return super.onClickHandler(view, pendingIntent, fillInIntent,
+ StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+ }
+
+ private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
+ Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
+ RemoteInput[] inputs = null;
+ if (tag instanceof RemoteInput[]) {
+ inputs = (RemoteInput[]) tag;
+ }
+
+ if (inputs == null) {
+ return false;
+ }
+
+ RemoteInput input = null;
+
+ for (RemoteInput i : inputs) {
+ if (i.getAllowFreeFormInput()) {
+ input = i;
+ }
+ }
+
+ if (input == null) {
+ return false;
+ }
+
+ ViewParent p = view.getParent();
+ RemoteInputView riv = null;
+ while (p != null) {
+ if (p instanceof View) {
+ View pv = (View) p;
+ if (pv.isRootNamespace()) {
+ riv = (RemoteInputView) pv.findViewWithTag(RemoteInputView.VIEW_TAG);
+ break;
+ }
+ }
+ p = p.getParent();
+ }
+ ExpandableNotificationRow row = null;
+ while (p != null) {
+ if (p instanceof ExpandableNotificationRow) {
+ row = (ExpandableNotificationRow) p;
+ break;
+ }
+ p = p.getParent();
+ }
+
+ if (riv == null || row == null) {
+ return false;
+ }
+
+ row.setUserExpanded(true);
+
+ if (!mAllowLockscreenRemoteInput) {
+ final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
+ if (isLockscreenPublicMode(userId)) {
+ onLockedRemoteInput(row, view);
+ return true;
+ }
+ if (mUserManager.getUserInfo(userId).isManagedProfile()
+ && mKeyguardManager.isDeviceLocked(userId)) {
+ onLockedWorkRemoteInput(userId, row, view);
+ return true;
+ }
+ }
+
+ int width = view.getWidth();
+ if (view instanceof TextView) {
+ // Center the reveal on the text which might be off-center from the TextView
+ TextView tv = (TextView) view;
+ if (tv.getLayout() != null) {
+ int innerWidth = (int) tv.getLayout().getLineWidth(0);
+ innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+ width = Math.min(width, innerWidth);
+ }
+ }
+ int cx = view.getLeft() + width / 2;
+ int cy = view.getTop() + view.getHeight() / 2;
+ int w = riv.getWidth();
+ int h = riv.getHeight();
+ int r = Math.max(
+ Math.max(cx + cy, cx + (h - cy)),
+ Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+ riv.setRevealParameters(cx, cy, r);
+ riv.setPendingIntent(pendingIntent);
+ riv.setRemoteInput(inputs, input);
+ riv.focusAnimated();
+
+ return true;
+ }
+
+ };
+
+ private final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ updateCurrentProfilesCache();
+ if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+
+ updateLockscreenNotificationSetting();
+
+ userSwitched(mCurrentUserId);
+ } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+ updateCurrentProfilesCache();
+ } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+ List<ActivityManager.RecentTaskInfo> recentTask = null;
+ try {
+ recentTask = ActivityManager.getService().getRecentTasks(1,
+ ActivityManager.RECENT_WITH_EXCLUDED
+ | ActivityManager.RECENT_INCLUDE_PROFILES,
+ mCurrentUserId).getList();
+ } catch (RemoteException e) {
+ // Abandon hope activity manager not running.
+ }
+ if (recentTask != null && recentTask.size() > 0) {
+ UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId);
+ if (user != null && user.isManagedProfile()) {
+ Toast toast = Toast.makeText(mContext,
+ R.string.managed_profile_foreground_toast,
+ Toast.LENGTH_SHORT);
+ TextView text = (TextView) toast.getView().findViewById(
+ android.R.id.message);
+ text.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
+ int paddingPx = mContext.getResources().getDimensionPixelSize(
+ R.dimen.managed_profile_toast_padding);
+ text.setCompoundDrawablePadding(paddingPx);
+ toast.show();
+ }
+ }
+ } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
+ NotificationManager noMan = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ noMan.cancel(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS);
+
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
+ if (BANNER_ACTION_SETUP.equals(action)) {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */);
+ mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ );
+ }
+ } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
+ final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+ if (intentSender != null) {
+ try {
+ mContext.startIntentSender(intentSender, null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ /* ignore */
+ }
+ }
+ if (notificationKey != null) {
+ try {
+ mBarService.onNotificationClick(notificationKey);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
+ }
+ }
+ };
+
+ private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+ if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
+ isCurrentProfile(getSendingUserId())) {
+ mUsersAllowingPrivateNotifications.clear();
+ updateLockscreenNotificationSetting();
+ updateNotifications();
+ } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
+ if (userId != mCurrentUserId && isCurrentProfile(userId)) {
+ onWorkChallengeChanged();
+ }
+ }
+ }
+ };
+
+ private final NotificationListenerService mNotificationListener =
+ new NotificationListenerService() {
+ @Override
+ public void onListenerConnected() {
+ if (DEBUG) Log.d(TAG, "onListenerConnected");
+ final StatusBarNotification[] notifications = getActiveNotifications();
+ if (notifications == null) {
+ Log.w(TAG, "onListenerConnected unable to get active notifications.");
+ return;
+ }
+ final RankingMap currentRanking = getCurrentRanking();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (StatusBarNotification sbn : notifications) {
+ addNotification(sbn, currentRanking, null /* oldEntry */);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onNotificationPosted(final StatusBarNotification sbn,
+ final RankingMap rankingMap) {
+ if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
+ if (sbn != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ processForRemoteInput(sbn.getNotification());
+ String key = sbn.getKey();
+ mKeysKeptForRemoteInput.remove(key);
+ boolean isUpdate = mNotificationData.get(key) != null;
+ // In case we don't allow child notifications, we ignore children of
+ // notifications that have a summary, since we're not going to show them
+ // anyway. This is true also when the summary is canceled,
+ // because children are automatically canceled by NoMan in that case.
+ if (!ENABLE_CHILD_NOTIFICATIONS
+ && mGroupManager.isChildInGroupWithSummary(sbn)) {
+ if (DEBUG) {
+ Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+ }
+
+ // Remove existing notification to avoid stale data.
+ if (isUpdate) {
+ removeNotification(key, rankingMap);
+ } else {
+ mNotificationData.updateRanking(rankingMap);
+ }
+ return;
+ }
+ if (isUpdate) {
+ updateNotification(sbn, rankingMap);
+ } else {
+ addNotification(sbn, rankingMap, null /* oldEntry */);
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn,
+ final RankingMap rankingMap) {
+ if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
+ if (sbn != null) {
+ final String key = sbn.getKey();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ removeNotification(key, rankingMap);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(final RankingMap rankingMap) {
+ if (DEBUG) Log.d(TAG, "onRankingUpdate");
+ if (rankingMap != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ updateNotificationRanking(rankingMap);
+ }
+ });
+ } }
+
+ };
+
+ private void updateCurrentProfilesCache() {
+ synchronized (mCurrentProfiles) {
+ mCurrentProfiles.clear();
+ if (mUserManager != null) {
+ for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
+ mCurrentProfiles.put(user.id, user);
+ }
+ }
+ }
+ }
+
+ protected void notifyUserAboutHiddenNotifications() {
+ if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) {
+ Log.d(TAG, "user hasn't seen notification about hidden notifications");
+ if (!mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
+ Log.d(TAG, "insecure lockscreen, skipping notification");
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
+ return;
+ }
+ Log.d(TAG, "disabling lockecreen notifications and alerting the user");
+ // disable lockscreen notifications until user acts on the banner.
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0);
+
+ final String packageName = mContext.getPackageName();
+ PendingIntent cancelIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(BANNER_ACTION_CANCEL).setPackage(packageName),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent setupIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(BANNER_ACTION_SETUP).setPackage(packageName),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ final int colorRes = com.android.internal.R.color.system_notification_accent_color;
+ Notification.Builder note = new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_android)
+ .setContentTitle(mContext.getString(R.string.hidden_notifications_title))
+ .setContentText(mContext.getString(R.string.hidden_notifications_text))
+ .setChannel(NotificationChannels.SECURITY)
+ .setOngoing(true)
+ .setColor(mContext.getColor(colorRes))
+ .setContentIntent(setupIntent)
+ .addAction(R.drawable.ic_close,
+ mContext.getString(R.string.hidden_notifications_cancel),
+ cancelIntent)
+ .addAction(R.drawable.ic_settings,
+ mContext.getString(R.string.hidden_notifications_setup),
+ setupIntent);
+ overrideNotificationAppName(mContext, note);
+
+ NotificationManager noMan =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ noMan.notify(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS, note.build());
+ }
+ }
+
+ @Override // NotificationData.Environment
+ public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
+ final int thisUserId = mCurrentUserId;
+ final int notificationUserId = n.getUserId();
+ if (DEBUG && MULTIUSER_DEBUG) {
+ Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
+ n, thisUserId, notificationUserId));
+ }
+ return isCurrentProfile(notificationUserId);
+ }
+
+ protected void setNotificationShown(StatusBarNotification n) {
+ setNotificationsShown(new String[]{n.getKey()});
+ }
+
+ protected void setNotificationsShown(String[] keys) {
+ try {
+ mNotificationListener.setNotificationsShown(keys);
+ } catch (RuntimeException e) {
+ Log.d(TAG, "failed setNotificationsShown: ", e);
+ }
+ }
+
+ protected boolean isCurrentProfile(int userId) {
+ synchronized (mCurrentProfiles) {
+ return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
+ }
+ }
+
+ @Override
+ public NotificationGroupManager getGroupManager() {
+ return mGroupManager;
+ }
+
+ protected void bindDismissRunnable(final ExpandableNotificationRow row) {
+ row.setOnDismissRunnable(() -> performRemoveNotification(row.getStatusBarNotification()));
+ }
+
+ protected void applyColorsAndBackgrounds(StatusBarNotification sbn,
+ NotificationData.Entry entry) {
+
+ if (entry.getContentView().getId()
+ != com.android.internal.R.id.status_bar_latest_event_content) {
+ // Using custom RemoteViews
+ if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+ && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) {
+ entry.row.setShowingLegacyBackground(true);
+ entry.legacy = true;
+ }
+ }
+
+ entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+ }
+
+ public boolean isMediaNotification(NotificationData.Entry entry) {
+ // TODO: confirm that there's a valid media key
+ return entry.getExpandedContentView() != null &&
+ entry.getExpandedContentView()
+ .findViewById(com.android.internal.R.id.media_actions) != null;
+ }
+
+ // The (i) button in the guts that links to the system notification settings for that app
+ private void startAppNotificationSettingsActivity(String packageName, final int appUid) {
+ final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
+ intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+ startNotificationGutsIntent(intent, appUid);
+ }
+
+ private void startNotificationGutsIntent(final Intent intent, final int appUid) {
+ dismissKeyguardThenExecute(new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ TaskStackBuilder.create(mContext)
+ .addNextIntentWithParentStack(intent)
+ .startActivities(getActivityOptions(),
+ new UserHandle(UserHandle.getUserId(appUid)));
+ }
+ });
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+ return true;
+ }
+ }, false /* afterKeyguardGone */);
+ }
+
+ protected void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
+ if (snoozeOption.criterion != null) {
+ mNotificationListener.snoozeNotification(sbn.getKey(), snoozeOption.criterion.getId());
+ } else {
+ GregorianCalendar snoozeUntil = new GregorianCalendar();
+ snoozeUntil.add(Calendar.MINUTE, snoozeOption.snoozeForMinutes);
+ mNotificationListener.snoozeNotification(sbn.getKey(), snoozeUntil.getTimeInMillis());
+ }
+ }
+
+ private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
+ row.inflateGuts();
+ row.setGutsView(item);
+ final StatusBarNotification sbn = row.getStatusBarNotification();
+ row.setTag(sbn.getPackageName());
+ final NotificationGuts guts = row.getGuts();
+ guts.setClosedListener((NotificationGuts g) -> {
+ if (!row.isRemoved()) {
+ mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
+ }
+ mNotificationGutsExposed = null;
+ mGutsMenuItem = null;
+ });
+
+ if (item.gutsContent instanceof SnoozeGutsContent) {
+ ((SnoozeGutsContent) item.gutsContent).setSnoozeListener(getSnoozeListener());
+ ((SnoozeGutsContent) item.gutsContent).setStatusBarNotification(sbn);
+ ((NotificationSnooze) item.gutsContent).setSnoozeOptions(row.getEntry().snoozeCriteria);
+ }
+
+ if (item.gutsContent instanceof NotificationInfo) {
+ final NotificationChannel channel = row.getEntry().channel;
+ PackageManager pmUser = getPackageManagerForUser(mContext,
+ sbn.getUser().getIdentifier());
+ final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final String pkg = sbn.getPackageName();
+ NotificationInfo info = (NotificationInfo) item.gutsContent;
+ final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
+ int appUid) -> {
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ startAppNotificationSettingsActivity(pkg, appUid);
+ };
+ final View.OnClickListener onDoneClick = (View v) -> {
+ // If the user has security enabled, show challenge if the setting is changed.
+ if (info.hasImportanceChanged()
+ && isLockscreenPublicMode(sbn.getUser().getIdentifier())
+ && (mState == StatusBarState.KEYGUARD
+ || mState == StatusBarState.SHADE_LOCKED)) {
+ OnDismissAction dismissAction = new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ saveAndCloseNotificationMenu(info, row, guts, v);
+ return true;
+ }
+ };
+ onLockedNotificationImportanceChange(dismissAction);
+ } else {
+ saveAndCloseNotificationMenu(info, row, guts, v);
+ }
+ };
+ info.bindNotification(pmUser, iNotificationManager, sbn, channel, onSettingsClick,
+ onDoneClick,
+ mNonBlockablePkgs);
+ }
+ }
+
+ private void saveAndCloseNotificationMenu(NotificationInfo info,
+ ExpandableNotificationRow row, NotificationGuts guts, View done) {
+ guts.resetFalsingCheck();
+ info.saveImportance();
+ int[] rowLocation = new int[2];
+ int[] doneLocation = new int[2];
+ row.getLocationOnScreen(rowLocation);
+ done.getLocationOnScreen(doneLocation);
+
+ final int centerX = done.getWidth() / 2;
+ final int centerY = done.getHeight() / 2;
+ final int x = doneLocation[0] - rowLocation[0] + centerX;
+ final int y = doneLocation[1] - rowLocation[1] + centerY;
+ dismissPopups(x, y);
+ }
+
+ protected SwipeHelper.LongPressListener getNotificationLongClicker() {
+ return new SwipeHelper.LongPressListener() {
+ @Override
+ public boolean onLongPress(View v, final int x, final int y,
+ MenuItem item) {
+ if (!(v instanceof ExpandableNotificationRow)) {
+ return false;
+ }
+ if (v.getWindowToken() == null) {
+ Log.e(TAG, "Trying to show notification guts, but not attached to window");
+ return false;
+ }
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ bindGuts(row, item);
+ NotificationGuts guts = row.getGuts();
+
+ // Assume we are a status_bar_notification_row
+ if (guts == null) {
+ // This view has no guts. Examples are the more card or the dismiss all view
+ return false;
+ }
+
+ // Already showing?
+ if (guts.getVisibility() == View.VISIBLE) {
+ dismissPopups(x, y);
+ return false;
+ }
+
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_CONTROLS);
+
+ // ensure that it's laid but not visible until actually laid out
+ guts.setVisibility(View.INVISIBLE);
+ // Post to ensure the the guts are properly laid out.
+ guts.post(new Runnable() {
+ @Override
+ public void run() {
+ if (row.getWindowToken() == null) {
+ Log.e(TAG, "Trying to show notification guts, but not attached to "
+ + "window");
+ return;
+ }
+ dismissPopups(-1 /* x */, -1 /* y */, false /* resetGear */,
+ false /* animate */);
+ guts.setVisibility(View.VISIBLE);
+ final double horz = Math.max(guts.getWidth() - x, x);
+ final double vert = Math.max(guts.getHeight() - y, y);
+ final float r = (float) Math.hypot(horz, vert);
+ final Animator a
+ = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ a.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ // Move the notification view back over the gear
+ row.resetTranslation();
+ }
+ });
+ a.start();
+ guts.setExposed(true /* exposed */,
+ mState == StatusBarState.KEYGUARD /* needsFalsingProtection */);
+ row.closeRemoteInput();
+ mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+ mNotificationGutsExposed = guts;
+ mGutsMenuItem = item;
+ }
+ });
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns the exposed NotificationGuts or null if none are exposed.
+ */
+ public NotificationGuts getExposedGuts() {
+ return mNotificationGutsExposed;
+ }
+
+ public void dismissPopups() {
+ dismissPopups(-1 /* x */, -1 /* y */, true /* resetGear */, false /* animate */);
+ }
+
+ private void dismissPopups(int x, int y) {
+ dismissPopups(x, y, true /* resetGear */, false /* animate */);
+ }
+
+ public void dismissPopups(int x, int y, boolean resetGear, boolean animate) {
+ if (mNotificationGutsExposed != null) {
+ mNotificationGutsExposed.closeControls(x, y, true /* save */);
+ }
+ if (resetGear) {
+ mStackScroller.resetExposedGearView(animate, true /* force */);
+ }
+ }
+
+ @Override
+ public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) {
+ int msg = MSG_SHOW_RECENT_APPS;
+ mHandler.removeMessages(msg);
+ mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, fromHome ? 1 : 0).sendToTarget();
+ }
+
+ @Override
+ public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+ int msg = MSG_HIDE_RECENT_APPS;
+ mHandler.removeMessages(msg);
+ mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0,
+ triggeredFromHomeKey ? 1 : 0).sendToTarget();
+ }
+
+ @Override
+ public void toggleRecentApps() {
+ toggleRecents();
+ }
+
+ @Override
+ public void toggleSplitScreen() {
+ toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
+ }
+
+ @Override
+ public void preloadRecentApps() {
+ int msg = MSG_PRELOAD_RECENT_APPS;
+ mHandler.removeMessages(msg);
+ mHandler.sendEmptyMessage(msg);
+ }
+
+ @Override
+ public void cancelPreloadRecentApps() {
+ int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
+ mHandler.removeMessages(msg);
+ mHandler.sendEmptyMessage(msg);
+ }
+
+ @Override
+ public void dismissKeyboardShortcutsMenu() {
+ int msg = MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU;
+ mHandler.removeMessages(msg);
+ mHandler.sendEmptyMessage(msg);
+ }
+
+ @Override
+ public void toggleKeyboardShortcutsMenu(int deviceId) {
+ int msg = MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU;
+ mHandler.removeMessages(msg);
+ mHandler.obtainMessage(msg, deviceId, 0).sendToTarget();
+ }
+
+ /** Jumps to the next affiliated task in the group. */
+ public void showNextAffiliatedTask() {
+ int msg = MSG_SHOW_NEXT_AFFILIATED_TASK;
+ mHandler.removeMessages(msg);
+ mHandler.sendEmptyMessage(msg);
+ }
+
+ /** Jumps to the previous affiliated task in the group. */
+ public void showPreviousAffiliatedTask() {
+ int msg = MSG_SHOW_PREV_AFFILIATED_TASK;
+ mHandler.removeMessages(msg);
+ mHandler.sendEmptyMessage(msg);
+ }
+
+ protected void sendCloseSystemWindows(String reason) {
+ try {
+ ActivityManager.getService().closeSystemDialogs(reason);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /** Proxy for RecentsComponent */
+
+ protected void showRecents(boolean triggeredFromAltTab, boolean fromHome) {
+ if (mRecents != null) {
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+ mRecents.showRecents(triggeredFromAltTab, fromHome);
+ }
+ }
+
+ protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+ if (mRecents != null) {
+ mRecents.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+ }
+ }
+
+ protected void toggleRecents() {
+ if (mRecents != null) {
+ mRecents.toggleRecents(mDisplay);
+ }
+ }
+
+ protected void preloadRecents() {
+ if (mRecents != null) {
+ mRecents.preloadRecents();
+ }
+ }
+
+ protected void toggleKeyboardShortcuts(int deviceId) {
+ KeyboardShortcuts.toggle(mContext, deviceId);
+ }
+
+ protected void dismissKeyboardShortcuts() {
+ KeyboardShortcuts.dismiss();
+ }
+
+ protected void cancelPreloadingRecents() {
+ if (mRecents != null) {
+ mRecents.cancelPreloadingRecents();
+ }
+ }
+
+ protected void showRecentsNextAffiliatedTask() {
+ if (mRecents != null) {
+ mRecents.showNextAffiliatedTask();
+ }
+ }
+
+ protected void showRecentsPreviousAffiliatedTask() {
+ if (mRecents != null) {
+ mRecents.showPrevAffiliatedTask();
+ }
+ }
+
+ /**
+ * Save the current "public" (locked and secure) state of the lockscreen.
+ */
+ public void setLockscreenPublicMode(boolean publicMode, int userId) {
+ mLockscreenPublicMode.put(userId, publicMode);
+ }
+
+ public boolean isLockscreenPublicMode(int userId) {
+ return mLockscreenPublicMode.get(userId, false);
+ }
+
+ /**
+ * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
+ * "public" (secure & locked) mode?
+ */
+ public boolean userAllowsNotificationsInPublic(int userHandle) {
+ if (userHandle == UserHandle.USER_ALL) {
+ return true;
+ }
+
+ if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+ final boolean allowed = 0 != Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+ mUsersAllowingNotifications.append(userHandle, allowed);
+ return allowed;
+ }
+
+ return mUsersAllowingNotifications.get(userHandle);
+ }
+
+ /**
+ * Has the given user chosen to allow their private (full) notifications to be shown even
+ * when the lockscreen is in "public" (secure & locked) mode?
+ */
+ public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
+ if (userHandle == UserHandle.USER_ALL) {
+ return true;
+ }
+
+ if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+ final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle);
+ final boolean allowed = allowedByUser && allowedByDpm;
+ mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+ return allowed;
+ }
+
+ return mUsersAllowingPrivateNotifications.get(userHandle);
+ }
+
+ private boolean adminAllowsUnredactedNotifications(int userHandle) {
+ if (userHandle == UserHandle.USER_ALL) {
+ return true;
+ }
+ final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
+ userHandle);
+ return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
+ }
+
+ /**
+ * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
+ * If so, notifications should be hidden.
+ */
+ @Override // NotificationData.Environment
+ public boolean shouldHideNotifications(int userId) {
+ return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
+ || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
+ }
+
+ /**
+ * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
+ * package-specific override.
+ */
+ @Override // NotificationDate.Environment
+ public boolean shouldHideNotifications(String key) {
+ return isLockscreenPublicMode(mCurrentUserId)
+ && mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET;
+ }
+
+ /**
+ * Returns true if we're on a secure lockscreen.
+ */
+ @Override // NotificationData.Environment
+ public boolean isSecurelyLocked(int userId) {
+ return isLockscreenPublicMode(userId);
+ }
+
+ public void onNotificationClear(StatusBarNotification notification) {
+ try {
+ mBarService.onNotificationClear(
+ notification.getPackageName(),
+ notification.getTag(),
+ notification.getId(),
+ notification.getUserId());
+ } catch (android.os.RemoteException ex) {
+ // oh well
+ }
+ }
+
+ /**
+ * Called when the notification panel layouts
+ */
+ public void onPanelLaidOut() {
+ if (mState == StatusBarState.KEYGUARD) {
+ // Since the number of notifications is determined based on the height of the view, we
+ // need to update them.
+ int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
+ int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+ if (maxBefore != maxNotifications) {
+ updateRowStates();
+ }
+ }
+ }
+
+ protected boolean inflateViews(Entry entry, ViewGroup parent) {
+ PackageManager pmUser = getPackageManagerForUser(mContext,
+ entry.notification.getUser().getIdentifier());
+
+ final StatusBarNotification sbn = entry.notification;
+ boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
+ try {
+ entry.cacheContentViews(mContext, null, isLowPriority);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Unable to get notification remote views", e);
+ return false;
+ }
+
+ final RemoteViews contentView = entry.cachedContentView;
+ final RemoteViews bigContentView = entry.cachedBigContentView;
+ final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+ final RemoteViews publicContentView = entry.cachedPublicContentView;
+ final RemoteViews ambientContentView = entry.cachedAmbientContentView;
+
+ if (contentView == null) {
+ Log.v(TAG, "no contentView for: " + sbn.getNotification());
+ return false;
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, "publicContentView: " + publicContentView);
+ }
+
+ ExpandableNotificationRow row;
+
+ // Stash away previous user expansion state so we can restore it at
+ // the end.
+ boolean hasUserChangedExpansion = false;
+ boolean userExpanded = false;
+ boolean userLocked = false;
+
+ if (entry.row != null) {
+ row = entry.row;
+ hasUserChangedExpansion = row.hasUserChangedExpansion();
+ userExpanded = row.isUserExpanded();
+ userLocked = row.isUserLocked();
+ entry.reset();
+ if (hasUserChangedExpansion) {
+ row.setUserExpanded(userExpanded);
+ }
+ } else {
+ // create the row view
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
+ parent, false);
+ row.setExpansionLogger(this, entry.notification.getKey());
+ row.setGroupManager(mGroupManager);
+ row.setHeadsUpManager(mHeadsUpManager);
+ row.setRemoteInputController(mRemoteInputController);
+ row.setOnExpandClickListener(this);
+
+ // Get the app name.
+ // Note that Notification.Builder#bindHeaderAppName has similar logic
+ // but since this field is used in the guts, it must be accurate.
+ // Therefore we will only show the application label, or, failing that, the
+ // package name. No substitutions.
+ final String pkg = sbn.getPackageName();
+ String appname = pkg;
+ try {
+ final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS);
+ if (info != null) {
+ appname = String.valueOf(pmUser.getApplicationLabel(info));
+ }
+ } catch (NameNotFoundException e) {
+ // Do nothing
+ }
+ row.setAppName(appname);
+ }
+
+ bindDismissRunnable(row);
+ row.setIsLowPriority(isLowPriority);
+
+ // NB: the large icon is now handled entirely by the template
+
+ // bind the click event to the content area
+ NotificationContentView contentContainer = row.getPrivateLayout();
+ NotificationContentView contentContainerPublic = row.getPublicLayout();
+
+ row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ if (ENABLE_REMOTE_INPUT) {
+ row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ }
+
+ mNotificationClicker.register(row, sbn);
+
+ // set up the adaptive layout
+ View contentViewLocal = null;
+ View bigContentViewLocal = null;
+ View headsUpContentViewLocal = null;
+ View publicViewLocal = null;
+ View ambientViewLocal = null;
+ try {
+ contentViewLocal = contentView.apply(
+ sbn.getPackageContext(mContext),
+ contentContainer,
+ mOnClickHandler);
+ if (bigContentView != null) {
+ bigContentViewLocal = bigContentView.apply(
+ sbn.getPackageContext(mContext),
+ contentContainer,
+ mOnClickHandler);
+ }
+ if (headsUpContentView != null) {
+ headsUpContentViewLocal = headsUpContentView.apply(
+ sbn.getPackageContext(mContext),
+ contentContainer,
+ mOnClickHandler);
+ }
+ if (publicContentView != null) {
+ publicViewLocal = publicContentView.apply(
+ sbn.getPackageContext(mContext),
+ contentContainerPublic, mOnClickHandler);
+ }
+ if (ambientContentView != null) {
+ ambientViewLocal = ambientContentView.apply(
+ sbn.getPackageContext(mContext),
+ contentContainer, mOnClickHandler);
+ }
+
+ if (contentViewLocal != null) {
+ contentViewLocal.setIsRootNamespace(true);
+ contentContainer.setContractedChild(contentViewLocal);
+ }
+ if (bigContentViewLocal != null) {
+ bigContentViewLocal.setIsRootNamespace(true);
+ contentContainer.setExpandedChild(bigContentViewLocal);
+ }
+ if (headsUpContentViewLocal != null) {
+ headsUpContentViewLocal.setIsRootNamespace(true);
+ contentContainer.setHeadsUpChild(headsUpContentViewLocal);
+ }
+ if (publicViewLocal != null) {
+ publicViewLocal.setIsRootNamespace(true);
+ contentContainerPublic.setContractedChild(publicViewLocal);
+ }
+
+ if (ambientViewLocal != null) {
+ ambientViewLocal.setIsRootNamespace(true);
+ contentContainer.setAmbientChild(ambientViewLocal);
+ }
+ }
+ catch (RuntimeException e) {
+ final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
+ Log.e(TAG, "couldn't inflate view for notification " + ident, e);
+ return false;
+ }
+
+ // Extract target SDK version.
+ try {
+ ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+ entry.targetSdk = info.targetSdkVersion;
+ } catch (NameNotFoundException ex) {
+ Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+ }
+ entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
+
+ entry.row = row;
+ entry.row.setOnActivatedListener(this);
+ entry.row.setExpandable(bigContentViewLocal != null);
+
+ applyColorsAndBackgrounds(sbn, entry);
+
+ // Restore previous flags.
+ if (hasUserChangedExpansion) {
+ // Note: setUserExpanded() conveniently ignores calls with
+ // userExpanded=true if !isExpandable().
+ row.setUserExpanded(userExpanded);
+ }
+ row.setUserLocked(userLocked);
+ row.onNotificationUpdated(entry);
+ return true;
+ }
+
+ /**
+ * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
+ * via first-class API.
+ *
+ * TODO: Remove once enough apps specify remote inputs on their own.
+ */
+ private void processForRemoteInput(Notification n) {
+ if (!ENABLE_REMOTE_INPUT) return;
+
+ if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
+ (n.actions == null || n.actions.length == 0)) {
+ Notification.Action viableAction = null;
+ Notification.WearableExtender we = new Notification.WearableExtender(n);
+
+ List<Notification.Action> actions = we.getActions();
+ final int numActions = actions.size();
+
+ for (int i = 0; i < numActions; i++) {
+ Notification.Action action = actions.get(i);
+ if (action == null) {
+ continue;
+ }
+ RemoteInput[] remoteInputs = action.getRemoteInputs();
+ if (remoteInputs == null) {
+ continue;
+ }
+ for (RemoteInput ri : remoteInputs) {
+ if (ri.getAllowFreeFormInput()) {
+ viableAction = action;
+ break;
+ }
+ }
+ if (viableAction != null) {
+ break;
+ }
+ }
+
+ if (viableAction != null) {
+ Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
+ rebuilder.setActions(viableAction);
+ rebuilder.build(); // will rewrite n
+ }
+ }
+ }
+
+ public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
+ if (!isDeviceProvisioned()) return;
+
+ final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+ final boolean afterKeyguardGone = intent.isActivity()
+ && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+ mCurrentUserId);
+ dismissKeyguardThenExecute(new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ try {
+ intent.send(null, 0, null, null, null, null, getActivityOptions());
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending intent failed: " + e);
+
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
+ }
+ }
+ }.start();
+
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */, true /* delayed */);
+ visibilityChanged(false);
+
+ return true;
+ }
+ }, afterKeyguardGone);
+ }
+
+
+ private final class NotificationClicker implements View.OnClickListener {
+ private final int[] mTmpInt2 = new int[2];
+
+ @Override
+ 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) {
+ Log.e(TAG, "NotificationClicker called on an unclickable notification,");
+ return;
+ }
+
+ // Check if the notification is displaying the gear, if so slide notification back
+ if (row.getSettingsRow() != null && row.getSettingsRow().isVisible()) {
+ row.animateTranslateNotification(0);
+ return;
+ }
+
+ Notification notification = sbn.getNotification();
+ final PendingIntent intent = notification.contentIntent != null
+ ? notification.contentIntent
+ : notification.fullScreenIntent;
+ final String notificationKey = sbn.getKey();
+
+ // Mark notification for one frame.
+ row.setJustClicked(true);
+ DejankUtils.postAfterTraversal(new Runnable() {
+ @Override
+ public void run() {
+ row.setJustClicked(false);
+ }
+ });
+
+ final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+ final boolean afterKeyguardGone = intent.isActivity()
+ && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+ mCurrentUserId);
+ dismissKeyguardThenExecute(new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+ // Release the HUN notification to the shade.
+
+ if (isPanelFullyCollapsed()) {
+ HeadsUpManager.setIsClickedNotification(row, true);
+ }
+ //
+ // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+ // become canceled shortly by NoMan, but we can't assume that.
+ mHeadsUpManager.releaseImmediately(notificationKey);
+ }
+ StatusBarNotification parentToCancel = null;
+ if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+ StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn)
+ .getStatusBarNotification();
+ if (shouldAutoCancel(summarySbn)) {
+ parentToCancel = summarySbn;
+ }
+ }
+ final StatusBarNotification parentToCancelFinal = parentToCancel;
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ if (intent != null) {
+ // If we are launching a work activity and require to launch
+ // separate work challenge, we defer the activity action and cancel
+ // notification until work challenge is unlocked.
+ if (intent.isActivity()) {
+ final int userId = intent.getCreatorUserHandle()
+ .getIdentifier();
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+ && mKeyguardManager.isDeviceLocked(userId)) {
+ boolean canBypass = false;
+ try {
+ canBypass = ActivityManager.getService()
+ .canBypassWorkChallenge(intent);
+ } catch (RemoteException e) {
+ }
+ // For direct-boot aware activities, they can be shown when
+ // the device is still locked without triggering the work
+ // challenge.
+ if ((!canBypass) && startWorkChallengeIfNecessary(userId,
+ intent.getIntentSender(), notificationKey)) {
+ // Show work challenge, do not run PendingIntent and
+ // remove notification
+ return;
+ }
+ }
+ }
+ try {
+ intent.send(null, 0, null, null, null, null,
+ getActivityOptions());
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending contentIntent failed: " + e);
+
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
+ }
+ }
+
+ try {
+ mBarService.onNotificationClick(notificationKey);
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ if (parentToCancelFinal != null) {
+ // We have to post it to the UI thread for synchronization
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Runnable removeRunnable = new Runnable() {
+ @Override
+ public void run() {
+ performRemoveNotification(parentToCancelFinal);
+ }
+ };
+ if (isCollapsing()) {
+ // To avoid lags we're only performing the remove
+ // after the shade was collapsed
+ addPostCollapseAction(removeRunnable);
+ } else {
+ removeRunnable.run();
+ }
+ }
+ });
+ }
+ }
+ }.start();
+
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */, true /* delayed */);
+ visibilityChanged(false);
+
+ return true;
+ }
+ }, afterKeyguardGone);
+ }
+
+ private boolean shouldAutoCancel(StatusBarNotification sbn) {
+ int flags = sbn.getNotification().flags;
+ if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
+ return false;
+ }
+ if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+ Notification notification = sbn.getNotification();
+ if (notification.contentIntent != null || notification.fullScreenIntent != null) {
+ row.setOnClickListener(this);
+ } else {
+ row.setOnClickListener(null);
+ }
+ }
+ }
+
+ protected Bundle getActivityOptions() {
+ // Anything launched from the notification shade should always go into the
+ // fullscreen stack.
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+ return options.toBundle();
+ }
+
+ protected void visibilityChanged(boolean visible) {
+ if (mVisible != visible) {
+ mVisible = visible;
+ if (!visible) {
+ dismissPopups();
+ }
+ }
+ updateVisibleToUser();
+ }
+
+ protected void updateVisibleToUser() {
+ boolean oldVisibleToUser = mVisibleToUser;
+ mVisibleToUser = mVisible && mDeviceInteractive;
+
+ if (oldVisibleToUser != mVisibleToUser) {
+ handleVisibleToUserChanged(mVisibleToUser);
+ }
+ }
+
+ /**
+ * Clear Buzz/Beep/Blink.
+ */
+ public void clearNotificationEffects() {
+ try {
+ mBarService.clearNotificationEffects();
+ } catch (RemoteException e) {
+ // Won't fail unless the world has ended.
+ }
+ }
+
+ /**
+ * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
+ * about the failure.
+ *
+ * WARNING: this will call back into us. Don't hold any locks.
+ */
+ void handleNotificationError(StatusBarNotification n, String message) {
+ removeNotification(n.getKey(), null);
+ try {
+ mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
+ n.getInitialPid(), message, n.getUserId());
+ } catch (RemoteException ex) {
+ // The end is nigh.
+ }
+ }
+
+ protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
+ NotificationData.Entry entry = mNotificationData.remove(key, ranking);
+ if (entry == null) {
+ Log.w(TAG, "removeNotification for unknown key: " + key);
+ return null;
+ }
+ updateNotifications();
+ return entry.notification;
+ }
+
+ protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
+ if (DEBUG) {
+ Log.d(TAG, "createNotificationViews(notification=" + sbn);
+ }
+ NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ try {
+ entry.createIcons(mContext, sbn);
+ } catch (NotificationData.IconException exception) {
+ handleNotificationError(sbn, exception.getMessage());
+ }
+
+ // Construct the expanded view.
+ if (!inflateViews(entry, mStackScroller)) {
+ handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
+ return null;
+ }
+ return entry;
+ }
+
+ protected void addNotificationViews(Entry entry, RankingMap ranking) {
+ if (entry == null) {
+ return;
+ }
+ // Add the expanded view and icon.
+ mNotificationData.add(entry, ranking);
+ updateNotifications();
+ }
+
+ /**
+ * Updates expanded, dimmed and locked states of notification rows.
+ */
+ protected void updateRowStates() {
+ final int N = mStackScroller.getChildCount();
+
+ int visibleNotifications = 0;
+ boolean onKeyguard = mState == StatusBarState.KEYGUARD;
+ int maxNotifications = -1;
+ if (onKeyguard) {
+ maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+ }
+ mStackScroller.setMaxDisplayedNotifications(maxNotifications);
+ Stack<ExpandableNotificationRow> stack = new Stack<>();
+ for (int i = N - 1; i >= 0; i--) {
+ View child = mStackScroller.getChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ stack.push((ExpandableNotificationRow) child);
+ }
+ while(!stack.isEmpty()) {
+ ExpandableNotificationRow row = stack.pop();
+ NotificationData.Entry entry = row.getEntry();
+ boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification);
+ if (onKeyguard) {
+ row.setOnKeyguard(true);
+ } else {
+ row.setOnKeyguard(false);
+ row.setSystemExpanded(visibleNotifications == 0 && !childNotification);
+ }
+ entry.row.setShowAmbient(isDozing());
+ int userId = entry.notification.getUserId();
+ boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
+ entry.notification) && !entry.row.isRemoved();
+ boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
+ if (suppressedSummary
+ || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications)
+ || (onKeyguard && !showOnKeyguard)) {
+ entry.row.setVisibility(View.GONE);
+ } else {
+ boolean wasGone = entry.row.getVisibility() == View.GONE;
+ if (wasGone) {
+ entry.row.setVisibility(View.VISIBLE);
+ }
+ if (!childNotification && !entry.row.isRemoved()) {
+ if (wasGone) {
+ // notify the scroller of a child addition
+ mStackScroller.generateAddAnimation(entry.row,
+ !showOnKeyguard /* fromMoreCard */);
+ }
+ visibleNotifications++;
+ }
+ }
+ if (row.isSummaryWithChildren()) {
+ List<ExpandableNotificationRow> notificationChildren =
+ row.getNotificationChildren();
+ int size = notificationChildren.size();
+ for (int i = size - 1; i >= 0; i--) {
+ stack.push(notificationChildren.get(i));
+ }
+ }
+ }
+
+ mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
+ mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
+ mStackScroller.changeViewPosition(mNotificationShelf, mStackScroller.getChildCount() - 3);
+ }
+
+ public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
+ return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
+ }
+
+ // extended in StatusBar
+ protected void setShowLockscreenNotifications(boolean show) {
+ mShowLockscreenNotifications = show;
+ }
+
+ protected void setLockScreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
+ mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
+ }
+
+ private void updateLockscreenNotificationSetting() {
+ final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1,
+ mCurrentUserId) != 0;
+ final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+ null /* admin */, mCurrentUserId);
+ final boolean allowedByDpm = (dpmFlags
+ & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+
+ setShowLockscreenNotifications(show && allowedByDpm);
+
+ if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
+ final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
+ 0,
+ mCurrentUserId) != 0;
+ final boolean remoteInputDpm =
+ (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
+
+ setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm);
+ } else {
+ setLockScreenAllowRemoteInput(false);
+ }
+ }
+
+ public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
+ if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
+
+ final String key = notification.getKey();
+ Entry entry = mNotificationData.get(key);
+ if (entry == null) {
+ return;
+ } else {
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+ mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
+ }
+
+ Notification n = notification.getNotification();
+ mNotificationData.updateRanking(ranking);
+
+ boolean applyInPlace;
+ try {
+ applyInPlace = entry.cacheContentViews(mContext, notification.getNotification(),
+ mNotificationData.isAmbient(key));
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Unable to get notification remote views", e);
+ applyInPlace = false;
+ }
+ boolean shouldPeek = shouldPeek(entry, notification);
+ boolean alertAgain = alertAgain(entry, n);
+ if (DEBUG) {
+ Log.d(TAG, "applyInPlace=" + applyInPlace
+ + " shouldPeek=" + shouldPeek
+ + " alertAgain=" + alertAgain);
+ }
+
+ final StatusBarNotification oldNotification = entry.notification;
+ entry.notification = notification;
+ mGroupManager.onEntryUpdated(entry, oldNotification);
+
+ boolean updateSuccessful = false;
+ try {
+ if (applyInPlace) {
+ if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
+ try {
+ entry.updateIcons(mContext, n);
+ updateNotificationViews(entry, notification);
+ updateSuccessful = true;
+ } catch (RuntimeException e) {
+ // It failed to apply cleanly.
+ Log.w(TAG, "Couldn't reapply views for package " +
+ notification.getPackageName(), e);
+ }
+ }
+ if (!updateSuccessful) {
+ entry.updateIcons(mContext, n);
+ if (!inflateViews(entry, mStackScroller)) {
+ handleNotificationError(notification, "Couldn't update remote views for: "
+ + notification);
+ }
+ }
+ } catch (NotificationData.IconException e) {
+ handleNotificationError(notification, e.getMessage());
+ }
+ updateHeadsUp(key, entry, shouldPeek, alertAgain);
+ updateNotifications();
+
+ if (!notification.isClearable()) {
+ // The user may have performed a dismiss action on the notification, since it's
+ // not clearable we should snap it back.
+ mStackScroller.snapViewIfNeeded(entry.row);
+ }
+
+ if (DEBUG) {
+ // Is this for you?
+ boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
+ Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+ }
+
+ setAreThereNotifications();
+ }
+
+ private void updateNotificationViews(Entry entry, StatusBarNotification sbn) {
+ final RemoteViews contentView = entry.cachedContentView;
+ final RemoteViews bigContentView = entry.cachedBigContentView;
+ final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+ final RemoteViews publicContentView = entry.cachedPublicContentView;
+
+ // Reapply the RemoteViews
+ contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
+ if (bigContentView != null && entry.getExpandedContentView() != null) {
+ bigContentView.reapply(sbn.getPackageContext(mContext),
+ entry.getExpandedContentView(),
+ mOnClickHandler);
+ }
+ View headsUpChild = entry.getHeadsUpContentView();
+ if (headsUpContentView != null && headsUpChild != null) {
+ headsUpContentView.reapply(sbn.getPackageContext(mContext),
+ headsUpChild, mOnClickHandler);
+ }
+ if (publicContentView != null && entry.getPublicContentView() != null) {
+ publicContentView.reapply(sbn.getPackageContext(mContext),
+ entry.getPublicContentView(), mOnClickHandler);
+ }
+ // update the contentIntent
+ mNotificationClicker.register(entry.row, sbn);
+
+ entry.row.onNotificationUpdated(entry);
+ entry.row.resetHeight();
+ }
+
+ protected void updatePublicContentView(Entry entry,
+ StatusBarNotification sbn) {
+ final RemoteViews publicContentView = entry.cachedPublicContentView;
+ View inflatedView = entry.getPublicContentView();
+ if (entry.autoRedacted && publicContentView != null && inflatedView != null) {
+ final boolean disabledByPolicy =
+ !adminAllowsUnredactedNotifications(entry.notification.getUserId());
+ String notificationHiddenText = mContext.getString(disabledByPolicy
+ ? com.android.internal.R.string.notification_hidden_by_policy_text
+ : com.android.internal.R.string.notification_hidden_text);
+ TextView titleView = (TextView) inflatedView.findViewById(android.R.id.title);
+ if (titleView != null
+ && !titleView.getText().toString().equals(notificationHiddenText)) {
+ publicContentView.setTextViewText(android.R.id.title, notificationHiddenText);
+ publicContentView.reapply(sbn.getPackageContext(mContext),
+ inflatedView, mOnClickHandler);
+ entry.row.onNotificationUpdated(entry);
+ }
+ }
+ }
+
+ protected void notifyHeadsUpScreenOff() {
+ maybeEscalateHeadsUp();
+ }
+
+ private boolean alertAgain(Entry oldEntry, Notification newNotification) {
+ return oldEntry == null || !oldEntry.hasInterrupted()
+ || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
+ }
+
+ protected boolean shouldPeek(Entry entry) {
+ return shouldPeek(entry, entry.notification);
+ }
+
+ protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
+ if (!mUseHeadsUp || isDeviceInVrMode()) {
+ return false;
+ }
+
+ if (mNotificationData.shouldFilterOut(sbn)) {
+ if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
+ return false;
+ }
+
+ boolean inUse = mPowerManager.isScreenOn();
+ try {
+ inUse = inUse && !mDreamManager.isDreaming();
+ } catch (RemoteException e) {
+ Log.d(TAG, "failed to query dream manager", e);
+ }
+
+ if (!inUse && !isDozing()) {
+ if (DEBUG) {
+ Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+ return false;
+ }
+
+ if (entry.hasJustLaunchedFullScreenIntent()) {
+ if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
+ return false;
+ }
+
+ if (isSnoozedPackage(sbn)) {
+ if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
+ return false;
+ }
+
+ if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) {
+ if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
+ return false;
+ }
+
+ if (sbn.getNotification().fullScreenIntent != null) {
+ if (mAccessibilityManager.isTouchExplorationEnabled()) {
+ if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
+ return false;
+ } else {
+ // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
+ return !mStatusBarKeyguardViewManager.isShowing()
+ || mStatusBarKeyguardViewManager.isOccluded();
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @return Whether the security bouncer from Keyguard is showing.
+ */
+ public boolean isBouncerShowing() {
+ return mBouncerShowing;
+ }
+
+ /**
+ * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then
+ * return PackageManager for mContext
+ */
+ public static PackageManager getPackageManagerForUser(Context context, int userId) {
+ Context contextForUser = context;
+ // UserHandle defines special userId as negative values, e.g. USER_ALL
+ if (userId >= 0) {
+ try {
+ // Create a context for the correct user so if a package isn't installed
+ // for user 0 we can still load information about the package.
+ contextForUser =
+ context.createPackageContextAsUser(context.getPackageName(),
+ Context.CONTEXT_RESTRICTED,
+ new UserHandle(userId));
+ } catch (NameNotFoundException e) {
+ // Shouldn't fail to find the package name for system ui.
+ }
+ }
+ return contextForUser.getPackageManager();
+ }
+
+ @Override
+ public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+ try {
+ mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+ }
+
+ public boolean isKeyguardSecure() {
+ if (mStatusBarKeyguardViewManager == null) {
+ // startKeyguard() hasn't been called yet, so we don't know.
+ // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this
+ // value onVisibilityChanged().
+ Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false",
+ new Throwable());
+ return false;
+ }
+ return mStatusBarKeyguardViewManager.isSecure();
+ }
+
+ @Override
+ public void showAssistDisclosure() {
+ if (mAssistManager != null) {
+ mAssistManager.showDisclosure();
+ }
+ }
+
+ @Override
+ public void startAssist(Bundle args) {
+ if (mAssistManager != null) {
+ mAssistManager.startAssist(args);
+ }
+ }
+ // End Extra BaseStatusBarMethods.
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index a0425e6..46ca3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import android.animation.ArgbEvaluator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -25,8 +24,6 @@
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -64,7 +61,7 @@
public static final int DEFAULT_ICON_TINT = Color.WHITE;
private Context mContext;
- private PhoneStatusBar mPhoneStatusBar;
+ private StatusBar mStatusBar;
private DemoStatusIcons mDemoStatusIcons;
private LinearLayout mSystemIconArea;
@@ -100,11 +97,11 @@
private final ArraySet<String> mIconBlacklist = new ArraySet<>();
public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar,
- PhoneStatusBar phoneStatusBar) {
+ StatusBar phoneStatusBar) {
super(context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
mContext = context;
- mPhoneStatusBar = phoneStatusBar;
+ mStatusBar = phoneStatusBar;
mSystemIconArea = (LinearLayout) statusBar.findViewById(R.id.system_icon_area);
mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
@@ -405,11 +402,11 @@
.withEndAction(null);
// Synchronize the motion with the Keyguard fading if necessary.
- if (mPhoneStatusBar.isKeyguardFadingAway()) {
+ if (mStatusBar.isKeyguardFadingAway()) {
v.animate()
- .setDuration(mPhoneStatusBar.getKeyguardFadingAwayDuration())
+ .setDuration(mStatusBar.getKeyguardFadingAwayDuration())
.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
- .setStartDelay(mPhoneStatusBar.getKeyguardFadingAwayDelay())
+ .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 2e279b2..8d5d890 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -70,7 +70,7 @@
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
- protected PhoneStatusBar mPhoneStatusBar;
+ protected StatusBar mStatusBar;
private ScrimController mScrimController;
private FingerprintUnlockController mFingerprintUnlockController;
@@ -103,12 +103,12 @@
mLockPatternUtils = lockPatternUtils;
}
- public void registerStatusBar(PhoneStatusBar phoneStatusBar,
+ public void registerStatusBar(StatusBar statusBar,
ViewGroup container, StatusBarWindowManager statusBarWindowManager,
ScrimController scrimController,
FingerprintUnlockController fingerprintUnlockController,
DismissCallbackRegistry dismissCallbackRegistry) {
- mPhoneStatusBar = phoneStatusBar;
+ mStatusBar = statusBar;
mContainer = container;
mStatusBarWindowManager = statusBarWindowManager;
mScrimController = scrimController;
@@ -136,10 +136,10 @@
if (mBouncer.needsFullscreenBouncer()) {
// The keyguard might be showing (already). So we need to hide it.
- mPhoneStatusBar.hideKeyguard();
+ mStatusBar.hideKeyguard();
mBouncer.show(true /* resetSecuritySelection */);
} else {
- mPhoneStatusBar.showKeyguard();
+ mStatusBar.showKeyguard();
if (hideBouncerWhenShowing) {
mBouncer.hide(false /* destroyView */);
mBouncer.prepare();
@@ -180,8 +180,8 @@
public void reset(boolean hideBouncerWhenShowing) {
if (mShowing) {
if (mOccluded) {
- mPhoneStatusBar.hideKeyguard();
- mPhoneStatusBar.stopWaitingForKeyguardExit();
+ mStatusBar.hideKeyguard();
+ mStatusBar.stopWaitingForKeyguardExit();
mBouncer.hide(false /* destroyView */);
} else {
showBouncerOrKeyguard(hideBouncerWhenShowing);
@@ -192,12 +192,12 @@
}
public void onStartedGoingToSleep() {
- mPhoneStatusBar.onStartedGoingToSleep();
+ mStatusBar.onStartedGoingToSleep();
}
public void onFinishedGoingToSleep() {
mDeviceInteractive = false;
- mPhoneStatusBar.onFinishedGoingToSleep();
+ mStatusBar.onFinishedGoingToSleep();
mBouncer.onScreenTurnedOff();
}
@@ -205,13 +205,13 @@
Trace.beginSection("StatusBarKeyguardViewManager#onStartedWakingUp");
mDeviceInteractive = true;
mDeviceWillWakeUp = false;
- mPhoneStatusBar.onStartedWakingUp();
+ mStatusBar.onStartedWakingUp();
Trace.endSection();
}
public void onScreenTurningOn() {
Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurningOn");
- mPhoneStatusBar.onScreenTurningOn();
+ mStatusBar.onScreenTurningOn();
Trace.endSection();
}
@@ -228,7 +228,7 @@
true /* skipFirstFrame */);
updateStates();
}
- mPhoneStatusBar.onScreenTurnedOn();
+ mStatusBar.onScreenTurnedOn();
Trace.endSection();
}
@@ -240,7 +240,7 @@
public void onScreenTurnedOff() {
mScreenTurnedOn = false;
- mPhoneStatusBar.onScreenTurnedOff();
+ mStatusBar.onScreenTurnedOff();
}
public void notifyDeviceWakeUpRequested() {
@@ -257,12 +257,12 @@
public void setOccluded(boolean occluded, boolean animate) {
if (occluded != mOccluded) {
- mPhoneStatusBar.onKeyguardOccludedChanged(occluded);
+ mStatusBar.onKeyguardOccludedChanged(occluded);
}
if (occluded && !mOccluded && mShowing) {
- if (mPhoneStatusBar.isInLaunchTransition()) {
+ if (mStatusBar.isInLaunchTransition()) {
mOccluded = true;
- mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */,
+ mStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */,
new Runnable() {
@Override
public void run() {
@@ -275,7 +275,7 @@
}
mOccluded = occluded;
if (mShowing) {
- mPhoneStatusBar.updateMediaMetaData(false, animate && !occluded);
+ mStatusBar.updateMediaMetaData(false, animate && !occluded);
}
mStatusBarWindowManager.setKeyguardOccluded(occluded);
@@ -283,7 +283,7 @@
// a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(false /* hideBouncerWhenShowing*/);
if (animate && !occluded && mShowing) {
- mPhoneStatusBar.animateKeyguardUnoccluding();
+ mStatusBar.animateKeyguardUnoccluding();
}
}
@@ -318,8 +318,8 @@
long uptimeMillis = SystemClock.uptimeMillis();
long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
- if (mPhoneStatusBar.isInLaunchTransition() ) {
- mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
+ if (mStatusBar.isInLaunchTransition() ) {
+ mStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
@Override
public void run() {
mStatusBarWindowManager.setKeyguardShowing(false);
@@ -327,14 +327,14 @@
mBouncer.hide(true /* destroyView */);
updateStates();
mScrimController.animateKeyguardFadingOut(
- PhoneStatusBar.FADE_KEYGUARD_START_DELAY,
- PhoneStatusBar.FADE_KEYGUARD_DURATION, null,
+ StatusBar.FADE_KEYGUARD_START_DELAY,
+ StatusBar.FADE_KEYGUARD_DURATION, null,
false /* skipFirstFrame */);
}
}, new Runnable() {
@Override
public void run() {
- mPhoneStatusBar.hideKeyguard();
+ mStatusBar.hideKeyguard();
mStatusBarWindowManager.setKeyguardFadingAway(false);
mViewMediatorCallback.keyguardGone();
executeAfterKeyguardGoneAction();
@@ -348,19 +348,19 @@
delay = 0;
fadeoutDuration = 240;
}
- mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
+ mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
mFingerprintUnlockController.startKeyguardFadingAway();
mBouncer.hide(true /* destroyView */);
updateStates();
if (wakeUnlockPulsing) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
- mPhoneStatusBar.fadeKeyguardWhilePulsing();
+ mStatusBar.fadeKeyguardWhilePulsing();
animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- mPhoneStatusBar::hideKeyguard, false /* skipFirstFrame */);
+ mStatusBar::hideKeyguard, false /* skipFirstFrame */);
} else {
mFingerprintUnlockController.startKeyguardFadingAway();
- mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
- boolean staying = mPhoneStatusBar.hideKeyguard();
+ mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
+ boolean staying = mStatusBar.hideKeyguard();
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
@@ -379,7 +379,7 @@
}
} else {
mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
- mPhoneStatusBar.finishKeyguardFadingAway();
+ mStatusBar.finishKeyguardFadingAway();
}
}
mStatusBarWindowManager.setKeyguardShowing(false);
@@ -408,7 +408,7 @@
}
mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
100);
- mPhoneStatusBar.finishKeyguardFadingAway();
+ mStatusBar.finishKeyguardFadingAway();
mFingerprintUnlockController.finishKeyguardFadingAway();
WindowManagerGlobal.getInstance().trimMemory(
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
@@ -438,7 +438,7 @@
* Dismisses the keyguard by going to the next screen or making it gone.
*/
public void dismissAndCollapse() {
- mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, true, false, true);
+ mStatusBar.executeRunnableDismissingKeyguard(null, null, true, false, true);
}
public void dismiss() {
@@ -466,7 +466,7 @@
*/
public boolean onBackPressed() {
if (mBouncer.isShowing()) {
- mPhoneStatusBar.endAffordanceLaunch();
+ mStatusBar.endAffordanceLaunch();
reset(true /* hideBouncerWhenShowing */);
return true;
}
@@ -478,8 +478,8 @@
}
private long getNavBarShowDelay() {
- if (mPhoneStatusBar.isKeyguardFadingAway()) {
- return mPhoneStatusBar.getKeyguardFadingAwayDelay();
+ if (mStatusBar.isKeyguardFadingAway()) {
+ return mStatusBar.getKeyguardFadingAwayDelay();
} else {
// Keyguard is not going away, thus we are showing the navigation bar because the
@@ -491,7 +491,7 @@
private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() {
@Override
public void run() {
- mPhoneStatusBar.getNavigationBarView().setVisibility(View.VISIBLE);
+ mStatusBar.getNavigationBarView().getRootView().setVisibility(View.VISIBLE);
}
};
@@ -516,7 +516,7 @@
boolean navBarVisible = isNavBarVisible();
boolean lastNavBarVisible = getLastNavBarVisible();
if (navBarVisible != lastNavBarVisible || mFirstUpdate) {
- if (mPhoneStatusBar.getNavigationBarView() != null) {
+ if (mStatusBar.getNavigationBarView() != null) {
if (navBarVisible) {
long delay = getNavBarShowDelay();
if (delay == 0) {
@@ -527,14 +527,14 @@
}
} else {
mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
- mPhoneStatusBar.getNavigationBarView().setVisibility(View.GONE);
+ mStatusBar.getNavigationBarView().getRootView().setVisibility(View.GONE);
}
}
}
if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
- mPhoneStatusBar.setBouncerShowing(bouncerShowing);
+ mStatusBar.setBouncerShowing(bouncerShowing);
mScrimController.setBouncerShowing(bouncerShowing);
}
@@ -553,7 +553,7 @@
mLastBouncerDismissible = bouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
- mPhoneStatusBar.onKeyguardViewManagerStatesUpdated();
+ mStatusBar.onKeyguardViewManagerStatesUpdated();
}
/**
@@ -583,11 +583,11 @@
}
public boolean shouldDisableWindowAnimationsForUnlock() {
- return mPhoneStatusBar.isInLaunchTransition();
+ return mStatusBar.isInLaunchTransition();
}
public boolean isGoingToNotificationShade() {
- return mPhoneStatusBar.isGoingToNotificationShade();
+ return mStatusBar.isGoingToNotificationShade();
}
public boolean isSecure(int userId) {
@@ -595,11 +595,11 @@
}
public void keyguardGoingAway() {
- mPhoneStatusBar.keyguardGoingAway();
+ mStatusBar.keyguardGoingAway();
}
public void animateCollapsePanels(float speedUpFactor) {
- mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
+ mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
false /* delayed */, speedUpFactor);
}
@@ -616,6 +616,6 @@
}
public ViewRootImpl getViewRootImpl() {
- return mPhoneStatusBar.getStatusBarView().getViewRootImpl();
+ return mStatusBar.getStatusBarView().getViewRootImpl();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 0660054..16999b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -25,7 +25,6 @@
import android.os.Binder;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.os.Trace;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
@@ -34,7 +33,6 @@
import com.android.keyguard.R;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
@@ -140,7 +138,7 @@
private void applyFocusableFlag(State state) {
boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
- || BaseStatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
+ || StatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
@@ -385,7 +383,7 @@
boolean backdropShowing;
/**
- * The {@link BaseStatusBar} state from the status bar.
+ * The {@link StatusBar} state from the status bar.
*/
int statusBarState;
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..7c42d00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -29,11 +29,11 @@
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;
import android.os.IBinder;
-import android.os.Trace;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.InputQueue;
@@ -55,7 +55,6 @@
import com.android.internal.widget.FloatingToolbar;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -63,7 +62,7 @@
public class StatusBarWindowView extends FrameLayout {
public static final String TAG = "StatusBarWindowView";
- public static final boolean DEBUG = BaseStatusBar.DEBUG;
+ public static final boolean DEBUG = StatusBar.DEBUG;
private DragDownHelper mDragDownHelper;
private NotificationStackScrollLayout mStackScrollLayout;
@@ -73,7 +72,7 @@
private int mRightInset = 0;
private int mLeftInset = 0;
- private PhoneStatusBar mService;
+ private StatusBar mService;
private final Paint mTransparentSrcPaint = new Paint();
private FalsingManager mFalsingManager;
@@ -172,7 +171,7 @@
}
}
- public void setService(PhoneStatusBar service) {
+ public void setService(StatusBar service) {
mService = service;
mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
}
@@ -223,7 +222,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 +263,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..5ab99e9 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
@@ -125,14 +125,14 @@
public void setMobileDataIndicators(final IconState statusIcon, final IconState qsIcon,
final int statusType, final int qsType,final boolean activityIn,
final boolean activityOut, final String typeContentDescription,
- final String description, final boolean isWide, final int subId) {
+ final String description, final boolean isWide, final int subId, boolean roaming) {
post(new Runnable() {
@Override
public void run() {
for (SignalCallback signalCluster : mSignalCallbacks) {
signalCluster.setMobileDataIndicators(statusIcon, qsIcon, statusType, qsType,
activityIn, activityOut, typeContentDescription, description, isWide,
- subId);
+ subId, roaming);
}
}
});
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/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index a8d8f60..8c805fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -37,7 +37,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -90,7 +90,7 @@
private final int mStatusBarHeight;
private final Context mContext;
private final NotificationGroupManager mGroupManager;
- private PhoneStatusBar mBar;
+ private StatusBar mBar;
private int mSnoozeLengthMs;
private ContentObserver mSettingsObserver;
private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
@@ -162,7 +162,7 @@
mIsObserving = shouldObserve;
}
- public void setBar(PhoneStatusBar bar) {
+ public void setBar(StatusBar bar) {
mBar = bar;
}
@@ -170,7 +170,7 @@
mListeners.add(listener);
}
- public PhoneStatusBar getBar() {
+ public StatusBar getBar() {
return mBar;
}
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/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 45cfbdc..882902e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -116,18 +116,18 @@
mOnClickListener = onClickListener;
}
- public void loadAsync(String uri) {
- new AsyncTask<String, Void, Drawable>() {
+ public void loadAsync(Icon icon) {
+ new AsyncTask<Icon, Void, Drawable>() {
@Override
- protected Drawable doInBackground(String... params) {
- return Icon.createWithContentUri(params[0]).loadDrawable(mContext);
+ protected Drawable doInBackground(Icon... params) {
+ return params[0].loadDrawable(mContext);
}
@Override
protected void onPostExecute(Drawable drawable) {
setImageDrawable(drawable);
}
- }.execute(uri);
+ }.execute(icon);
}
@Override
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/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 83463e2..03c46e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -226,10 +226,8 @@
final boolean dataDisabled = mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED
&& mCurrentState.userSetup;
- // Show icon in QS when we are connected or need to show roaming or data is disabled.
- boolean showDataIcon = mCurrentState.dataConnected
- || mCurrentState.iconGroup == TelephonyIcons.ROAMING
- || dataDisabled;
+ // Show icon in QS when we are connected or data is disabled.
+ boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;
IconState statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,
getCurrentIconId(), contentDescription);
@@ -249,13 +247,11 @@
boolean activityOut = mCurrentState.dataConnected
&& !mCurrentState.carrierNetworkChangeMode
&& mCurrentState.activityOut;
- showDataIcon &= mCurrentState.isDefault
- || mCurrentState.iconGroup == TelephonyIcons.ROAMING
- || dataDisabled;
+ showDataIcon &= mCurrentState.isDefault || dataDisabled;
int typeIcon = showDataIcon ? icons.mDataType : 0;
callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
activityIn, activityOut, dataContentDescription, description, icons.mIsWide,
- mSubscriptionInfo.getSubscriptionId());
+ mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming);
}
@Override
@@ -405,10 +401,9 @@
mCurrentState.dataConnected = mCurrentState.connected
&& mDataState == TelephonyManager.DATA_CONNECTED;
+ mCurrentState.roaming = isRoaming();
if (isCarrierNetworkChangeActive()) {
mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
- } else if (isRoaming()) {
- mCurrentState.iconGroup = TelephonyIcons.ROAMING;
} else if (isDataDisabled()) {
mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
}
@@ -541,6 +536,7 @@
boolean carrierNetworkChangeMode;
boolean isDefault;
boolean userSetup;
+ boolean roaming;
@Override
public void copyFrom(State s) {
@@ -555,6 +551,7 @@
airplaneMode = state.airplaneMode;
carrierNetworkChangeMode = state.carrierNetworkChangeMode;
userSetup = state.userSetup;
+ roaming = state.roaming;
}
@Override
@@ -565,6 +562,7 @@
builder.append("networkName=").append(networkName).append(',');
builder.append("networkNameData=").append(networkNameData).append(',');
builder.append("dataConnected=").append(dataConnected).append(',');
+ builder.append("roaming=").append(roaming).append(',');
builder.append("isDefault=").append(isDefault).append(',');
builder.append("isEmergency=").append(isEmergency).append(',');
builder.append("airplaneMode=").append(airplaneMode).append(',');
@@ -584,7 +582,8 @@
&& ((MobileState) o).airplaneMode == airplaneMode
&& ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
&& ((MobileState) o).userSetup == userSetup
- && ((MobileState) o).isDefault == isDefault;
+ && ((MobileState) o).isDefault == isDefault
+ && ((MobileState) o).roaming == roaming;
}
}
}
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..eb47a3c 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,
@@ -47,7 +53,7 @@
default void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId) {}
+ String description, boolean isWide, int subId, boolean roaming) {}
default void setSubs(List<SubscriptionInfo> subs) {}
default void setNoSims(boolean show) {}
@@ -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..d7c919d 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 :
@@ -787,9 +822,12 @@
datatype.equals("h") ? TelephonyIcons.H :
datatype.equals("lte") ? TelephonyIcons.LTE :
datatype.equals("lte+") ? TelephonyIcons.LTE_PLUS :
- datatype.equals("roam") ? TelephonyIcons.ROAMING :
+ datatype.equals("dis") ? TelephonyIcons.DATA_DISABLED :
TelephonyIcons.UNKNOWN;
}
+ if (args.containsKey("roam")) {
+ controller.getState().roaming = "show".equals(args.getString("roam"));
+ }
int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH;
String level = args.getString("level");
if (level != null) {
@@ -797,6 +835,10 @@
: Math.min(Integer.parseInt(level), icons[0].length - 1);
controller.getState().connected = controller.getState().level >= 0;
}
+ String activity = args.getString("activity");
+ if (activity != null) {
+ controller.setActivity(Integer.parseInt(activity));
+ }
controller.getState().enabled = show;
controller.notifyListeners();
}
@@ -813,9 +855,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/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 44ec283..784f25e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -56,6 +56,7 @@
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.stack.ScrollContainer;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -90,6 +91,7 @@
private int mRevealR;
private boolean mResetting;
+ private NotificationViewWrapper mWrapper;
public RemoteInputView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -210,11 +212,17 @@
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(INVISIBLE);
+ if (mWrapper != null) {
+ mWrapper.setRemoteInputVisible(false);
+ }
}
});
reveal.start();
} else {
setVisibility(INVISIBLE);
+ if (mWrapper != null) {
+ mWrapper.setRemoteInputVisible(false);
+ }
}
}
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_CLOSE,
@@ -267,6 +275,9 @@
mEntry.notification.getPackageName());
setVisibility(VISIBLE);
+ if (mWrapper != null) {
+ mWrapper.setRemoteInputVisible(true);
+ }
mController.addRemoteInput(mEntry, mToken);
mEditText.setInnerFocusable(true);
mEditText.mShowImeOnInputConnection = true;
@@ -283,6 +294,10 @@
// Update came in after we sent the reply, time to reset.
reset();
}
+
+ if (isActive() && mWrapper != null) {
+ mWrapper.setRemoteInputVisible(true);
+ }
}
private void reset() {
@@ -452,6 +467,10 @@
super.dispatchFinishTemporaryDetach();
}
+ public void setWrapper(NotificationViewWrapper wrapper) {
+ mWrapper = wrapper;
+ }
+
/**
* An EditText that changes appearance based on whether it's focusable and becomes
* un-focusable whenever the user navigates away from it or it becomes invisible.
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/SignalCallbackAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
deleted file mode 100644
index dce889f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.policy;
-
-import android.telephony.SubscriptionInfo;
-
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-
-import java.util.List;
-
-
-/**
- * Provides empty implementations of SignalCallback for those that only want some of
- * the callbacks.
- */
-public class SignalCallbackAdapter implements SignalCallback {
-
- @Override
- public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
- boolean activityIn, boolean activityOut, String description) {
- }
-
- @Override
- public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
- int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId) {
- }
-
- @Override
- public void setSubs(List<SubscriptionInfo> subs) {
- }
-
- @Override
- public void setNoSims(boolean show) {
- }
-
- @Override
- public void setEthernetIndicators(IconState icon) {
- }
-
- @Override
- public void setIsAirplaneMode(IconState icon) {
- }
-
- @Override
- public void setMobileDataEnabled(boolean enabled) {
- }
-
-}
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 ed8c7ff..6b2361e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -95,8 +95,6 @@
R.drawable.ic_qs_signal_carrier_network_change_animation }
};
- static final int QS_DATA_R = R.drawable.ic_qs_signal_r;
-
//***** Data connection icons
//GSM/UMTS
@@ -211,7 +209,7 @@
static final int QS_DATA_LTE_PLUS = R.drawable.ic_qs_signal_lte_plus;
static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
- static final int ROAMING_ICON = R.drawable.stat_sys_data_fully_connected_roam;
+ static final int ROAMING_ICON = R.drawable.stat_sys_roaming;
static final int ICON_LTE = R.drawable.stat_sys_data_fully_connected_lte;
static final int ICON_LTE_PLUS = R.drawable.stat_sys_data_fully_connected_lte_plus;
static final int ICON_G = R.drawable.stat_sys_data_fully_connected_g;
@@ -410,21 +408,6 @@
TelephonyIcons.QS_DATA_LTE_PLUS
);
- static final MobileIconGroup ROAMING = new MobileIconGroup(
- "Roaming",
- TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING,
- TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
- TelephonyIcons.TELEPHONY_NO_NETWORK,
- TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- R.string.accessibility_data_connection_roaming,
- TelephonyIcons.ROAMING_ICON,
- false,
- TelephonyIcons.QS_DATA_R
- );
-
static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
"DataDisabled",
TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
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..fd71f43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -52,14 +52,16 @@
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 com.android.systemui.util.NotificationChannels;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -560,7 +562,7 @@
0, new Intent(ACTION_LOGOUT_USER), 0, UserHandle.SYSTEM);
Notification.Builder builder = new Notification.Builder(mContext)
.setVisibility(Notification.VISIBILITY_SECRET)
- .setPriority(Notification.PRIORITY_MIN)
+ .setChannel(NotificationChannels.USER)
.setSmallIcon(R.drawable.ic_person)
.setContentTitle(mContext.getString(R.string.user_logout_notification_title))
.setContentText(mContext.getString(R.string.user_logout_notification_text))
@@ -584,7 +586,7 @@
Notification.Builder builder = new Notification.Builder(mContext)
.setVisibility(Notification.VISIBILITY_SECRET)
- .setPriority(Notification.PRIORITY_MIN)
+ .setChannel(NotificationChannels.USER)
.setSmallIcon(R.drawable.ic_person)
.setContentTitle(mContext.getString(R.string.guest_notification_title))
.setContentText(mContext.getString(R.string.guest_notification_text))
@@ -645,11 +647,6 @@
}
@VisibleForTesting
- public KeyguardMonitor getKeyguardMonitor() {
- return mKeyguardMonitor;
- }
-
- @VisibleForTesting
public ArrayList<UserRecord> getUsers() {
return mUsers;
}
@@ -657,17 +654,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..886b8be 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 mScoringUiEnabled = 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,45 @@
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;
+ configureScoringGating();
+ registerScoreCache();
+ }
+
+ private void configureScoringGating() {
+ ContentObserver observer = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mScoringUiEnabled =
+ Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
+ }
+ };
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED),
+ false /* notifyForDescendants */,
+ observer);
+
+ observer.onChange(false /* selfChange */); // Set the initial values
+ }
+
+ private void registerScoreCache() {
+ Log.d(mTag, "Registered score cache");
+ mNetworkScoreManager.registerNetworkScoreCache(
+ NetworkKey.TYPE_WIFI,
+ mScoreCache,
+ NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
}
@Override
@@ -88,27 +143,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 (!mScoringUiEnabled || 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 +251,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 +270,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/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 1a2d778..e6a3add 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -78,6 +78,7 @@
private NotificationHeaderUtil mHeaderUtil;
private ViewState mHeaderViewState;
private int mClipBottomAmount;
+ private boolean mIsLowPriority;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -246,10 +247,13 @@
return mChildren.size();
}
- public void recreateNotificationHeader(OnClickListener listener, StatusBarNotification notification) {
+ public void recreateNotificationHeader(OnClickListener listener,
+ StatusBarNotification notification) {
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
mNotificationParent.getStatusBarNotification().getNotification());
- final RemoteViews header = builder.makeNotificationHeader();
+ final RemoteViews header = mIsLowPriority
+ ? builder.makeLowPriorityContentView(true /* useRegularSubtext */)
+ : builder.makeNotificationHeader();
if (mNotificationHeader == null) {
mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
final View expandButton = mNotificationHeader.findViewById(
@@ -262,7 +266,7 @@
invalidate();
} else {
header.reapply(getContext(), mNotificationHeader);
- mNotificationHeaderWrapper.notifyContentUpdated(notification);
+ mNotificationHeaderWrapper.notifyContentUpdated(notification, mIsLowPriority);
}
updateChildrenHeaderAppearance();
}
@@ -367,6 +371,9 @@
* @return the intrinsic size of this children container, i.e the natural fully expanded state
*/
public int getIntrinsicHeight() {
+ if (mIsLowPriority && !mChildrenExpanded) {
+ return mNotificationHeader.getHeight();
+ }
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
return getIntrinsicHeight(maxAllowedVisibleChildren);
}
@@ -480,7 +487,7 @@
childState.clipTopAmount = 0;
childState.alpha = 0;
if (i < firstOverflowIndex) {
- childState.alpha = 1;
+ childState.alpha = mIsLowPriority && !mChildrenExpanded ? expandFactor : 1.0f;
} else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
@@ -828,6 +835,9 @@
}
private int getMinHeight(int maxAllowedVisibleChildren) {
+ if (mIsLowPriority && !mChildrenExpanded) {
+ return mNotificationHeader.getHeight();
+ }
int minExpandHeight = mNotificationHeaderMargin;
int visibleChildren = 0;
boolean firstChild = true;
@@ -922,4 +932,8 @@
mClipBottomAmount = clipBottomAmount;
updateChildrenClipping();
}
+
+ public void setIsLowPriority(boolean isLowPriority) {
+ mIsLowPriority = isLowPriority;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 395e8f2..85b1c32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -63,14 +63,15 @@
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationGuts;
-import com.android.systemui.statusbar.NotificationSettingsIconRow;
-import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
+import com.android.systemui.statusbar.NotificationMenuRow;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
@@ -78,7 +79,7 @@
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ScrollAdapter;
@@ -94,7 +95,8 @@
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
- SettingsIconRowListener, ScrollContainer, VisibilityLocationProvider {
+ NotificationMenuRowProvider.OnMenuClickListener, ScrollContainer,
+ VisibilityLocationProvider {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -224,7 +226,7 @@
private int mMaxScrollAfterExpand;
private SwipeHelper.LongPressListener mLongPressListener;
- private NotificationSettingsIconRow mCurrIconRow;
+ private NotificationMenuRow mCurrIconRow;
private View mTranslatingParentView;
private View mGearExposedView;
@@ -249,7 +251,7 @@
return true;
}
};
- private PhoneStatusBar mPhoneStatusBar;
+ private StatusBar mStatusBar;
private int[] mTempInt2 = new int[2];
private boolean mGenerateChildOrderChangedEvent;
private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
@@ -399,16 +401,20 @@
}
@Override
- public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
- if (mLongPressListener != null) {
+ public void onMenuClicked(View view, int x, int y, MenuItem item) {
+ if (mLongPressListener == null) {
+ return;
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
row.getStatusBarNotification().getPackageName());
- mLongPressListener.onLongPress(row, x, y);
}
+ mLongPressListener.onLongPress(view, x, y, item);
}
@Override
- public void onSettingsIconRowReset(ExpandableNotificationRow row) {
+ public void onMenuReset(View row) {
if (mTranslatingParentView != null && row == mTranslatingParentView) {
mSwipeHelper.setSnappedToGear(false);
mGearExposedView = null;
@@ -425,7 +431,7 @@
if (DEBUG) {
int y = mTopPadding;
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
- y = (int) getLayoutHeight();
+ y = getLayoutHeight();
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
y = getHeight() - getEmptyBottomMargin();
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
@@ -606,9 +612,9 @@
ExpandableView child = (ExpandableView) getChildAt(i);
if (mChildrenToAddAnimated.contains(child)) {
int startingPosition = getPositionInLinearLayout(child);
- int padding = child.getIncreasedPaddingAmount() == 1.0f
- ? mIncreasedPaddingBetweenElements :
- mPaddingBetweenElements;
+ float increasedPaddingAmount = child.getIncreasedPaddingAmount();
+ int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
+ : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
int childHeight = getIntrinsicHeight(child) + padding;
if (startingPosition < mOwnScrollY) {
// This child starts off screen, so let's keep it offscreen to keep the others visible
@@ -871,7 +877,7 @@
mFalsingManager.onNotificationDismissed();
if (mFalsingManager.shouldEnforceBouncer()) {
- mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
+ mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
}
}
@@ -911,7 +917,7 @@
mDragAnimPendingChildren.remove(animView);
}
if (mCurrIconRow != null && targetLeft == 0) {
- mCurrIconRow.resetState();
+ mCurrIconRow.resetState(true /* notify */);
mCurrIconRow = null;
}
}
@@ -962,7 +968,7 @@
@Override
public float getFalsingThresholdFactor() {
- return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+ return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
}
@Override
@@ -1880,7 +1886,8 @@
private void updateContentHeight() {
int height = 0;
- float previousIncreasedAmount = 0.0f;
+ float previousPaddingRequest = mPaddingBetweenElements;
+ float previousPaddingAmount = 0.0f;
int numShownItems = 0;
boolean finish = false;
int maxDisplayedNotifications = mAmbientState.isDark()
@@ -1897,13 +1904,35 @@
finish = true;
}
float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
- if (height != 0) {
- height += (int) NotificationUtils.interpolate(
+ float padding;
+ if (increasedPaddingAmount >= 0.0f) {
+ padding = (int) NotificationUtils.interpolate(
+ previousPaddingRequest,
+ mIncreasedPaddingBetweenElements,
+ increasedPaddingAmount);
+ previousPaddingRequest = (int) NotificationUtils.interpolate(
mPaddingBetweenElements,
mIncreasedPaddingBetweenElements,
- Math.max(previousIncreasedAmount, increasedPaddingAmount));
+ increasedPaddingAmount);
+ } else {
+ int ownPadding = (int) NotificationUtils.interpolate(
+ 0,
+ mPaddingBetweenElements,
+ 1.0f + increasedPaddingAmount);
+ if (previousPaddingAmount > 0.0f) {
+ padding = (int) NotificationUtils.interpolate(
+ ownPadding,
+ mIncreasedPaddingBetweenElements,
+ previousPaddingAmount);
+ } else {
+ padding = ownPadding;
+ }
+ previousPaddingRequest = ownPadding;
}
- previousIncreasedAmount = increasedPaddingAmount;
+ if (height != 0) {
+ height += padding;
+ }
+ previousPaddingAmount = increasedPaddingAmount;
height += expandableView.getIntrinsicHeight();
numShownItems++;
if (finish) {
@@ -2566,10 +2595,19 @@
*/
private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
int startingPosition = getPositionInLinearLayout(removedChild);
- int padding = (int) NotificationUtils.interpolate(
- mPaddingBetweenElements,
- mIncreasedPaddingBetweenElements,
- removedChild.getIncreasedPaddingAmount());
+ float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
+ int padding;
+ if (increasedPaddingAmount >= 0) {
+ padding = (int) NotificationUtils.interpolate(
+ mPaddingBetweenElements,
+ mIncreasedPaddingBetweenElements,
+ increasedPaddingAmount);
+ } else {
+ padding = (int) NotificationUtils.interpolate(
+ 0,
+ mPaddingBetweenElements,
+ 1.0f + increasedPaddingAmount);
+ }
int childHeight = getIntrinsicHeight(removedChild) + padding;
int endPosition = startingPosition + childHeight;
if (endPosition <= mOwnScrollY) {
@@ -2601,19 +2639,42 @@
requestedView = requestedRow = childInGroup.getNotificationParent();
}
int position = 0;
- float previousIncreasedAmount = 0.0f;
+ float previousPaddingRequest = mPaddingBetweenElements;
+ float previousPaddingAmount = 0.0f;
for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = (ExpandableView) getChildAt(i);
boolean notGone = child.getVisibility() != View.GONE;
if (notGone && !child.hasNoContentHeight()) {
float increasedPaddingAmount = child.getIncreasedPaddingAmount();
- if (position != 0) {
- position += (int) NotificationUtils.interpolate(
+ float padding;
+ if (increasedPaddingAmount >= 0.0f) {
+ padding = (int) NotificationUtils.interpolate(
+ previousPaddingRequest,
+ mIncreasedPaddingBetweenElements,
+ increasedPaddingAmount);
+ previousPaddingRequest = (int) NotificationUtils.interpolate(
mPaddingBetweenElements,
mIncreasedPaddingBetweenElements,
- Math.max(previousIncreasedAmount, increasedPaddingAmount));
+ increasedPaddingAmount);
+ } else {
+ int ownPadding = (int) NotificationUtils.interpolate(
+ 0,
+ mPaddingBetweenElements,
+ 1.0f + increasedPaddingAmount);
+ if (previousPaddingAmount > 0.0f) {
+ padding = (int) NotificationUtils.interpolate(
+ ownPadding,
+ mIncreasedPaddingBetweenElements,
+ previousPaddingAmount);
+ } else {
+ padding = ownPadding;
+ }
+ previousPaddingRequest = ownPadding;
}
- previousIncreasedAmount = increasedPaddingAmount;
+ if (position != 0) {
+ position += padding;
+ }
+ previousPaddingAmount = increasedPaddingAmount;
}
if (child == requestedView) {
if (requestedRow != null) {
@@ -3161,7 +3222,7 @@
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
setOwnScrollY(0);
- mPhoneStatusBar.resetUserExpandedStates();
+ mStatusBar.resetUserExpandedStates();
// lets make sure nothing is in the overlay / transient anymore
clearTemporaryViews(this);
@@ -3696,8 +3757,8 @@
return max + getStackTranslation();
}
- public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
- this.mPhoneStatusBar = phoneStatusBar;
+ public void setStatusBar(StatusBar statusBar) {
+ this.mStatusBar = statusBar;
}
public void setGroupManager(NotificationGroupManager groupManager) {
@@ -3768,7 +3829,7 @@
@Override
public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
- mPhoneStatusBar.requestNotificationUpdate();
+ mStatusBar.requestNotificationUpdate();
}
/** @hide */
@@ -3836,7 +3897,7 @@
@Override
public void onGroupsChanged() {
- mPhoneStatusBar.requestNotificationUpdate();
+ mStatusBar.requestNotificationUpdate();
}
public void generateChildOrderChangedEvent() {
@@ -4121,7 +4182,7 @@
if (currView instanceof ExpandableNotificationRow) {
// Set the listener for the current row's gear
mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
- mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
+ mCurrIconRow.setMenuClickListener(NotificationStackScrollLayout.this);
}
}
@@ -4133,9 +4194,9 @@
mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
// If the gear is visible and the movement is towards it it's not a location change.
- boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
+ boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isMenuOnLeft();
boolean locationChange = isTowardsGear(translation, onLeft)
- ? false : mCurrIconRow.isIconLocationChange(translation);
+ ? false : mCurrIconRow.isMenuLocationChange(translation);
if (locationChange) {
// Don't consider it "snapped" if location has changed.
setSnappedToGear(false);
@@ -4146,8 +4207,8 @@
mCheckForDrag = null;
} else {
// Check scheduled, reset alpha and update location; check will fade it in
- mCurrIconRow.setGearAlpha(0f);
- mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
+ mCurrIconRow.setMenuAlpha(0f);
+ mCurrIconRow.setMenuLocation((int) translation);
}
}
}
@@ -4198,14 +4259,14 @@
return false; // Let SwipeHelper handle it.
}
- boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
+ boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isMenuOnLeft());
boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
final double timeForGesture = ev.getEventTime() - ev.getDownTime();
final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView)
&& timeForGesture >= SWIPE_GEAR_TIMING;
if (mGearSnappedTo && mCurrIconRow.isVisible()) {
- if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
+ if (mGearSnappedOnLeft == mCurrIconRow.isMenuOnLeft()) {
boolean coveringGear =
Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
if (gestureTowardsGear || coveringGear) {
@@ -4249,7 +4310,7 @@
private void snapToGear(View animView, float velocity) {
final float snapBackThreshold = getSpaceForGear(animView);
- final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
+ final float target = mCurrIconRow.isMenuOnLeft() ? snapBackThreshold
: -snapBackThreshold;
mGearExposedView = mTranslatingParentView;
if (animView instanceof ExpandableNotificationRow) {
@@ -4280,7 +4341,7 @@
final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
final float translation = getTranslation(animView);
- return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
+ return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isMenuOnLeft()
? translation > snapBackThreshold
: translation < -snapBackThreshold);
}
@@ -4306,7 +4367,7 @@
}
public void closeControlsIfOutsideTouch(MotionEvent ev) {
- NotificationGuts guts = mPhoneStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getExposedGuts();
View view = null;
int height = 0;
if (guts != null) {
@@ -4329,7 +4390,7 @@
Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
if (!rect.contains(rx, ry)) {
// Touch was outside visible guts / gear notification, close what's visible
- mPhoneStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
+ mStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
}
}
}
@@ -4349,7 +4410,7 @@
* Indicates the the gear has been snapped to.
*/
private void setSnappedToGear(boolean snapped) {
- mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
+ mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isMenuOnLeft() : false;
mGearSnappedTo = snapped && mCurrIconRow != null;
}
@@ -4389,11 +4450,11 @@
final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
- || mCurrIconRow.isIconLocationChange(translation)))
+ || mCurrIconRow.isMenuLocationChange(translation)))
&& absTransX >= bounceBackToGearWidth * 0.4
&& absTransX < notiThreshold) {
// Fade in the gear
- mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
+ mCurrIconRow.fadeInMenu(translation > 0 /* fromLeft */, translation,
notiThreshold);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 7ff1000..ba91ffd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -27,7 +27,6 @@
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import java.util.ArrayList;
@@ -244,7 +243,7 @@
int childCount = hostView.getChildCount();
state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount);
- state.increasedPaddingMap.clear();
+ state.paddingMap.clear();
int notGoneIndex = 0;
ExpandableView lastView = null;
for (int i = 0; i < childCount; i++) {
@@ -254,16 +253,31 @@
continue;
}
notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
- float increasedPadding = v.getIncreasedPaddingAmount();
+ float increasedPadding = v.getIncreasedPaddingAmount();;
if (increasedPadding != 0.0f) {
- state.increasedPaddingMap.put(v, increasedPadding);
+ state.paddingMap.put(v, increasedPadding);
if (lastView != null) {
- Float prevValue = state.increasedPaddingMap.get(lastView);
- float newValue = prevValue != null
- ? Math.max(prevValue, increasedPadding)
- : increasedPadding;
- state.increasedPaddingMap.put(lastView, newValue);
+ Float prevValue = state.paddingMap.get(lastView);
+ float newValue = getPaddingForValue(increasedPadding);
+ if (prevValue != null) {
+ float prevPadding = getPaddingForValue(prevValue);
+ if (increasedPadding > 0) {
+ newValue = NotificationUtils.interpolate(
+ prevPadding,
+ newValue,
+ increasedPadding);
+ } else if (prevValue > 0) {
+ newValue = NotificationUtils.interpolate(
+ newValue,
+ prevPadding,
+ prevValue);
+ }
+ }
+ state.paddingMap.put(lastView, newValue);
}
+ } else if (lastView != null) {
+ float newValue = getPaddingForValue(state.paddingMap.get(lastView));
+ state.paddingMap.put(lastView, newValue);
}
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
@@ -287,6 +301,22 @@
}
}
+ private float getPaddingForValue(Float increasedPadding) {
+ if (increasedPadding == null) {
+ return mPaddingBetweenElements;
+ } else if (increasedPadding >= 0.0f) {
+ return NotificationUtils.interpolate(
+ mPaddingBetweenElements,
+ mIncreasedPaddingBetweenElements,
+ increasedPadding);
+ } else {
+ return NotificationUtils.interpolate(
+ 0,
+ mPaddingBetweenElements,
+ 1.0f + increasedPadding);
+ }
+ }
+
private int updateNotGoneIndex(StackScrollState resultState,
StackScrollAlgorithmState state, int notGoneIndex,
ExpandableView v) {
@@ -527,18 +557,17 @@
public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
/**
- * The children from the host that need an increased padding after them. A value of 0 means
- * no increased padding, a value of 1 means full padding.
+ * The padding after each child measured in pixels.
*/
- public final HashMap<ExpandableView, Float> increasedPaddingMap = new HashMap<>();
+ public final HashMap<ExpandableView, Float> paddingMap = new HashMap<>();
public int getPaddingAfterChild(ExpandableView child) {
- Float paddingValue = increasedPaddingMap.get(child);
- return paddingValue == null
- ? mPaddingBetweenElements
- : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
- mIncreasedPaddingBetweenElements,
- paddingValue);
+ Float padding = paddingMap.get(child);
+ if (padding == null) {
+ // Should only happen for the last view
+ return mPaddingBetweenElements;
+ }
+ return (int) padding.floatValue();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 9b8183b..ab562d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -17,23 +17,29 @@
package com.android.systemui.statusbar.tv;
import android.content.ComponentName;
+import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.view.View;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.statusbar.ActivatableNotificationView;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.SystemUI;
import com.android.systemui.pip.tv.PipManager;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
+
+import java.util.ArrayList;
/**
* Status bar implementation for "large screen" products that mostly present no on-screen nav
*/
-public class TvStatusBar extends BaseStatusBar {
+public class TvStatusBar extends SystemUI implements Callbacks {
+
+ private IStatusBarService mBarService;
@Override
public void setIcon(String slot, StatusBarIcon icon) {
@@ -43,16 +49,6 @@
public void removeIcon(String slot) {
}
- @Override
- public void addNotification(StatusBarNotification notification, RankingMap ranking,
- NotificationData.Entry entry) {
- }
-
- @Override
- protected void updateNotificationRanking(RankingMap ranking) {
- }
-
- @Override
public void removeNotification(String key, RankingMap ranking) {
}
@@ -99,53 +95,10 @@
}
@Override
- protected void setAreThereNotifications() {
- }
-
- @Override
- protected void updateNotifications() {
- }
-
- public View getStatusBarView() {
- return null;
- }
-
- @Override
- protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
- return false;
- }
-
- @Override
- public void maybeEscalateHeadsUp() {
- }
-
- @Override
- public boolean isPanelFullyCollapsed() {
- return false;
- }
-
- @Override
- protected int getMaxKeyguardNotifications(boolean recompute) {
- return 0;
- }
-
- @Override
public void animateExpandSettingsPanel(String subPanel) {
}
@Override
- protected void createAndAddWindows() {
- }
-
- @Override
- public void onActivated(ActivatableNotificationView view) {
- }
-
- @Override
- public void onActivationReset(ActivatableNotificationView view) {
- }
-
- @Override
public void showScreenPinningRequest(int taskId) {
}
@@ -175,19 +128,6 @@
}
@Override
- protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
- boolean alertAgain) {
- }
-
- @Override
- protected void setHeadsUpUser(int newUserId) {
- }
-
- protected boolean isSnoozedPackage(StatusBarNotification sbn) {
- return false;
- }
-
- @Override
public void addQsTile(ComponentName tile) {
}
@@ -201,8 +141,23 @@
@Override
public void start() {
- super.start();
putComponent(TvStatusBar.class, this);
+ CommandQueue commandQueue = getComponent(CommandQueue.class);
+ commandQueue.addCallbacks(this);
+ int[] switches = new int[9];
+ ArrayList<IBinder> binders = new ArrayList<>();
+ ArrayList<String> iconSlots = new ArrayList<>();
+ ArrayList<StatusBarIcon> icons = new ArrayList<>();
+ Rect fullscreenStackBounds = new Rect();
+ Rect dockedStackBounds = new Rect();
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ try {
+ mBarService.registerStatusBar(commandQueue, iconSlots, icons, switches, binders,
+ fullscreenStackBounds, dockedStackBounds);
+ } catch (RemoteException ex) {
+ // If the system process isn't there we're doomed anyway.
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java
new file mode 100644
index 0000000..c9c780a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.tuner;
+
+import android.content.Context;
+import android.support.v7.preference.ListPreference;
+import android.util.AttributeSet;
+
+public class BetterListPreference extends ListPreference {
+
+ private CharSequence mSummary;
+
+ public BetterListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setSummary(CharSequence summary) {
+ super.setSummary(summary);
+ mSummary = summary;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mSummary;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
new file mode 100644
index 0000000..41786b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.tuner;
+
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Process;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
+import com.android.systemui.statusbar.phone.ExpandableIndicator;
+import com.android.systemui.tuner.ShortcutParser.Shortcut;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class LockscreenFragment extends PreferenceFragment {
+
+ private static final String KEY_LEFT = "left";
+ private static final String KEY_RIGHT = "right";
+ private static final String KEY_CUSTOMIZE = "customize";
+ private static final String KEY_SHORTCUT = "shortcut";
+
+ public static final String LOCKSCREEN_LEFT_BUTTON = "sysui_keyguard_left";
+ public static final String LOCKSCREEN_LEFT_UNLOCK = "sysui_keyguard_left_unlock";
+ public static final String LOCKSCREEN_RIGHT_BUTTON = "sysui_keyguard_right";
+ public static final String LOCKSCREEN_RIGHT_UNLOCK = "sysui_keyguard_right_unlock";
+
+ private final ArrayList<Tunable> mTunables = new ArrayList<>();
+ private TunerService mTunerService;
+ private Handler mHandler;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ mTunerService = TunerService.get(getContext());
+ mHandler = new Handler();
+ addPreferencesFromResource(R.xml.lockscreen_settings);
+ setupGroup((PreferenceGroup) findPreference(KEY_LEFT), LOCKSCREEN_LEFT_BUTTON,
+ LOCKSCREEN_LEFT_UNLOCK);
+ setupGroup((PreferenceGroup) findPreference(KEY_RIGHT), LOCKSCREEN_RIGHT_BUTTON,
+ LOCKSCREEN_RIGHT_UNLOCK);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mTunables.forEach(t -> mTunerService.removeTunable(t));
+ }
+
+ private void setupGroup(PreferenceGroup group, String buttonSetting, String unlockKey) {
+ SwitchPreference customize = (SwitchPreference) group.findPreference(KEY_CUSTOMIZE);
+ Preference shortcut = group.findPreference(KEY_SHORTCUT);
+ SwitchPreference unlock = (SwitchPreference) group.findPreference(unlockKey);
+ addTunable((k, v) -> {
+ boolean visible = v != null;
+ customize.setChecked(visible);
+ shortcut.setVisible(visible);
+ unlock.setVisible(visible);
+ if (visible) {
+ setSummary(shortcut, v);
+ }
+ }, buttonSetting);
+ customize.setOnPreferenceChangeListener((preference, newValue) -> {
+ boolean hasSetting = mTunerService.getValue(buttonSetting) != null;
+ if (hasSetting != (boolean) newValue) {
+ mHandler.post(() -> mTunerService.setValue(buttonSetting, hasSetting ? null : ""));
+ }
+ return true;
+ });
+ shortcut.setOnPreferenceClickListener(preference -> {
+ showSelectDialog(buttonSetting);
+ return true;
+ });
+ }
+
+ private void showSelectDialog(String buttonSetting) {
+ RecyclerView v = (RecyclerView) LayoutInflater.from(getContext())
+ .inflate(R.layout.tuner_shortcut_list, null);
+ v.setLayoutManager(new LinearLayoutManager(getContext()));
+ AlertDialog dialog = new Builder(getContext())
+ .setView(v)
+ .show();
+ Adapter adapter = new Adapter(getContext(), item -> {
+ mTunerService.setValue(buttonSetting, item.getSettingValue());
+ dialog.dismiss();
+ });
+ LauncherApps apps = getContext().getSystemService(LauncherApps.class);
+ List<LauncherActivityInfo> activities = apps.getActivityList(null,
+ Process.myUserHandle());
+
+ activities.forEach(info -> {
+ App app = new App(getContext(), info);
+ try {
+ new ShortcutParser(getContext(), info.getComponentName()).getShortcuts().forEach(
+ shortcut -> app.addChild(new StaticShortcut(getContext(), shortcut)));
+ } catch (NameNotFoundException e) {
+ }
+ adapter.addItem(app);
+ });
+
+ v.setAdapter(adapter);
+ }
+
+ private void setSummary(Preference shortcut, String value) {
+ if (value.contains("::")) {
+ Shortcut info = getShortcutInfo(getContext(), value);
+ shortcut.setSummary(info != null ? info.label : null);
+ } else if (value.contains("/")) {
+ ActivityInfo info = getActivityinfo(getContext(), value);
+ shortcut.setSummary(info != null ? info.loadLabel(getContext().getPackageManager())
+ : null);
+ } else {
+ shortcut.setSummary(null);
+ }
+ }
+
+ private void addTunable(Tunable t, String... keys) {
+ mTunables.add(t);
+ mTunerService.addTunable(t, keys);
+ }
+
+ public static ActivityInfo getActivityinfo(Context context, String value) {
+ ComponentName component = ComponentName.unflattenFromString(value);
+ try {
+ return context.getPackageManager().getActivityInfo(component, 0);
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ public static Shortcut getShortcutInfo(Context context, String value) {
+ return Shortcut.create(context, value);
+ }
+
+ public static class Holder extends ViewHolder {
+ public final ImageView icon;
+ public final TextView title;
+ public final ExpandableIndicator expand;
+
+ public Holder(View itemView) {
+ super(itemView);
+ icon = (ImageView) itemView.findViewById(android.R.id.icon);
+ title = (TextView) itemView.findViewById(android.R.id.title);
+ expand = (ExpandableIndicator) itemView.findViewById(R.id.expand);
+ }
+ }
+
+ private static class StaticShortcut extends Item {
+
+ private final Context mContext;
+ private final Shortcut mShortcut;
+
+
+ public StaticShortcut(Context context, Shortcut shortcut) {
+ mContext = context;
+ mShortcut = shortcut;
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return mShortcut.icon.loadDrawable(mContext);
+ }
+
+ @Override
+ public String getLabel() {
+ return mShortcut.label;
+ }
+
+ @Override
+ public String getSettingValue() {
+ return mShortcut.toString();
+ }
+
+ @Override
+ public Boolean getExpando() {
+ return null;
+ }
+ }
+
+ private static class App extends Item {
+
+ private final Context mContext;
+ private final LauncherActivityInfo mInfo;
+ private final ArrayList<Item> mChildren = new ArrayList<>();
+ private boolean mExpanded;
+
+ public App(Context context, LauncherActivityInfo info) {
+ mContext = context;
+ mInfo = info;
+ mExpanded = false;
+ }
+
+ public void addChild(Item child) {
+ mChildren.add(child);
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return mInfo.getBadgedIcon(mContext.getResources().getConfiguration().densityDpi);
+ }
+
+ @Override
+ public String getLabel() {
+ return mInfo.getLabel().toString();
+ }
+
+ @Override
+ public String getSettingValue() {
+ return mInfo.getComponentName().flattenToString();
+ }
+
+ @Override
+ public Boolean getExpando() {
+ return mChildren.size() != 0 ? mExpanded : null;
+ }
+
+ @Override
+ public void toggleExpando(Adapter adapter) {
+ mExpanded = !mExpanded;
+ if (mExpanded) {
+ mChildren.forEach(child -> adapter.addItem(this, child));
+ } else {
+ mChildren.forEach(child -> adapter.remItem(child));
+ }
+ }
+ }
+
+ private abstract static class Item {
+ public abstract Drawable getDrawable();
+
+ public abstract String getLabel();
+
+ public abstract String getSettingValue();
+
+ public abstract Boolean getExpando();
+
+ public void toggleExpando(Adapter adapter) {
+ }
+ }
+
+ public static class Adapter extends RecyclerView.Adapter<Holder> {
+ private ArrayList<Item> mItems = new ArrayList<>();
+ private final Context mContext;
+ private final Consumer<Item> mCallback;
+
+ public Adapter(Context context, Consumer<Item> callback) {
+ mContext = context;
+ mCallback = callback;
+ }
+
+ @Override
+ public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new Holder(LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.tuner_shortcut_item, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(Holder holder, int position) {
+ Item item = mItems.get(position);
+ holder.icon.setImageDrawable(item.getDrawable());
+ holder.title.setText(item.getLabel());
+ holder.itemView.setOnClickListener(
+ v -> mCallback.accept(mItems.get(holder.getAdapterPosition())));
+ Boolean expando = item.getExpando();
+ if (expando != null) {
+ holder.expand.setVisibility(View.VISIBLE);
+ holder.expand.setExpanded(expando);
+ holder.expand.setOnClickListener(
+ v -> mItems.get(holder.getAdapterPosition()).toggleExpando(Adapter.this));
+ } else {
+ holder.expand.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mItems.size();
+ }
+
+ public void addItem(Item item) {
+ mItems.add(item);
+ notifyDataSetChanged();
+ }
+
+ public void remItem(Item item) {
+ int index = mItems.indexOf(item);
+ mItems.remove(item);
+ notifyItemRemoved(index);
+ }
+
+ public void addItem(Item parent, Item child) {
+ int index = mItems.indexOf(parent);
+ mItems.add(index + 1, child);
+ notifyItemInserted(index + 1);
+ }
+ }
+
+ public static IntentButton getIntentButton(Context context, String buttonStr,
+ IntentButton plugin, IntentButton def) {
+ // Plugin wins.
+ if (plugin != null) return plugin;
+ // Then tuner options.
+ if (!TextUtils.isEmpty(buttonStr)) {
+ if (buttonStr.contains("::")) {
+ Shortcut shortcut = getShortcutInfo(context, buttonStr);
+ if (shortcut != null) {
+ return new ShortcutButton(context, shortcut);
+ }
+ } else if (buttonStr.contains("/")) {
+ ActivityInfo info = getActivityinfo(context, buttonStr);
+ if (info != null) {
+ return new ActivityButton(context, info);
+ }
+ }
+ }
+ // Then default.
+ return def;
+ }
+
+ private static class ShortcutButton implements IntentButton {
+ private final Shortcut mShortcut;
+ private final IconState mIconState;
+
+ public ShortcutButton(Context context, Shortcut shortcut) {
+ mShortcut = shortcut;
+ mIconState = new IconState();
+ mIconState.isVisible = true;
+ mIconState.drawable = shortcut.icon.loadDrawable(context);
+ mIconState.contentDescription = mShortcut.label;
+ }
+
+ @Override
+ public IconState getIcon() {
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return mShortcut.intent;
+ }
+ }
+
+ private static class ActivityButton implements IntentButton {
+ private final Intent mIntent;
+ private final IconState mIconState;
+
+ public ActivityButton(Context context, ActivityInfo info) {
+ mIntent = new Intent().setComponent(new ComponentName(info.packageName, info.name));
+ mIconState = new IconState();
+ mIconState.isVisible = true;
+ mIconState.drawable = info.loadIcon(context.getPackageManager());
+ mIconState.contentDescription = info.loadLabel(context.getPackageManager());
+ }
+
+ @Override
+ public IconState getIcon() {
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return mIntent;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index ad42459..9593c45 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
@@ -14,590 +14,227 @@
package com.android.systemui.tuner;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.helper.ItemTouchHelper;
-import android.util.TypedValue;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BACK;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BUTTON_SEPARATOR;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.CLIPBOARD;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.GRAVITY_SEPARATOR;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.HOME;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_LEFT;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_RIGHT;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.RECENT;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_END;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_START;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractButton;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractSize;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractImage;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractKeycode;
-public class NavBarTuner extends Fragment implements TunerService.Tunable {
+import android.annotation.Nullable;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.DropDownPreference;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.Preference.OnPreferenceClickListener;
+import android.support.v7.preference.PreferenceCategory;
+import android.text.SpannableStringBuilder;
+import android.text.style.ImageSpan;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.widget.EditText;
- private static final int SAVE = Menu.FIRST + 1;
- private static final int RESET = Menu.FIRST + 2;
- private static final int READ_REQUEST = 42;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
+import com.android.systemui.tuner.TunerService.Tunable;
- private static final float PREVIEW_SCALE = .95f;
- private static final float PREVIEW_SCALE_LANDSCAPE = .75f;
+import java.util.ArrayList;
- private NavBarAdapter mNavBarAdapter;
- private PreviewNavInflater mPreview;
+public class NavBarTuner extends PreferenceFragment {
+
+ private static final String LAYOUT = "layout";
+ private static final String LEFT = "left";
+ private static final String RIGHT = "right";
+
+ private static final String TYPE = "type";
+ private static final String KEYCODE = "keycode";
+ private static final String ICON = "icon";
+
+ private static final int[] ICONS = new int[]{
+ R.drawable.ic_qs_circle,
+ R.drawable.ic_add,
+ R.drawable.ic_remove,
+ R.drawable.ic_left,
+ R.drawable.ic_right,
+ R.drawable.ic_menu,
+ };
+
+ private final ArrayList<Tunable> mTunables = new ArrayList<>();
+ private Handler mHandler;
@Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.nav_bar_tuner, container, false);
- inflatePreview((ViewGroup) view.findViewById(R.id.nav_preview_frame));
- return view;
- }
-
- private void inflatePreview(ViewGroup view) {
- Display display = getActivity().getWindowManager().getDefaultDisplay();
- boolean isRotated = display.getRotation() == Surface.ROTATION_90
- || display.getRotation() == Surface.ROTATION_270;
-
- Configuration config = new Configuration(getContext().getResources().getConfiguration());
- boolean isPhoneLandscape = isRotated && (config.smallestScreenWidthDp < 600);
- final float scale = isPhoneLandscape ? PREVIEW_SCALE_LANDSCAPE : PREVIEW_SCALE;
- config.densityDpi = (int) (config.densityDpi * scale);
-
- mPreview = (PreviewNavInflater) LayoutInflater.from(getContext().createConfigurationContext(
- config)).inflate(R.layout.nav_bar_tuner_inflater, view, false);
- final ViewGroup.LayoutParams layoutParams = mPreview.getLayoutParams();
- layoutParams.width = (int) ((isPhoneLandscape ? display.getHeight() : display.getWidth())
- * scale);
- // Not sure why, but the height dimen is not being scaled with the dp, set it manually
- // for now.
- layoutParams.height = (int) (layoutParams.height * scale);
- if (isPhoneLandscape) {
- int width = layoutParams.width;
- layoutParams.width = layoutParams.height;
- layoutParams.height = width;
- }
- view.addView(mPreview);
-
- if (isRotated) {
- mPreview.findViewById(R.id.rot0).setVisibility(View.GONE);
- final View rot90 = mPreview.findViewById(R.id.rot90);
- } else {
- mPreview.findViewById(R.id.rot90).setVisibility(View.GONE);
- final View rot0 = mPreview.findViewById(R.id.rot0);
- }
- }
-
- private void notifyChanged() {
- mPreview.onTuningChanged(NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ mHandler = new Handler();
+ super.onCreate(savedInstanceState);
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- RecyclerView recyclerView = (RecyclerView) view.findViewById(android.R.id.list);
- final Context context = getContext();
- recyclerView.setLayoutManager(new LinearLayoutManager(context));
- mNavBarAdapter = new NavBarAdapter(context);
- recyclerView.setAdapter(mNavBarAdapter);
- recyclerView.addItemDecoration(new Dividers(context));
- final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mNavBarAdapter.mCallbacks);
- mNavBarAdapter.setTouchHelper(itemTouchHelper);
- itemTouchHelper.attachToRecyclerView(recyclerView);
-
- TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
- public void onDestroyView() {
- super.onDestroyView();
- TunerService.get(getContext()).removeTunable(this);
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ addPreferencesFromResource(R.xml.nav_bar_tuner);
+ bindLayout((ListPreference) findPreference(LAYOUT));
+ bindButton((PreferenceCategory) findPreference(LEFT),
+ NAV_BAR_LEFT, NAVSPACE);
+ bindButton((PreferenceCategory) findPreference(RIGHT),
+ NAV_BAR_RIGHT, MENU_IME);
}
@Override
- public void onTuningChanged(String key, String navLayout) {
- if (!NAV_BAR_VIEWS.equals(key)) return;
- Context context = getContext();
- if (navLayout == null) {
- navLayout = context.getString(R.string.config_navBarLayout);
- }
- String[] views = navLayout.split(GRAVITY_SEPARATOR);
- String[] groups = new String[] { NavBarAdapter.START, NavBarAdapter.CENTER,
- NavBarAdapter.END};
- CharSequence[] groupLabels = new String[] { getString(R.string.start),
- getString(R.string.center), getString(R.string.end) };
- mNavBarAdapter.clear();
- for (int i = 0; i < 3; i++) {
- mNavBarAdapter.addButton(groups[i], groupLabels[i]);
- for (String button : views[i].split(BUTTON_SEPARATOR)) {
- mNavBarAdapter.addButton(button, getLabel(button, context));
+ public void onDestroy() {
+ super.onDestroy();
+ mTunables.forEach(t -> TunerService.get(getContext()).removeTunable(t));
+ }
+
+ private void addTunable(Tunable tunable, String... keys) {
+ mTunables.add(tunable);
+ TunerService.get(getContext()).addTunable(tunable, keys);
+ }
+
+ private void bindLayout(ListPreference preference) {
+ addTunable((key, newValue) -> mHandler.post(() -> {
+ String val = newValue;
+ if (val == null) {
+ val = "default";
}
- }
- mNavBarAdapter.addButton(NavBarAdapter.ADD, getString(R.string.add_button));
- setHasOptionsMenu(true);
+ preference.setValue(val);
+ }), NAV_BAR_VIEWS);
+ preference.setOnPreferenceChangeListener((preference1, newValue) -> {
+ String val = (String) newValue;
+ if ("default".equals(val)) val = null;
+ TunerService.get(getContext()).setValue(NAV_BAR_VIEWS, val);
+ return true;
+ });
}
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- // TODO: Show save button conditionally, only when there are changes.
- menu.add(Menu.NONE, SAVE, Menu.NONE, getString(R.string.save))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(Menu.NONE, RESET, Menu.NONE, getString(R.string.reset));
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == SAVE) {
- if (!mNavBarAdapter.hasHomeButton()) {
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.no_home_title)
- .setMessage(R.string.no_home_message)
- .setPositiveButton(android.R.string.ok, null)
- .show();
+ private void bindButton(PreferenceCategory parent, String setting, String def) {
+ String k = parent.getKey();
+ DropDownPreference type = (DropDownPreference) findPreference(TYPE + "_" + k);
+ Preference keycode = findPreference(KEYCODE + "_" + k);
+ ListPreference icon = (ListPreference) findPreference(ICON + "_" + k);
+ setupIcons(icon);
+ addTunable((key, newValue) -> mHandler.post(() -> {
+ String val = newValue;
+ if (val == null) {
+ val = def;
+ }
+ String button = extractButton(val);
+ if (button.startsWith(KEY)) {
+ type.setValue(KEY);
+ String uri = extractImage(button);
+ int code = extractKeycode(button);
+ icon.setValue(uri);
+ updateSummary(icon);
+ keycode.setSummary(code + "");
+ keycode.setVisible(true);
+ icon.setVisible(true);
} else {
- Settings.Secure.putString(getContext().getContentResolver(),
- NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
+ type.setValue(button);
+ keycode.setVisible(false);
+ icon.setVisible(false);
}
- return true;
- } else if (item.getItemId() == RESET) {
- Settings.Secure.putString(getContext().getContentResolver(),
- NAV_BAR_VIEWS, null);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private static CharSequence getLabel(String button, Context context) {
- if (button.startsWith(HOME)) {
- return context.getString(R.string.accessibility_home);
- } else if (button.startsWith(BACK)) {
- return context.getString(R.string.accessibility_back);
- } else if (button.startsWith(RECENT)) {
- return context.getString(R.string.accessibility_recent);
- } else if (button.startsWith(NAVSPACE)) {
- return context.getString(R.string.space);
- } else if (button.startsWith(MENU_IME)) {
- return context.getString(R.string.menu_ime);
- } else if (button.startsWith(CLIPBOARD)) {
- return context.getString(R.string.clipboard);
- } else if (button.startsWith(KEY)) {
- return context.getString(R.string.keycode);
- }
- return button;
- }
-
- private static class Holder extends RecyclerView.ViewHolder {
- private TextView title;
-
- public Holder(View itemView) {
- super(itemView);
- title = (TextView) itemView.findViewById(android.R.id.title);
- }
- }
-
- private static class Dividers extends RecyclerView.ItemDecoration {
- private final Drawable mDivider;
-
- public Dividers(Context context) {
- TypedValue value = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.listDivider, value, true);
- mDivider = context.getDrawable(value.resourceId);
- }
-
- @Override
- public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- super.onDraw(c, parent, state);
- final int left = parent.getPaddingLeft();
- final int right = parent.getWidth() - parent.getPaddingRight();
-
- final int childCount = parent.getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = parent.getChildAt(i);
- final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
- .getLayoutParams();
- final int top = child.getBottom() + params.bottomMargin;
- final int bottom = top + mDivider.getIntrinsicHeight();
- mDivider.setBounds(left, top, right, bottom);
- mDivider.draw(c);
- }
- }
- }
-
- private void selectImage() {
- startActivityForResult(KeycodeSelectionHelper.getSelectImageIntent(), READ_REQUEST);
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == READ_REQUEST && resultCode == Activity.RESULT_OK && data != null) {
- final Uri uri = data.getData();
- final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION);
- getContext().getContentResolver().takePersistableUriPermission(uri, takeFlags);
- mNavBarAdapter.onImageSelected(uri);
- } else {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-
- private class NavBarAdapter extends RecyclerView.Adapter<Holder>
- implements View.OnClickListener {
-
- private static final String START = "start";
- private static final String CENTER = "center";
- private static final String END = "end";
- private static final String ADD = "add";
-
- private static final int ADD_ID = 0;
- private static final int BUTTON_ID = 1;
- private static final int CATEGORY_ID = 2;
-
- private List<String> mButtons = new ArrayList<>();
- private List<CharSequence> mLabels = new ArrayList<>();
- private int mCategoryLayout;
- private int mButtonLayout;
- private ItemTouchHelper mTouchHelper;
-
- // Stored keycode while we wait for image selection on a KEY.
- private int mKeycode;
-
- public NavBarAdapter(Context context) {
- TypedArray attrs = context.getTheme().obtainStyledAttributes(null,
- android.R.styleable.Preference, android.R.attr.preferenceStyle, 0);
- mButtonLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
- attrs = context.getTheme().obtainStyledAttributes(null,
- android.R.styleable.Preference, android.R.attr.preferenceCategoryStyle, 0);
- mCategoryLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
- }
-
- public void setTouchHelper(ItemTouchHelper itemTouchHelper) {
- mTouchHelper = itemTouchHelper;
- }
-
- public void clear() {
- mButtons.clear();
- mLabels.clear();
- notifyDataSetChanged();
- }
-
- public void addButton(String button, CharSequence label) {
- mButtons.add(button);
- mLabels.add(label);
- notifyItemInserted(mLabels.size() - 1);
- notifyChanged();
- }
-
- public boolean hasHomeButton() {
- final int N = mButtons.size();
- for (int i = 0; i < N; i++) {
- if (mButtons.get(i).startsWith(HOME)) {
- return true;
- }
- }
- return false;
- }
-
- public String getNavString() {
- StringBuilder builder = new StringBuilder();
- for (int i = 1; i < mButtons.size() - 1; i++) {
- String button = mButtons.get(i);
- if (button.equals(CENTER) || button.equals(END)) {
- if (builder.length() == 0 || builder.toString().endsWith(GRAVITY_SEPARATOR)) {
- // No start or center buttons, fill with a space.
- builder.append(NAVSPACE);
- }
- builder.append(GRAVITY_SEPARATOR);
- continue;
- } else if (builder.length() != 0 && !builder.toString().endsWith(
- GRAVITY_SEPARATOR)) {
- builder.append(BUTTON_SEPARATOR);
- }
- builder.append(button);
- }
- if (builder.toString().endsWith(GRAVITY_SEPARATOR)) {
- // No end buttons, fill with space.
- builder.append(NAVSPACE);
- }
- return builder.toString();
- }
-
- @Override
- public int getItemViewType(int position) {
- String button = mButtons.get(position);
- if (button.equals(START) || button.equals(CENTER) || button.equals(END)) {
- return CATEGORY_ID;
- }
- if (button.equals(ADD)) {
- return ADD_ID;
- }
- return BUTTON_ID;
- }
-
- @Override
- public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
- final Context context = parent.getContext();
- final LayoutInflater inflater = LayoutInflater.from(context);
- final View view = inflater.inflate(getLayoutId(viewType), parent, false);
- if (viewType == BUTTON_ID) {
- inflater.inflate(R.layout.nav_control_widget,
- (ViewGroup) view.findViewById(android.R.id.widget_frame));
- }
- return new Holder(view);
- }
-
- private int getLayoutId(int viewType) {
- if (viewType == CATEGORY_ID) {
- return mCategoryLayout;
- }
- return mButtonLayout;
- }
-
- @Override
- public void onBindViewHolder(Holder holder, int position) {
- holder.title.setText(mLabels.get(position));
- if (holder.getItemViewType() == BUTTON_ID) {
- bindButton(holder, position);
- } else if (holder.getItemViewType() == ADD_ID) {
- bindAdd(holder);
- }
- }
-
- private void bindAdd(Holder holder) {
- TypedValue value = new TypedValue();
- final Context context = holder.itemView.getContext();
- context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true);
- final ImageView icon = (ImageView) holder.itemView.findViewById(android.R.id.icon);
- icon.setImageResource(R.drawable.ic_add);
- icon.setImageTintList(ColorStateList.valueOf(context.getColor(value.resourceId)));
- holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
- holder.itemView.setClickable(true);
- holder.itemView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showAddDialog(v.getContext());
- }
+ }), setting);
+ OnPreferenceChangeListener listener = (preference, newValue) -> {
+ mHandler.post(() -> {
+ setValue(setting, type, keycode, icon);
+ updateSummary(icon);
});
- }
-
- private void bindButton(final Holder holder, int position) {
- holder.itemView.findViewById(android.R.id.icon_frame).setVisibility(View.GONE);
- holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
- bindClick(holder.itemView.findViewById(R.id.close), holder);
- bindClick(holder.itemView.findViewById(R.id.width), holder);
- holder.itemView.findViewById(R.id.drag).setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- mTouchHelper.startDrag(holder);
- return true;
- }
- });
- }
-
- private void showAddDialog(final Context context) {
- final String[] options = new String[] {
- BACK, HOME, RECENT, MENU_IME, NAVSPACE, CLIPBOARD, KEY,
- };
- final CharSequence[] labels = new CharSequence[options.length];
- for (int i = 0; i < options.length; i++) {
- labels[i] = getLabel(options[i], context);
- }
- new AlertDialog.Builder(context)
- .setTitle(R.string.select_button)
- .setItems(labels, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (KEY.equals(options[which])) {
- showKeyDialogs(context);
- } else {
- int index = mButtons.size() - 1;
- showAddedMessage(context, options[which]);
- mButtons.add(index, options[which]);
- mLabels.add(index, labels[which]);
-
- notifyItemInserted(index);
- notifyChanged();
- }
- }
- }).setNegativeButton(android.R.string.cancel, null)
- .show();
- }
-
- private void onImageSelected(Uri uri) {
- int index = mButtons.size() - 1;
- mButtons.add(index, KEY + KEY_CODE_START + mKeycode + KEY_IMAGE_DELIM + uri.toString()
- + KEY_CODE_END);
- mLabels.add(index, getLabel(KEY, getContext()));
-
- notifyItemInserted(index);
- notifyChanged();
- }
-
- private void showKeyDialogs(final Context context) {
- final KeycodeSelectionHelper.OnSelectionComplete listener =
- new KeycodeSelectionHelper.OnSelectionComplete() {
- @Override
- public void onSelectionComplete(int code) {
- mKeycode = code;
- selectImage();
- }
- };
- new AlertDialog.Builder(context)
- .setTitle(R.string.keycode)
- .setMessage(R.string.keycode_description)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- KeycodeSelectionHelper.showKeycodeSelect(context, listener);
- }
- }).show();
- }
-
- private void showAddedMessage(Context context, String button) {
- if (CLIPBOARD.equals(button)) {
- new AlertDialog.Builder(context)
- .setTitle(R.string.clipboard)
- .setMessage(R.string.clipboard_description)
- .setPositiveButton(android.R.string.ok, null)
- .show();
- }
- }
-
- private void bindClick(View view, Holder holder) {
- view.setOnClickListener(this);
- view.setTag(holder);
- }
-
- @Override
- public void onClick(View v) {
- Holder holder = (Holder) v.getTag();
- if (v.getId() == R.id.width) {
- showWidthDialog(holder, v.getContext());
- } else if (v.getId() == R.id.close) {
- int position = holder.getAdapterPosition();
- mButtons.remove(position);
- mLabels.remove(position);
- notifyItemRemoved(position);
- notifyChanged();
- }
- }
-
- private void showWidthDialog(final Holder holder, Context context) {
- final String buttonSpec = mButtons.get(holder.getAdapterPosition());
- float amount = extractSize(buttonSpec);
- final AlertDialog dialog = new AlertDialog.Builder(context)
- .setTitle(R.string.adjust_button_width)
- .setView(R.layout.nav_width_view)
- .setNegativeButton(android.R.string.cancel, null).create();
- dialog.setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(android.R.string.ok),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface d, int which) {
- final String button = extractButton(buttonSpec);
- SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
- if (seekBar.getProgress() == 75) {
- mButtons.set(holder.getAdapterPosition(), button);
- } else {
- float amount = (seekBar.getProgress() + 25) / 100f;
- mButtons.set(holder.getAdapterPosition(), button
- + SIZE_MOD_START + amount + SIZE_MOD_END);
- }
- notifyChanged();
- }
- });
- dialog.show();
- SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
- // Range is .25 - 1.75.
- seekBar.setMax(150);
- seekBar.setProgress((int) ((amount - .25f) * 100));
- }
-
- @Override
- public int getItemCount() {
- return mButtons.size();
- }
-
- private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
- @Override
- public boolean isLongPressDragEnabled() {
- return false;
- }
-
- @Override
- public boolean isItemViewSwipeEnabled() {
- return false;
- }
-
- @Override
- public int getMovementFlags(RecyclerView recyclerView,
- RecyclerView.ViewHolder viewHolder) {
- if (viewHolder.getItemViewType() != BUTTON_ID) {
- return makeMovementFlags(0, 0);
- }
- int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
- return makeMovementFlags(dragFlags, 0);
- }
-
- @Override
- public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
- RecyclerView.ViewHolder target) {
- int from = viewHolder.getAdapterPosition();
- int to = target.getAdapterPosition();
- if (to == 0) {
- // Can't go above the top.
- return false;
- }
- move(from, to, mButtons);
- move(from, to, mLabels);
- notifyChanged();
- notifyItemMoved(from, to);
- return true;
- }
-
- private <T> void move(int from, int to, List<T> list) {
- list.add(from > to ? to : to + 1, list.get(from));
- list.remove(from > to ? from + 1 : from);
- }
-
- @Override
- public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
- // Don't care.
- }
+ return true;
};
+ type.setOnPreferenceChangeListener(listener);
+ icon.setOnPreferenceChangeListener(listener);
+ keycode.setOnPreferenceClickListener(preference -> {
+ EditText editText = new EditText(getContext());
+ new AlertDialog.Builder(getContext())
+ .setTitle(preference.getTitle())
+ .setView(editText)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ int code = KeyEvent.KEYCODE_ENTER;
+ try {
+ code = Integer.parseInt(editText.getText().toString());
+ } catch (Exception e) {
+ }
+ keycode.setSummary(code + "");
+ setValue(setting, type, keycode, icon);
+ }).show();
+ return true;
+ });
+ }
+
+ private void updateSummary(ListPreference icon) {
+ try {
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14,
+ getContext().getResources().getDisplayMetrics());
+ String pkg = icon.getValue().split("/")[0];
+ int id = Integer.parseInt(icon.getValue().split("/")[1]);
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ Drawable d = Icon.createWithResource(pkg, id)
+ .loadDrawable(getContext());
+ d.setTint(Color.BLACK);
+ d.setBounds(0, 0, size, size);
+ ImageSpan span = new ImageSpan(d);
+ builder.append(" ", span, 0);
+ icon.setSummary(builder);
+ } catch (Exception e) {
+ Log.d("NavButton", "Problem with summary", e);
+ icon.setSummary(null);
+ }
+ }
+
+ private void setValue(String setting, DropDownPreference type, Preference keycode,
+ ListPreference icon) {
+ String button = type.getValue();
+ if (KEY.equals(button)) {
+ String uri = icon.getValue();
+ int code = KeyEvent.KEYCODE_ENTER;
+ try {
+ code = Integer.parseInt(keycode.getSummary().toString());
+ } catch (Exception e) {
+ }
+ button = button + KEY_CODE_START + code + KEY_IMAGE_DELIM + uri + KEY_CODE_END;
+ }
+ TunerService.get(getContext()).setValue(setting, button);
+ }
+
+ private void setupIcons(ListPreference icon) {
+ CharSequence[] labels = new CharSequence[ICONS.length];
+ CharSequence[] values = new CharSequence[ICONS.length];
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14,
+ getContext().getResources().getDisplayMetrics());
+ for (int i = 0; i < ICONS.length; i++) {
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ Drawable d = Icon.createWithResource(getContext().getPackageName(), ICONS[i])
+ .loadDrawable(getContext());
+ d.setTint(Color.BLACK);
+ d.setBounds(0, 0, size, size);
+ ImageSpan span = new ImageSpan(d);
+ builder.append(" ", span, 0);
+ labels[i] = builder;
+ values[i] = getContext().getPackageName() + "/" + ICONS[i];
+ }
+ icon.setEntries(labels);
+ icon.setEntryValues(values);
}
}
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/tuner/ShortcutParser.java b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutParser.java
new file mode 100644
index 0000000..2aa51b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutParser.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.tuner;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShortcutParser {
+ private static final String SHORTCUTS = "android.app.shortcuts";
+ private static final String SHORTCUT = "shortcut";
+ private static final String INTENT = "intent";
+
+ private final Context mContext;
+ private final String mPkg;
+ private final int mResId;
+ private final String mName;
+ private Resources mResources;
+ private AttributeSet mAttrs;
+
+ public ShortcutParser(Context context, ComponentName component) throws NameNotFoundException {
+ this(context, component.getPackageName(), component.getClassName(),
+ getResId(context, component));
+ }
+
+ private static int getResId(Context context, ComponentName component)
+ throws NameNotFoundException {
+ ActivityInfo i = context.getPackageManager().getActivityInfo(
+ component, PackageManager.GET_META_DATA);
+ int resId = 0;
+ if (i.metaData != null && i.metaData.containsKey(SHORTCUTS)) {
+ resId = i.metaData.getInt(SHORTCUTS);
+ }
+ return resId;
+ }
+
+ public ShortcutParser(Context context, String pkg, String name, int resId) {
+ mContext = context;
+ mPkg = pkg;
+ mResId = resId;
+ mName = name;
+ }
+
+ public List<Shortcut> getShortcuts() {
+ List<Shortcut> list = new ArrayList<>();
+ if (mResId != 0) {
+ try {
+ mResources = mContext.getPackageManager().getResourcesForApplication(mPkg);
+ XmlResourceParser parser = mResources.getXml(mResId);
+ mAttrs = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (parser.getName().equals(SHORTCUT)) {
+ Shortcut c = parseShortcut(parser);
+ if (c != null) {
+ list.add(c);
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ return list;
+ }
+
+ private Shortcut parseShortcut(XmlResourceParser parser)
+ throws IOException, XmlPullParserException {
+ final TypedArray sa = mResources.obtainAttributes(mAttrs, R.styleable.Shortcut);
+ Shortcut c = new Shortcut();
+
+ final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true);
+ if (!enabled) return null;
+ final String id = sa.getString(R.styleable.Shortcut_shortcutId);
+ final int iconResId = sa.getResourceId(R.styleable.Shortcut_icon, 0);
+ final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutShortLabel, 0);
+
+ c.pkg = mPkg;
+ c.icon = Icon.createWithResource(mPkg, iconResId);
+ c.id = id;
+ c.label = mResources.getString(titleResId);
+ c.name = mName;
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_TAG) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (parser.getName().equals(INTENT)) {
+ c.intent = Intent.parseIntent(mResources, parser, mAttrs);
+ }
+ }
+ return c.intent != null ? c : null;
+ }
+
+ public static class Shortcut {
+ public Intent intent;
+ public String label;
+ public Icon icon;
+ public String pkg;
+ public String id;
+ public String name;
+
+ public static Shortcut create(Context context, String value) {
+ String[] sp = value.split("::");
+ try {
+ for (Shortcut shortcut : new ShortcutParser(context,
+ new ComponentName(sp[0], sp[1])).getShortcuts()) {
+ if (shortcut.id.equals(sp[2])) {
+ return shortcut;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(pkg);
+ builder.append("::");
+ builder.append(name);
+ builder.append("::");
+ builder.append(id);
+ return builder.toString();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java b/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java
deleted file mode 100644
index a068172..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.tuner;
-
-import android.app.AlertDialog;
-import android.app.UiModeManager;
-import android.content.Context;
-import android.os.SystemProperties;
-import android.support.v7.preference.ListPreference;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import com.android.systemui.R;
-
-import libcore.util.Objects;
-
-import com.google.android.collect.Lists;
-
-import java.io.File;
-import java.util.ArrayList;
-
-public class ThemePreference extends ListPreference {
-
- public ThemePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void onAttached() {
- super.onAttached();
- String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
- if (TextUtils.isEmpty(def)) {
- def = getContext().getString(R.string.default_theme);
- }
- String[] fileList = new File("/vendor/overlay").list();
- ArrayList<String> options = fileList != null
- ? Lists.newArrayList(fileList) : new ArrayList<>();
- if (!options.contains(def)) {
- options.add(0, def);
- }
- String[] list = options.toArray(new String[options.size()]);
- setVisible(options.size() > 1);
- setEntries(list);
- setEntryValues(list);
- updateValue();
- }
-
- private void updateValue() {
- setValue(getContext().getSystemService(UiModeManager.class).getTheme());
- }
-
- @Override
- protected void notifyChanged() {
- super.notifyChanged();
- if (!Objects.equal(getValue(),
- getContext().getSystemService(UiModeManager.class).getTheme())) {
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.change_theme_reboot)
- .setPositiveButton(com.android.internal.R.string.global_action_restart, (d, i)
- -> getContext().getSystemService(UiModeManager.class)
- .setTheme(getValue()))
- .setNegativeButton(android.R.string.cancel, (d, i) -> updateValue())
- .show();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 565ac08..fb94061 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -33,6 +33,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -126,6 +127,12 @@
return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser);
}
+ public String getValue(String setting, String def) {
+ String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
+ if (ret == null) return def;
+ return ret;
+ }
+
public void setValue(String setting, int value) {
Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser);
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 2c90e62..9a16d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -43,6 +43,7 @@
import com.android.internal.R;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.SystemUI;
+import com.android.systemui.util.NotificationChannels;
import java.util.List;
@@ -206,6 +207,7 @@
.setStyle(new Notification.BigTextStyle().bigText(text))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setLocalOnly(true)
+ .setChannel(NotificationChannels.STORAGE)
.setCategory(Notification.CATEGORY_SYSTEM)
.setDeleteIntent(buildSnoozeIntent(fsUuid));
SystemUI.overrideNotificationAppName(mContext, builder);
@@ -225,6 +227,7 @@
R.string.ext_media_unsupported_notification_message, disk.getDescription());
Notification.Builder builder = new Notification.Builder(mContext)
+ .setChannel(NotificationChannels.STORAGE)
.setSmallIcon(getSmallIcon(disk, VolumeInfo.STATE_UNMOUNTABLE))
.setColor(mContext.getColor(R.color.system_notification_accent_color))
.setContentTitle(title)
@@ -331,7 +334,6 @@
return buildNotificationBuilder(vol, title, text)
.setCategory(Notification.CATEGORY_PROGRESS)
- .setPriority(Notification.PRIORITY_LOW)
.setOngoing(true)
.build();
}
@@ -360,7 +362,6 @@
buildUnmountPendingIntent(vol)))
.setContentIntent(initIntent)
.setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()))
- .setCategory(Notification.CATEGORY_SYSTEM)
.build();
} else {
@@ -377,8 +378,7 @@
mContext.getString(R.string.ext_media_unmount_action),
buildUnmountPendingIntent(vol)))
.setContentIntent(browseIntent)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .setPriority(Notification.PRIORITY_LOW);
+ .setCategory(Notification.CATEGORY_SYSTEM);
// Non-adoptable disks can't be snoozed.
if (disk.isAdoptable()) {
builder.setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()));
@@ -402,7 +402,6 @@
return buildNotificationBuilder(vol, title, text)
.setCategory(Notification.CATEGORY_PROGRESS)
- .setPriority(Notification.PRIORITY_LOW)
.setOngoing(true)
.build();
}
@@ -485,8 +484,8 @@
.setStyle(new Notification.BigTextStyle().bigText(text))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setLocalOnly(true)
+ .setChannel(NotificationChannels.STORAGE)
.setCategory(Notification.CATEGORY_PROGRESS)
- .setPriority(Notification.PRIORITY_LOW)
.setProgress(100, status, false)
.setOngoing(true);
SystemUI.overrideNotificationAppName(mContext, builder);
@@ -537,7 +536,7 @@
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setLocalOnly(true)
.setCategory(Notification.CATEGORY_SYSTEM)
- .setPriority(Notification.PRIORITY_LOW)
+ .setChannel(NotificationChannels.STORAGE)
.setAutoCancel(true);
SystemUI.overrideNotificationAppName(mContext, builder);
@@ -564,6 +563,7 @@
private Notification.Builder buildNotificationBuilder(VolumeInfo vol, CharSequence title,
CharSequence text) {
Notification.Builder builder = new Notification.Builder(mContext)
+ .setChannel(NotificationChannels.STORAGE)
.setSmallIcon(getSmallIcon(vol.getDisk(), vol.getState()))
.setColor(mContext.getColor(R.color.system_notification_accent_color))
.setContentTitle(title)
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
new file mode 100644
index 0000000..6bb8aea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.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.systemui.util;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+
+import android.content.Context;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+import java.util.Arrays;
+
+public class NotificationChannels extends SystemUI {
+ public static String ALERTS = "ALR";
+ public static String SCREENSHOTS = "SCN";
+ public static String SECURITY = "SEC";
+ public static String USER = "USR";
+ public static String STORAGE = "DSK";
+
+ @VisibleForTesting
+ static void createAll(Context context) {
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
+ nm.createNotificationChannels(Arrays.asList(
+ new NotificationChannel(
+ ALERTS,
+ context.getString(R.string.notification_channel_alerts),
+ NotificationManager.IMPORTANCE_HIGH),
+ new NotificationChannel(
+ SCREENSHOTS,
+ context.getString(R.string.notification_channel_screenshot),
+ NotificationManager.IMPORTANCE_DEFAULT),
+ new NotificationChannel(
+ SECURITY,
+ context.getString(R.string.notification_channel_security),
+ NotificationManager.IMPORTANCE_HIGH),
+ new NotificationChannel(
+ USER,
+ context.getString(R.string.notification_channel_user_status),
+ NotificationManager.IMPORTANCE_MIN),
+ new NotificationChannel(
+ STORAGE,
+ context.getString(R.string.notification_channel_storage),
+ NotificationManager.IMPORTANCE_LOW)
+ ));
+ }
+
+ @Override
+ public void start() {
+ createAll(mContext);
+ }
+}
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..78145fe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -16,32 +16,26 @@
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;
import com.android.systemui.qs.tiles.DndTile;
-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 +63,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 +127,6 @@
}
@Override
- public ZenModeController getZenController() {
- return mZenModeController;
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfig) {
// noop
}
@@ -166,7 +154,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/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index dec8ba6..408e8f3 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -17,6 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.systemui.tests">
+ <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
<uses-permission android:name="android.permission.INJECT_EVENTS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
@@ -28,6 +29,11 @@
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
+ <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
<application>
<uses-library android:name="android.test.runner" />
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..fb4b6bd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.any;
+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;
+
+import java.io.PrintWriter;
+
+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, mock(PrintWriter.class), null);
+ verify(d).dump(eq(null), any(), 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..f258e5d 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,16 @@
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);
+ SystemUIFactory.createFromConfig(mContext);
+ mDependency = new TestDependency();
+ mDependency.mContext = mContext;
+ mDependency.start();
}
@After
@@ -78,11 +86,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..5345031 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,25 @@
super(QSFragment.class);
}
+ @Before
+ public void addLeakCheckDependencies() {
+ injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
+ 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..166fcb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
@@ -63,7 +63,7 @@
private static final String TEST_CHANNEL = "test_channel";
private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
- private NotificationGuts mNotificationGuts;
+ private NotificationInfo mNotificationInfo;
private final INotificationManager mMockINotificationManager = mock(INotificationManager.class);
private final PackageManager mMockPackageManager = mock(PackageManager.class);
private NotificationChannel mNotificationChannel;
@@ -76,7 +76,7 @@
// Inflate the layout
final LayoutInflater layoutInflater =
LayoutInflater.from(InstrumentationRegistry.getTargetContext());
- mNotificationGuts = (NotificationGuts) layoutInflater.inflate(R.layout.notification_guts,
+ mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
null);
// PackageManager must return a packageInfo and applicationInfo.
@@ -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);
}
@@ -99,18 +98,18 @@
@UiThreadTest
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
- final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.pkgname);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
}
@Test
@UiThreadTest
public void testBindNotification_SetsTextChannelName() throws Exception {
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
- final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.channel_name);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -118,12 +117,12 @@
@UiThreadTest
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);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel,
+ (View v, int appUid) -> { latch.countDown(); }, null, null);
- final TextView settingsButton =
- (TextView) mNotificationGuts.findViewById(R.id.more_settings);
+ final TextView settingsButton =
+ (TextView) mNotificationInfo.findViewById(R.id.more_settings);
settingsButton.performClick();
// Verify that listener was triggered.
assertEquals(0, latch.getCount());
@@ -133,12 +132,12 @@
@UiThreadTest
public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null,
(View v) -> { latch.countDown(); },
null);
- final TextView doneButton = (TextView) mNotificationGuts.findViewById(R.id.done);
+ final TextView doneButton = (TextView) mNotificationInfo.findViewById(R.id.done);
doneButton.performClick();
// Verify that listener was triggered.
assertEquals(0, latch.getCount());
@@ -147,39 +146,39 @@
@Test
@UiThreadTest
public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
- assertFalse(mNotificationGuts.hasImportanceChanged());
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ assertFalse(mNotificationInfo.hasImportanceChanged());
}
@Test
@UiThreadTest
public void testHasImportanceChanged_ReturnsTrueAfterButtonChecked() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
// Find the high button and check it.
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
- assertTrue(mNotificationGuts.hasImportanceChanged());
+ assertTrue(mNotificationInfo.hasImportanceChanged());
}
@Test
@UiThreadTest
public void testImportanceButtonCheckedBasedOnInitialImportance() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
assertTrue(highButton.isChecked());
}
@Test
@UiThreadTest
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -188,10 +187,10 @@
@UiThreadTest
public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
@@ -200,10 +199,10 @@
@Test
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception {
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -212,10 +211,10 @@
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnspecified() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -224,12 +223,12 @@
@UiThreadTest
public void testCloseControls_CallsUpdateNotificationChannelIfChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
assertEquals(NotificationManager.IMPORTANCE_HIGH, mNotificationChannel.getImportance());
@@ -239,12 +238,12 @@
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfSaveFalse() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
- mNotificationGuts.closeControls(-1, -1, false);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -253,10 +252,10 @@
@UiThreadTest
public void testEnabledSwitchOnByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertTrue(enabledSwitch.isChecked());
}
@@ -264,10 +263,10 @@
@UiThreadTest
public void testEnabledSwitchVisibleByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
}
@@ -275,10 +274,11 @@
@UiThreadTest
public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null,
+ Collections.singleton(TEST_PACKAGE_NAME));
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
}
@@ -286,12 +286,13 @@
@UiThreadTest
public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null,
+ Collections.singleton(TEST_PACKAGE_NAME));
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
@@ -300,14 +301,14 @@
@UiThreadTest
public void testEnabledSwitchOverridesOtherButtons() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
- RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
+ RadioButton lowButton = (RadioButton) mNotificationInfo.findViewById(R.id.low_importance);
lowButton.setChecked(true);
enabledSwitch.setChecked(false);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
assertEquals(NotificationManager.IMPORTANCE_NONE, mNotificationChannel.getImportance());
}
}
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 34743ff..e28d077 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
@@ -16,12 +16,16 @@
import static org.mockito.Mockito.mock;
+import android.content.Context;
+import android.view.WindowManager;
+
import com.android.systemui.FragmentTestCase;
import com.android.systemui.recents.Recents;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
+import org.junit.Test;
public class NavigationBarFragmentTest extends FragmentTestCase {
@@ -32,9 +36,19 @@
@Before
public void setup() {
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
- mContext.putComponent(PhoneStatusBar.class, mock(PhoneStatusBar.class));
+ mContext.putComponent(StatusBar.class, mock(StatusBar.class));
mContext.putComponent(Recents.class, mock(Recents.class));
mContext.putComponent(Divider.class, mock(Divider.class));
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mock(WindowManager.class));
+ }
+
+ @Test
+ public void testHomeLongPress() {
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mock(WindowManager.class));
+ NavigationBarFragment navigationBarFragment = (NavigationBarFragment) mFragment;
+
+ postAndWait(() -> mFragments.dispatchResume());
+ navigationBarFragment.onHomeLongClick(navigationBarFragment.getView());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
new file mode 100644
index 0000000..309559b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.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 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 org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StatusBarTest extends SysuiTestCase {
+
+ StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ StatusBar mStatusBar;
+
+ @Before
+ public void setup() {
+ mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager.class);
+ mStatusBar = new TestableStatusBar(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);
+
+ mStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+ }
+
+ @Test
+ public void executeRunnableDismissingKeyguard_nullRunnable_showing() {
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+
+ mStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+ }
+
+ @Test
+ public void executeRunnableDismissingKeyguard_nullRunnable_notShowing() {
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+
+ mStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+ }
+
+ static class TestableStatusBar extends StatusBar {
+ public TestableStatusBar(StatusBarKeyguardViewManager man) {
+ mStatusBarKeyguardViewManager = man;
+ }
+
+ @Override
+ protected 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..b544d9d 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;
@@ -37,6 +36,8 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class CallbackHandlerTest {
@@ -110,8 +111,9 @@
int qsType = R.drawable.ic_qs_signal_1x;
boolean wide = true;
int subId = 5;
+ boolean roaming = true;
mHandler.setMobileDataIndicators(status, qs, type, qsType, in, out, typeDescription,
- description, wide, subId);
+ description, wide, subId, roaming);
waitForCallbacks();
ArgumentCaptor<IconState> statusArg = ArgumentCaptor.forClass(IconState.class);
@@ -127,7 +129,7 @@
Mockito.verify(mSignalCallback).setMobileDataIndicators(statusArg.capture(),
qsArg.capture(), typeIconArg.capture(), qsTypeIconArg.capture(), inArg.capture(),
outArg.capture(), typeContentArg.capture(), descArg.capture(), wideArg.capture(),
- subIdArg.capture());
+ subIdArg.capture(), eq(roaming));
assertEquals(status, statusArg.getValue());
assertEquals(qs, qsArg.getValue());
assertEquals(type, (int) typeIconArg.getValue());
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..0e5f513 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;
@@ -54,6 +55,7 @@
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -79,6 +81,7 @@
protected Config mConfig;
protected CallbackHandler mCallbackHandler;
protected SubscriptionDefaults mMockSubDefaults;
+ protected NetworkScoreManager mMockNetworkScoreManager;
protected int mSubId;
@@ -105,6 +108,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 +120,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 +163,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();
@@ -291,7 +298,7 @@
iconArg.capture(),
anyInt(),
typeIconArg.capture(), dataInArg.capture(), dataOutArg.capture(),
- anyString(), anyString(), anyBoolean(), anyInt());
+ anyString(), anyString(), anyBoolean(), anyInt(), anyBoolean());
IconState iconState = iconArg.getValue();
assertEquals("Visibility in, quick settings", visible, iconState.visible);
assertEquals("Signal icon in, quick settings", icon, iconState.icon);
@@ -303,6 +310,11 @@
}
protected void verifyLastMobileDataIndicators(boolean visible, int icon, int typeIcon) {
+ verifyLastMobileDataIndicators(visible, icon, typeIcon, false);
+ }
+
+ protected void verifyLastMobileDataIndicators(boolean visible, int icon, int typeIcon,
+ boolean roaming) {
ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
@@ -312,9 +324,10 @@
any(),
typeIconArg.capture(),
anyInt(), anyBoolean(), anyBoolean(), anyString(), anyString(), anyBoolean(),
- anyInt());
+ anyInt(), eq(roaming));
IconState iconState = iconArg.getValue();
+ assertEquals("Signal icon in status bar", icon, iconState.icon);
assertEquals("Data icon in status bar", typeIcon, (int) typeIconArg.getValue());
assertEquals("Visibility in status bar", visible, iconState.visible);
}
@@ -335,7 +348,7 @@
qsTypeIconArg.capture(),
dataInArg.capture(),
dataOutArg.capture(),
- anyString(), anyString(), anyBoolean(), anyInt());
+ anyString(), anyString(), anyBoolean(), anyInt(), anyBoolean());
IconState iconState = iconArg.getValue();
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..d7f961c 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;
@@ -23,19 +25,6 @@
}
@Test
- public void testRoamingDataIcon() {
- setupDefaultSignal();
- setGsmRoaming(true);
-
- verifyLastMobileDataIndicators(true,
- TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING[1][DEFAULT_LEVEL],
- TelephonyIcons.ROAMING_ICON,
- true,
- TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[1][DEFAULT_LEVEL],
- TelephonyIcons.QS_DATA_R, false, false);
- }
-
- @Test
public void test2gDataIcon() {
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
@@ -98,10 +87,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..2c0f9c9 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.
@@ -156,13 +158,12 @@
for (int testStrength = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
testStrength <= SignalStrength.SIGNAL_STRENGTH_GREAT; testStrength++) {
setupDefaultSignal();
- setConnectivity(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
setGsmRoaming(true);
setLevel(testStrength);
verifyLastMobileDataIndicators(true,
TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING[1][testStrength],
- TelephonyIcons.ROAMING_ICON);
+ DEFAULT_ICON, true);
}
}
@@ -177,7 +178,7 @@
verifyLastMobileDataIndicators(true,
TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING[1][testStrength],
- TelephonyIcons.ROAMING_ICON);
+ TelephonyIcons.DATA_1X[1][0 /* No direction */], true);
}
}
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..9b382f6 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
@@ -3,22 +3,46 @@
import android.content.Intent;
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 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 +51,13 @@
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;
+
@Test
public void testWifiIcon() {
String testSsid = "Test SSID";
@@ -47,6 +78,82 @@
}
@Test
+ public void testBadgedWifiIcon() throws Exception {
+ // TODO(sghuman): Refactor this setup code when creating a test for the badged QsIcon.
+ 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);
+
+ // Must set the Settings value before instantiating the NetworkControllerImpl due to bugs in
+ // FakeSettingsProvider.
+ SettingOverrider settingsOverrider =
+ mContext.getSettingsProvider().acquireOverridesBuilder(this)
+ .addSetting("global", Settings.Global.NETWORK_SCORING_UI_ENABLED, "1")
+ .build();
+ super.setUp(); // re-instantiate NetworkControllImpl now that setting has been updated
+ setupNetworkScoreManager();
+
+ // Test Requesting Scores
+ 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));
+
+ // Test badge is set
+ setWifiLevel(testLevel);
+
+ ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+ Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
+ anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(),
+ any());
+ IconState iconState = iconArg.getValue();
+
+ assertEquals("Badged Wifi Resource is set",
+ Utils.WIFI_PIE_FOR_BADGING[testLevel],
+ iconState.icon);
+ assertEquals("SD Badge is set",
+ Utils.getWifiBadgeResource(ScoredNetwork.BADGING_SD),
+ iconState.iconOverlay);
+
+ settingsOverrider.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 +204,7 @@
@Test
public void testRoamingIconDuringWifi() {
// Setup normal connection
- String testSsid = "Test SSID";
+ String testSsid = "\"Test SSID\"";
int testLevel = 2;
setWifiEnabled(true);
setWifiState(true, testSsid);
@@ -109,9 +216,10 @@
setGsmRoaming(true);
// Still be on wifi though.
setConnectivity(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivity(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
verifyLastMobileDataIndicators(true,
TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING[1][DEFAULT_LEVEL],
- TelephonyIcons.ROAMING_ICON);
+ 0, true);
}
protected void setWifiActivity(int activity) {
@@ -137,12 +245,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 +281,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 +294,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/util/ChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
new file mode 100644
index 0000000..8949598
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.NotificationChannels;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ChannelsTest extends SysuiTestCase {
+ private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
+
+ @Before
+ public void setup() throws Exception {
+ mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager);
+ }
+
+ @Test
+ public void testChannelSetup() {
+ Set<String> ALL_CHANNELS = new ArraySet<>(Arrays.asList(
+ NotificationChannels.ALERTS,
+ NotificationChannels.SCREENSHOTS,
+ NotificationChannels.SECURITY,
+ NotificationChannels.USER,
+ NotificationChannels.STORAGE
+ ));
+ NotificationChannels.createAll(mContext);
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(mMockNotificationManager).createNotificationChannels(captor.capture());
+ final List<NotificationChannel> list = captor.getValue();
+ assertEquals(ALL_CHANNELS.size(), list.size());
+ list.forEach((chan) -> assertTrue(ALL_CHANNELS.contains(chan.getId())));
+ }
+}
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 62ea9e3..e0089c2 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3303,6 +3303,54 @@
RESERVED_FOR_LOGBUILDER_TIMESTAMP = 805;
RESERVED_FOR_LOGBUILDER_PACKAGENAME = 806;
+ // ACTION: "Force stop" action on an app
+ ACTION_APP_FORCE_STOP = 807;
+
+ // OPEN: Settings > Apps > Gear > Special Access > Install other apps
+ // CATEGORY: SETTINGS
+ // 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;
+
+ // OPEN: Settings > Language & input > Text-to-speech output -> Speech rate & pitch
+ // CATEGORY: SETTINGS
+ // OS: 8.0
+ TTS_SLIDERS = 815;
+
+ // ACTION: Settings -> Display -> Theme
+ ACTION_THEME = 816;
+
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Select to Speak
+ // ACTION: Select to Speak configuration is chosen
+ // SUBTYPE: 0 is off, 1 is on
+ // CATEGORY: SETTINGS
+ // OS: N
+ SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK = 817;
+
+ // OPEN: Settings > System > Backup
+ // CATEGORY: SETTINGS
+ // OS: O
+ BACKUP_SETTINGS = 818;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/proto/src/task_snapshot.proto b/proto/src/task_snapshot.proto
new file mode 100644
index 0000000..c9d5c27
--- /dev/null
+++ b/proto/src/task_snapshot.proto
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ syntax = "proto3";
+
+ package com.android.server.wm;
+
+ option java_package = "com.android.server.wm";
+ option java_outer_classname = "WindowManagerProtos";
+
+ message TaskSnapshotProto {
+ int32 orientation = 1;
+ int32 inset_left = 2;
+ int32 inset_top = 3;
+ int32 inset_right = 4;
+ int32 inset_bottom = 5;
+ }
\ No newline at end of file
diff --git a/rs/java/android/renderscript/ScriptIntrinsicBLAS.java b/rs/java/android/renderscript/ScriptIntrinsicBLAS.java
index c04191b..49a71b4 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicBLAS.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicBLAS.java
@@ -2441,10 +2441,10 @@
* @param TransA The type of transpose applied to matrix A.
* @param TransB The type of transpose applied to matrix B.
* @param alpha The scalar alpha.
- * @param A The input allocation contains matrix A, supported elements type {@link Element#F64_2
- * @param B The input allocation contains matrix B, supported elements type {@link Element#F64_2
+ * @param A The input allocation contains matrix A, supported elements type {@link Element#F64_2}.
+ * @param B The input allocation contains matrix B, supported elements type {@link Element#F64_2}.
* @param beta The scalar beta.
- * @param C The input allocation contains matrix C, supported elements type {@link Element#F64_2
+ * @param C The input allocation contains matrix C, supported elements type {@link Element#F64_2}.
*/
public void ZGEMM(@Transpose int TransA, @Transpose int TransB, Double2 alpha, Allocation A,
Allocation B, Double2 beta, Allocation C) {
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 3523706..ce50b30 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -42,6 +42,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -1547,6 +1548,13 @@
}
@Override
+ public boolean isRequestPinAppWidgetSupported() {
+ return LocalServices.getService(ShortcutServiceInternal.class)
+ .isRequestPinItemSupported(UserHandle.getCallingUserId(),
+ LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET);
+ }
+
+ @Override
public boolean requestPinAppWidget(String callingPackage, ComponentName componentName,
IntentSender resultSender) {
final int callingUid = Binder.getCallingUid();
diff --git a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
new file mode 100644
index 0000000..e674309
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.autofill;
+
+import static com.android.server.autofill.Helper.DEBUG;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.FrameLayout;
+
+/**
+ * A window above the application that is smartly anchored to a rectangular region.
+ */
+final class AnchoredWindow {
+ private static final String TAG = "AutoFill";
+
+ private final WindowManager mWm;
+ private final View mRootView;
+ private final View mView;
+ private final int mWidth;
+ private final int mHeight;
+ private boolean mIsShowing = false;
+
+ /**
+ * Constructor.
+ *
+ * @param wm window manager that draws the view on a window
+ * @param view singleton view in the window
+ * @param width requested width of the view
+ * @param height requested height of the view
+ */
+ AnchoredWindow(WindowManager wm, View view, int width, int height) {
+ mWm = wm;
+ mRootView = wrapView(view, width, height);
+ mView = view;
+ mWidth = width;
+ mHeight = height;
+ }
+
+ /**
+ * Shows the window.
+ *
+ * @param bounds the rectangular region this window should be anchored to
+ */
+ void show(Rect bounds) {
+ LayoutParams params = createBaseLayoutParams();
+ params.x = bounds.left;
+ params.y = bounds.bottom;
+
+ if (!mIsShowing) {
+ if (DEBUG) Slog.d(TAG, "adding view " + mView);
+ mWm.addView(mRootView, params);
+ } else {
+ if (DEBUG) Slog.d(TAG, "updating view " + mView);
+ mWm.updateViewLayout(mRootView, params);
+ }
+ mIsShowing = true;
+ }
+
+ /**
+ * Hides the window.
+ */
+ void hide() {
+ if (DEBUG) Slog.d(TAG, "removing view " + mView);
+ if (mIsShowing) {
+ mWm.removeView(mRootView);
+ }
+ mIsShowing = false;
+ }
+
+ /**
+ * Wraps a view with a SelfRemovingView and sets its requested width and height.
+ */
+ private View wrapView(View view, int width, int height) {
+ ViewGroup viewGroup = new SelfRemovingView(view.getContext());
+ viewGroup.addView(view, new ViewGroup.LayoutParams(width, height));
+ return viewGroup;
+ }
+
+ private static LayoutParams createBaseLayoutParams() {
+ LayoutParams params = new LayoutParams();
+ // TODO(b/33197203): LayoutParams.TYPE_AUTOFILL
+ params.type = LayoutParams.TYPE_SYSTEM_ALERT;
+ params.flags =
+ LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+ | LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | LayoutParams.FLAG_LAYOUT_NO_LIMITS
+ | LayoutParams.FLAG_NOT_FOCUSABLE
+ | LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+ params.gravity = Gravity.TOP | Gravity.LEFT;
+ params.width = LayoutParams.WRAP_CONTENT;
+ params.height = LayoutParams.WRAP_CONTENT;
+ return params;
+ }
+
+ /** FrameLayout that listens for touch events removes itself if the touch event is outside. */
+ private final class SelfRemovingView extends FrameLayout {
+ public SelfRemovingView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ hide();
+ return true;
+ } else {
+ return super.onTouchEvent(event);
+ }
+ }
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 87eaf29..637656c 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -19,12 +19,9 @@
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
-
-import static com.android.server.autofill.AutoFillUI.MSG_SHOW_ALL_NOTIFICATIONS;
-import static com.android.server.autofill.AutoFillUI.SHOW_ALL_NOTIFICATIONS_DELAY_MS;
import android.Manifest;
+import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -32,33 +29,38 @@
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.autofill.IAutoFillManagerService;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.util.LocalLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
+import android.view.autofill.AutoFillId;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
import com.android.server.FgThread;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
/**
* Entry point service for auto-fill management.
@@ -70,13 +72,13 @@
public final class AutoFillManagerService extends SystemService {
private static final String TAG = "AutoFillManagerService";
- static final boolean DEBUG = true; // TODO: change to false once stable
+ static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable
private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
- private static final int ARG_NOT_USED = 0;
-
protected static final int MSG_UNBIND = 1;
+ protected static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 2;
+ protected static final int MSG_REQUEST_AUTO_FILL = 3;
private final AutoFillManagerServiceStub mServiceStub;
private final AutoFillUI mUi;
@@ -85,23 +87,39 @@
private final Object mLock = new Object();
- private final Handler mHandler = new Handler() {
+ private final HandlerCaller.Callback mHandlerCallback = new HandlerCaller.Callback() {
+
@Override
- public void handleMessage(Message msg) {
+ public void executeMessage(Message msg) {
switch (msg.what) {
- case MSG_UNBIND:
- removeStaleServiceForUser(msg.arg1);
+ case MSG_UNBIND: {
+ synchronized (mLock) {
+ removeCachedServiceLocked(msg.arg1);
+ }
return;
- case MSG_SHOW_ALL_NOTIFICATIONS:
- mUi.showAllNotifications();
+ } case MSG_REQUEST_AUTO_FILL_FOR_USER: {
+ final int userId = msg.arg1;
+ final int flags = msg.arg2;
+ handleAutoFillForUser(userId, flags);
return;
- default:
+ } case MSG_REQUEST_AUTO_FILL: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final int userId = msg.arg1;
+ final int flags = msg.arg2;
+ final IBinder activityToken = (IBinder) args.arg1;
+ final AutoFillId autoFillId = (AutoFillId) args.arg2;
+ final Rect bounds = (Rect) args.arg3;
+ handleAutoFill(activityToken, userId, autoFillId, bounds, flags);
+ return;
+ } default: {
Slog.w(TAG, "Invalid message: " + msg);
+ }
}
}
-
};
+ private HandlerCaller mHandlerCaller;
+
/**
* Cache of {@link AutoFillManagerServiceImpl} per user id.
* <p>
@@ -119,9 +137,14 @@
@GuardedBy("mLock")
private SparseArray<AutoFillManagerServiceImpl> mServicesCache = new SparseArray<>();
+ // TODO(b/33197203): set a different max (or disable it) on low-memory devices.
+ private final LocalLog mRequestsHistory = new LocalLog(100);
+
public AutoFillManagerService(Context context) {
super(context);
+ mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true);
+
mContext = context;
mUi = new AutoFillUI(context, this, mLock);
mResolver = context.getContentResolver();
@@ -139,14 +162,6 @@
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
new SettingsObserver(BackgroundThread.getHandler());
}
- if (phase == PHASE_BOOT_COMPLETED) {
- // TODO: if sent right away, the notification is not displayed. Since the notification
- // mechanism is a temporary approach anyways, just delay it..
- if (DEBUG)
- Slog.d(TAG, "Showing notifications in " + SHOW_ALL_NOTIFICATIONS_DELAY_MS + "ms");
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ALL_NOTIFICATIONS),
- SHOW_ALL_NOTIFICATIONS_DELAY_MS);
- }
}
private AutoFillManagerServiceImpl newServiceForUser(int userId) {
@@ -168,11 +183,11 @@
if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
+ serviceComponent + ", info: " + serviceInfo);
if (serviceInfo == null) {
- Slog.w(TAG, "no service info for " + serviceComponent);
+ if (DEBUG) Slog.d(TAG, "no service info for " + serviceComponent);
return null;
}
- return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, FgThread.getHandler(),
- userId, serviceInfo.applicationInfo.uid, serviceComponent,
+ return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, mRequestsHistory,
+ FgThread.getHandler(), userId, serviceInfo.applicationInfo.uid, serviceComponent,
SERVICE_BINDING_LIFETIME_MS);
}
@@ -200,64 +215,92 @@
}
// Keep service connection alive for a while, in case user needs to interact with it
// (for example, to save the data that was inputted in)
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UNBIND, userId, ARG_NOT_USED),
+ if (mHandlerCaller.hasMessages(MSG_UNBIND)) {
+ mHandlerCaller.removeMessages(MSG_UNBIND);
+ }
+ mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessageI(MSG_UNBIND, userId),
SERVICE_BINDING_LIFETIME_MS);
return service;
}
/**
- * Removes a cached service, but respecting its TTL.
+ * Removes a cached service for a given user.
*/
- private void removeStaleServiceForUser(int userId) {
- synchronized (mLock) {
- removeCachedService(userId, false);
- }
- }
-
- /**
- * Removes a cached service, even if it has TTL.
- */
- void removeCachedServiceForUserLocked(int userId) {
- removeCachedService(userId, true);
- }
-
- private void removeCachedService(int userId, boolean force) {
+ void removeCachedServiceLocked(int userId) {
if (DEBUG) Log.d(TAG, "removing cached service for userId " + userId);
final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
if (service == null) {
- Log.w(TAG, "removeCachedServiceForUser(): no cached service for userId " + userId);
- return;
- }
- if (!force) {
- // Check TTL first.
- final long now = SystemClock.uptimeMillis();
- if (service.mEstimateTimeOfDeath > now) {
- if (DEBUG) {
- final StringBuilder msg = new StringBuilder("service has some TTL left: ");
- TimeUtils.formatDuration(service.mEstimateTimeOfDeath - now, msg);
- Log.d(TAG, msg.toString());
- }
- return;
+ if (DEBUG) {
+ Log.d(TAG, "removeCachedServiceForUser(): no cached service for userId " + userId);
}
+ return;
}
mServicesCache.delete(userId);
service.stopLocked();
+ }
+ private void handleAutoFill(IBinder activityToken, int userId, AutoFillId autoFillId,
+ Rect bounds, int flags) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service != null) {
+ // TODO(b/33197203): must pass AUTO_FILL_FLAG_TYPE_FILL because AM is expecting
+ // either that flag or AUTO_FILL_FLAG_TYPE_SAVE; should go away once save is
+ // refactored
+ flags |= AUTO_FILL_FLAG_TYPE_FILL;
+ service.requestAutoFillLocked(activityToken, autoFillId, bounds, flags);
+ }
+ }
+ }
+
+ private void handleAutoFillForUser(int userId, int flags) {
+ if (DEBUG) {
+ Slog.d(TAG, "handler.requestAutoFillForUser(): id=" + userId + ", flags=" + flags);
+ }
+ final List<IBinder> topActivities = LocalServices
+ .getService(ActivityManagerInternal.class).getTopVisibleActivities();
+ if (DEBUG)
+ Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
+ if (topActivities.isEmpty()) {
+ Slog.w(TAG, "Could not get top activity");
+ return;
+ }
+ final IBinder activityToken = topActivities.get(0);
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "no service for user " + userId);
+ return;
+ }
+ service.requestAutoFillLocked(activityToken, null, null, flags);
+ }
}
final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
@Override
- public void requestAutoFill(IBinder activityToken, int userId, Bundle extras, int flags) {
- if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
+ public void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
+ if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", autoFillId=" + id
+ + ", bounds=" + bounds);
+
+ // Make sure its called by the top activity.
+ final int uid = Binder.getCallingUid();
+ final IBinder activityToken = LocalServices.getService(ActivityManagerInternal.class)
+ .getTopVisibleActivity(uid);
+ if (activityToken == null) {
+ throw new SecurityException("uid " + uid + " does not own the top activity");
+ }
+
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL,
+ UserHandle.getCallingUserId(), flags, activityToken, id, bounds));
+ }
+
+ @Override
+ public void requestAutoFillForUser(int userId, int flags) {
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- synchronized (mLock) {
- final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
- if (service != null) {
- service.requestAutoFill(activityToken, extras, flags);
- }
- }
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageII(
+ MSG_REQUEST_AUTO_FILL_FOR_USER, userId, flags));
}
@Override
@@ -283,6 +326,8 @@
}
}
}
+ pw.println("Requests history:");
+ mRequestsHistory.reverseDump(fd, pw, args);
}
@Override
@@ -291,7 +336,6 @@
(new AutoFillManagerServiceShellCommand(this)).exec(
this, in, out, err, args, callback, resultReceiver);
}
-
}
private final class SettingsObserver extends ContentObserver {
@@ -306,8 +350,7 @@
public void onChange(boolean selfChange, Uri uri, int userId) {
if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId);
synchronized (mLock) {
- removeCachedServiceForUserLocked(userId);
- mUi.updateNotification(userId);
+ removeCachedServiceLocked(userId);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index ae21b07..0dd891c 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -16,12 +16,18 @@
package com.android.server.autofill;
-import static com.android.server.autofill.AutoFillManagerService.DEBUG;
+import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_ERROR;
+import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_REQUESTED;
+import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_SUCCESS;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
+import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_HIDE;
+
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.bundleToString;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.assist.AssistStructure;
import android.content.BroadcastReceiver;
@@ -31,12 +37,17 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
-import android.icu.text.DateFormat;
+import android.graphics.Rect;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintService;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.Binder;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.service.autofill.AutoFillService;
@@ -45,6 +56,8 @@
import android.service.autofill.IAutoFillServerCallback;
import android.service.autofill.IAutoFillService;
import android.service.voice.VoiceInteractionSession;
+import android.util.LocalLog;
+import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -55,11 +68,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
-import com.android.server.LocalServices;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.Arrays;
-import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -73,11 +85,12 @@
private static final String TAG = "AutoFillManagerServiceImpl";
/** Used do assign ids to new ServerCallback instances. */
- private static int sServerCallbackCounter = 0;
+ private static int sSessionIdCounter = 0;
private final int mUserId;
private final int mUid;
private final ComponentName mComponent;
+ private final String mComponentName;
private final Context mContext;
private final IActivityManager mAm;
private final Object mLock;
@@ -85,14 +98,15 @@
private final AutoFillManagerService mManagerService;
private final AutoFillUI mUi;
- // TODO(b/33197203): improve its usage
- // - set maximum number of entries
- // - disable on low-memory devices.
- private final List<String> mRequestHistory = new LinkedList<>();
+ // Token used for fingerprint authentication
+ // TODO(b/33197203): create on demand?
+ private final IBinder mAuthToken = new Binder();
@GuardedBy("mLock")
private final List<QueuedRequest> mQueuedRequests = new LinkedList<>();
+ private final LocalLog mRequestsHistory;
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -106,13 +120,15 @@
};
/**
- * Cache of pending ServerCallbacks, keyed by {@link ServerCallback#id}.
+ * Cache of pending {@link Session}s, keyed by {@link Session#mId}.
*
- * <p>They're kept until the AutoFillService handles a request, or an error occurs.
+ * <p>They're kept until the {@link AutoFillService} finished handling a request, an error
+ * occurs, or the session times out.
*/
- // TODO(b/33197203): need to make sure service is bound while callback is pending
+ // TODO(b/33197203): need to make sure service is bound while callback is pending and/or
+ // use WeakReference
@GuardedBy("mLock")
- private static final SparseArray<ServerCallback> mServerCallbacks = new SparseArray<>();
+ private static final SparseArray<Session> mSessions = new SparseArray<>();
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
@@ -130,9 +146,10 @@
if (DEBUG) Slog.d(TAG, "queued requests:" + mQueuedRequests.size());
}
for (final QueuedRequest request: mQueuedRequests) {
- requestAutoFillLocked(request.activityToken, request.extras, request.flags,
- false);
+ requestAutoFillLocked(request.activityToken, request.autoFillId,
+ request.bounds, request.flags, false);
}
+ mQueuedRequests.clear();
}
}
@@ -141,15 +158,14 @@
if (DEBUG) Slog.d(TAG, name + " disconnected");
synchronized (mLock) {
mService = null;
- mManagerService.removeCachedServiceForUserLocked(mUserId);
+ mManagerService.removeCachedServiceLocked(mUserId);
}
}
};
-
/**
* Receiver of assist data from the app's {@link Activity}, uses the {@code resultData} as
- * the {@link ServerCallback#id}.
+ * the {@link Session#mId}.
*/
private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
@Override
@@ -163,40 +179,43 @@
}
final AssistStructure structure = resultData
.getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
- final Bundle data = resultData.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0);
- final ServerCallback serverCallback;
+ final Session session;
synchronized (mLock) {
- serverCallback = mServerCallbacks.get(resultCode);
- if (serverCallback == null) {
+ session = mSessions.get(resultCode);
+ if (session == null) {
Slog.w(TAG, "no server callback for id " + resultCode);
return;
}
- serverCallback.appCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+ session.setAppCallback(appBinder);
}
- mService.autoFill(structure, serverCallback, serverCallback.extras, flags);
+ mService.autoFill(structure, session.mServerCallback, flags);
}
};
@GuardedBy("mLock")
private IAutoFillService mService;
+ @GuardedBy("mLock")
private boolean mBound;
+ @GuardedBy("mLock")
private boolean mValid;
// Estimated time when the service will be evicted from the cache.
long mEstimateTimeOfDeath;
AutoFillManagerServiceImpl(AutoFillManagerService managerService, AutoFillUI ui,
- Context context, Object lock, Handler handler, int userId, int uid,
- ComponentName component, long ttl) {
+ Context context, Object lock, LocalLog requestsHistory, Handler handler, int userId,
+ int uid, ComponentName component, long ttl) {
mManagerService = managerService;
mUi = ui;
mContext = context;
mLock = lock;
+ mRequestsHistory = requestsHistory;
mUserId = userId;
mUid = uid;
mComponent = component;
+ mComponentName = mComponent.flattenToShortString();
mAm = ActivityManager.getService();
setLifeExpectancy(ttl);
@@ -244,60 +263,86 @@
/**
* Asks service to auto-fill an activity.
*
- * @param activityToken activity token
- * @param extras bundle to be passed to the {@link AutoFillService} method.
+ * @param activityToken activity token.
+ * @param autoFillId id of the view that requested auto-fill.
* @param flags optional flags.
*/
- void requestAutoFill(@Nullable IBinder activityToken, @Nullable Bundle extras, int flags) {
- synchronized (mLock) {
- if (!mBound) {
- Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
- return;
- }
+ void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId,
+ @Nullable Rect bounds, int flags) {
+ if (!mBound) {
+ Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
+ return;
}
- // TODO(b/33197203): activityToken should probably not be null, but we need to wait until
- // the UI is triggering the call (for now it's trough 'adb shell cmd autofill request'
- if (activityToken == null) {
- // Let's get top activities from all visible stacks.
-
- // TODO(b/33197203): overload getTopVisibleActivities() to take userId, otherwise it
- // could return activities for different users when a work profile app is displayed in
- // another window (in a multi-window environment).
- final List<IBinder> topActivities = LocalServices
- .getService(ActivityManagerInternal.class).getTopVisibleActivities();
- if (DEBUG)
- Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
- if (topActivities.isEmpty()) {
- Slog.w(TAG, "Could not get top activity");
- return;
- }
- activityToken = topActivities.get(0);
- }
-
- final String historyItem =
- DateFormat.getDateTimeInstance().format(new Date()) + " - " + activityToken;
- synchronized (mLock) {
- mRequestHistory.add(historyItem);
- requestAutoFillLocked(activityToken, extras, flags, true);
- }
+ requestAutoFillLocked(activityToken, autoFillId, bounds, flags, true);
}
- private void requestAutoFillLocked(IBinder activityToken, @Nullable Bundle extras, int flags,
- boolean queueIfNecessary) {
+ private void requestAutoFillLocked(IBinder activityToken, AutoFillId autoFillId, Rect bounds,
+ int flags, boolean queueIfNecessary) {
if (mService == null) {
if (!queueIfNecessary) {
Slog.w(TAG, "requestAutoFillLocked(): service is null");
return;
}
- if (DEBUG) Slog.d(TAG, "requestAutoFill(): service not set yet, queuing it");
- mQueuedRequests.add(new QueuedRequest(activityToken, extras, flags));
+ if (DEBUG) Slog.d(TAG, "requestAutoFillLocked(): service not set yet, queuing it");
+ mQueuedRequests.add(new QueuedRequest(activityToken, autoFillId, bounds, flags));
+ return;
+ }
+ if (activityToken == null) {
+ // Sanity check
+ Slog.wtf(TAG, "requestAutoFillLocked(): null activityToken");
return;
}
- final int callbackId = ++sServerCallbackCounter;
- final ServerCallback serverCallback = new ServerCallback(callbackId, extras);
- mServerCallbacks.put(callbackId, serverCallback);
+ final Session session = getSessionByTokenLocked(activityToken);
+
+ if (session != null) {
+ // Session already exist, update UI instead...
+ /*
+ * TODO(b/33197203): currently, it's always reusing the session, regardless of the
+ * requested autoFillId, but it should start a new session for views that
+ * were not part of the initial auto-fill dataset returned by the service. For example:
+ *
+ * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
+ * 2.User taps `first_name`.
+ * 3.Service returns a dataset with ids for `first_name` and `last_name`.
+ * 4.When user taps `first_name` (again) or `last_name`, session should be reused, but
+ * when user taps `address`, it should start a new session (since that field was
+ * not part of the initial dataset).
+ *
+ * Similarly, once the activity is auto-filled, the flag logic should be reset (so if
+ * the user taps the view again, a new auto-fill request is made)
+ */
+ if (DEBUG) {
+ Slog.d(TAG, "requestAutoFillLocked(): reusing session for token "
+ + activityToken + ", id " + autoFillId + " and flags " + flags);
+ }
+
+ if ((flags & FLAG_UPDATE_UI_HIDE) != 0) {
+ // TODO(b/33197203): handle it?
+ if (DEBUG) Slog.d(TAG, "ignoring FLAG_UPDATE_UI_HIDE request for " + autoFillId);
+
+ return;
+ }
+
+ session.mCurrentAutoFillId = autoFillId;
+ session.mCurrentBounds = bounds;
+ mUi.showResponse(mUserId, session.mId, autoFillId, bounds, session.mCurrentResponse);
+ return;
+ }
+
+ final int sessionId = ++sSessionIdCounter;
+ if (DEBUG) {
+ Slog.d(TAG, "requestAutoFillLocked(): new session (id=" + sessionId + " for token "
+ + activityToken + " and autoFillId " + autoFillId);
+ }
+
+ final Session newSession = new Session(sessionId, activityToken, autoFillId, bounds);
+ mSessions.put(sessionId, newSession);
+
+ final String historyItem = "s=" + mComponentName + " u=" + mUserId + " f=" + flags
+ + " a=" + activityToken + " i=" + autoFillId + " b=" + bounds;
+ mRequestsHistory.log(historyItem);
/*
* TODO(b/33197203): apply security checks below:
@@ -308,16 +353,85 @@
*/
try {
// TODO(b/33197203): add MetricsLogger call
- if (!mAm.requestAutoFillData(mAssistReceiver, null, callbackId, activityToken, flags)) {
+ if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken, flags)) {
// TODO(b/33197203): might need a way to warn user (perhaps a new method on
// AutoFillService).
Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
}
} catch (RemoteException e) {
- // Should happen, it's a local call.
+ // Should not happen, it's a local call.
}
}
+ /**
+ * Called by UI to trigger a save request to the service.
+ */
+ void requestSaveLocked(int sessionId) {
+ // TODO(b/33197203): add MetricsLogger call
+ // TODO(b/33197203): use handler?
+ // TODO(b/33197203): show error on UI on Slog.w situations below???
+
+ if (mService == null) {
+ Slog.w(TAG, "requestSave(): service is null");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null) {
+ Slog.w(TAG, "requestSave(): no session with id " + sessionId);
+ return;
+ }
+ final IBinder activityToken = session.mActivityToken.get();
+ if (activityToken == null) {
+ Slog.w(TAG, "activity token for session " + sessionId + " already GCed");
+ return;
+ }
+
+ /*
+ * TODO(b/33197203): apply security checks below:
+ * - checks if disabled by secure settings / device policy
+ * - log operation using noteOp()
+ * - check flags
+ * - display disclosure if needed
+ */
+ try {
+ /* TODO(b/33197203): refactor save logic so it uses a cached AssistStructure, and get
+ the extras to be sent to the service based on the response / dataset in the session.
+ Something like:
+ final Bundle extras = (responseExtras == null && datasetExtras == null)
+ ? null : new Bundle();
+ if (responseExtras != null) {
+ if (DEBUG) Slog.d(TAG, "response extras on save notification: " +
+ bundleToString(responseExtras));
+ extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
+ }
+ if (datasetExtras != null) {
+ if (DEBUG) Slog.d(TAG, "dataset extras on save notification: " +
+ bundleToString(datasetExtras));
+ extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
+ }
+
+ */
+
+ if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken,
+ AUTO_FILL_FLAG_TYPE_SAVE)) {
+ Slog.w(TAG, "failed to save for " + activityToken);
+ }
+ } catch (RemoteException e) {
+ // Should not happen, it's a local call.
+ }
+ }
+
+ private Session getSessionByTokenLocked(IBinder activityToken) {
+ final int size = mSessions.size();
+ for (int i = 0; i < size; i++) {
+ final Session session = mSessions.valueAt(i);
+ if (activityToken.equals(session.mActivityToken.get())) {
+ return session;
+ }
+ }
+ return null;
+ }
+
void stopLocked() {
if (DEBUG) Slog.d(TAG, "stopLocked()");
@@ -348,39 +462,74 @@
/**
* Called by {@link AutoFillUI} to fill an activity after the user selected a dataset.
*/
- void autoFillApp(int callbackId, Dataset dataset) {
+ void autoFillApp(int sessionId, Dataset dataset) {
// TODO(b/33197203): add MetricsLogger call
if (dataset == null) {
- Slog.w(TAG, "autoFillApp(): no dataset for callback id " + callbackId);
+ Slog.w(TAG, "autoFillApp(): no dataset for callback id " + sessionId);
return;
}
- final ServerCallback serverCallback;
+
+ final Session session;
synchronized (mLock) {
- serverCallback = mServerCallbacks.get(callbackId);
- if (serverCallback == null) {
- Slog.w(TAG, "autoFillApp(): no server callback with id " + callbackId);
+ session = mSessions.get(sessionId);
+ if (session == null) {
+ Slog.w(TAG, "autoFillApp(): no session with id " + sessionId);
return;
}
- if (serverCallback.appCallback == null) {
- Slog.w(TAG, "autoFillApp(): no app callback for server callback " + callbackId);
+ if (session.mAppCallback == null) {
+ Slog.w(TAG, "autoFillApp(): no app callback for session " + sessionId);
return;
}
+
// TODO(b/33197203): use a handler?
- try {
- if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
- serverCallback.appCallback.autoFill(dataset);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error auto-filling activity: " + e);
- }
- removeServerCallbackLocked(callbackId);
+ session.autoFill(dataset);
}
}
- void removeServerCallbackLocked(int id) {
- if (DEBUG) Slog.d(TAG, "Removing " + id + " from server callbacks");
- mServerCallbacks.remove(id);
+ void removeSessionLocked(int id) {
+ if (DEBUG) Slog.d(TAG, "Removing session " + id);
+ mSessions.remove(id);
+
+ // TODO(b/33197203): notify mService so it can invalidate the FillCallback / SaveCallback?
+ }
+
+ /**
+ * Notifies the result of a {@link FillResponse} authentication request to the service.
+ *
+ * <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after user
+ * used the fingerprint sensors to authenticate.
+ */
+ void notifyResponseAuthenticationResult(Bundle extras, int flags) {
+ if (DEBUG) Slog.d(TAG, "notifyResponseAuthenticationResult(): flags=" + flags
+ + ", extras=" + bundleToString(extras));
+
+ synchronized (mLock) {
+ try {
+ mService.authenticateFillResponse(extras, flags);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error sending authentication result back to service: " + e);
+ }
+ }
+ }
+
+ /**
+ * Notifies the result of a {@link Dataset} authentication request to the service.
+ *
+ * <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after
+ * it gets the results from a fingerprint authentication.
+ */
+ void notifyDatasetAuthenticationResult(Bundle extras, int flags) {
+ if (DEBUG) Slog.d(TAG, "notifyDatasetAuthenticationResult(): flags=" + flags
+ + ", extras=" + bundleToString(extras));
+ synchronized (mLock) {
+ try {
+ mService.authenticateDataset(extras, flags);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error sending authentication result back to service: " + e);
+ }
+ }
}
void dumpLocked(String prefix, PrintWriter pw) {
@@ -398,27 +547,20 @@
pw.print(prefix); pw.print("mUserId="); pw.println(mUserId);
pw.print(prefix); pw.print("mUid="); pw.println(mUid);
- pw.print(prefix); pw.print("mComponent="); pw.println(mComponent.flattenToShortString());
+ pw.print(prefix); pw.print("mComponent="); pw.println(mComponentName);
+ pw.print(prefix); pw.print("mService: "); pw.println(mService);
pw.print(prefix); pw.print("mBound="); pw.println(mBound);
- pw.print(prefix); pw.print("mService="); pw.println(mService);
pw.print(prefix); pw.print("mEstimateTimeOfDeath=");
TimeUtils.formatDuration(mEstimateTimeOfDeath, SystemClock.uptimeMillis(), pw);
- pw.println();
+ pw.println();
+ pw.print(prefix); pw.print("mAuthToken: "); pw.println(mAuthToken);
if (DEBUG) {
// ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
- pw.print(prefix); pw.println("Service info:");
+ pw.print(prefix); pw.println("ServiceInfo:");
mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
}
- if (mRequestHistory.isEmpty()) {
- pw.print(prefix); pw.println("No history");
- } else {
- pw.print(prefix); pw.println("History:");
- for (int i = 0; i < mRequestHistory.size(); i++) {
- pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mRequestHistory.get(i));
- }
- }
if (mQueuedRequests.isEmpty()) {
pw.print(prefix); pw.println("No queued requests");
} else {
@@ -428,94 +570,391 @@
}
}
- pw.print(prefix); pw.print("sServerCallbackCounter="); pw.println(sServerCallbackCounter);
- final int size = mServerCallbacks.size();
+ pw.print(prefix); pw.print("sSessionIdCounter="); pw.println(sSessionIdCounter);
+ final int size = mSessions.size();
if (size == 0) {
- pw.print(prefix); pw.println("No server callbacks");
+ pw.print(prefix); pw.println("No sessions");
} else {
- pw.print(prefix); pw.print(size); pw.println(" server callbacks:");
+ pw.print(prefix); pw.print(size); pw.println(" sessions:");
for (int i = 0; i < size; i++) {
- pw.print(prefix2); pw.print(mServerCallbacks.keyAt(i));
- final ServerCallback callback = mServerCallbacks.valueAt(i);
- if (callback.appCallback == null) {
- pw.println("(no appCallback)");
- } else {
- pw.print(" (app callback: "); pw.print(callback.appCallback) ; pw.println(")");
- }
+ pw.print(prefix); pw.print("#"); pw.println(i + 1);
+ mSessions.valueAt(i).dumpLocked(prefix2, pw);
}
- pw.println();
}
}
@Override
public String toString() {
- return "[AutoFillManagerServiceImpl: userId=" + mUserId + ", uid=" + mUid
- + ", component=" + mComponent.flattenToShortString() + "]";
+ return "AutoFillManagerServiceImpl: [userId=" + mUserId + ", uid=" + mUid
+ + ", component=" + mComponentName + "]";
}
private static final class QueuedRequest {
final IBinder activityToken;
- final Bundle extras;
+ final AutoFillId autoFillId;
+ final Rect bounds;
final int flags;
- QueuedRequest(IBinder activityToken, Bundle extras, int flags) {
+ QueuedRequest(IBinder activityToken, AutoFillId autoFillId, Rect bounds, int flags) {
this.activityToken = activityToken;
- this.extras = extras;
+ this.autoFillId = autoFillId;
+ this.bounds = bounds;
this.flags = flags;
}
@Override
public String toString() {
- return "flags: " + flags + " token: " + activityToken;
+ if (!DEBUG) return super.toString();
+
+ return "QueuedRequest: [flags=" + flags + ", token=" + activityToken
+ + ", id=" + autoFillId + ", bounds=" + bounds;
}
}
/**
* A bridge between the {@link AutoFillService} implementation and the activity being
* auto-filled (represented through the {@link IAutoFillAppCallback}).
+ *
+ * <p>Although the auto-fill requests and callbacks are stateless from the service's point of
+ * view, we need to keep state in the framework side for cases such as authentication. For
+ * example, when service return a {@link FillResponse} that contains all the fields needed
+ * to fill the activity but it requires authentication first, that response need to be held
+ * until the user authenticates or it times out.
*/
- private final class ServerCallback extends IAutoFillServerCallback.Stub {
+ // TODO(b/33197203): make sure sessions are removed (and tested by CTS):
+ // - On all authentication scenarios.
+ // - When user does not interact back after a while.
+ // - When service is unbound.
+ private final class Session {
- private final int id;
- private final Bundle extras;
- private IAutoFillAppCallback appCallback;
+ private final int mId;
+ private final WeakReference<IBinder> mActivityToken;
- private ServerCallback(int id, Bundle extras) {
- this.id = id;
- this.extras = extras;
+ private IAutoFillAppCallback mAppCallback;
+
+ // Current view where the auto-fill bar is displayed
+ @GuardedBy("mLock")
+ private AutoFillId mCurrentAutoFillId;
+ @GuardedBy("mLock")
+ private Rect mCurrentBounds;
+ @GuardedBy("mLock")
+ private FillResponse mCurrentResponse;
+
+ private final IFingerprintService mFingerprintService;
+
+ @GuardedBy("mLock")
+ private FillResponse mResponseRequiringAuth;
+ @GuardedBy("mLock")
+ private Dataset mDatasetRequiringAuth;
+
+ // Used to auto-fill the activity directly when the FillCallback.onResponse() is called as
+ // the result of a successful user authentication on service's side.
+ @GuardedBy("mLock")
+ private boolean mAutoFillDirectly;
+
+ // TODO(b/33197203): use handler to handle results?
+ // TODO(b/33197203): handle all callback methods and/or cancelation?
+ private IFingerprintServiceReceiver mServiceReceiver =
+ new IFingerprintServiceReceiver.Stub() {
+
+ @Override
+ public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
+ if (DEBUG) Slog.d(TAG, "onEnrollResult()");
+ }
+
+ @Override
+ public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+ if (DEBUG) Slog.d(TAG, "onAcquired()");
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
+ if (DEBUG) Slog.d(TAG, "onAuthenticationSucceeded(): " + fp.getGroupId());
+
+ // First, check what was authenticated, a response or a dataset.
+ // Then, decide how to handle it:
+ // - If service provided data, handle them directly.
+ // - Otherwise, notify service.
+
+ mAutoFillDirectly = true;
+
+ if (mDatasetRequiringAuth != null) {
+ if (mDatasetRequiringAuth.isEmpty()) {
+ notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(),
+ FLAG_AUTHENTICATION_SUCCESS);
+ } else {
+ autoFillAppLocked(mDatasetRequiringAuth, true);
+ }
+ } else if (mResponseRequiringAuth != null) {
+ final List<Dataset> datasets = mResponseRequiringAuth.getDatasets();
+ if (datasets.isEmpty()) {
+ notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(),
+ FLAG_AUTHENTICATION_SUCCESS);
+ } else {
+ showResponseLocked(mResponseRequiringAuth, true);
+ }
+ } else {
+ Slog.w(TAG, "onAuthenticationSucceeded(): no response or dataset");
+ }
+
+ mUi.dismissFingerprintRequest(mUserId, true);
+ }
+
+ @Override
+ public void onAuthenticationFailed(long deviceId) {
+ if (DEBUG) Slog.d(TAG, "onAuthenticationFailed()");
+ // Do nothing - onError() will be called after a few failures...
+ }
+
+ @Override
+ public void onError(long deviceId, int error, int vendorCode) {
+ if (DEBUG) Slog.d(TAG, "onError()");
+
+ // Notify service so it can fallback to its own authentication
+ if (mDatasetRequiringAuth != null) {
+ notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(),
+ FLAG_AUTHENTICATION_ERROR);
+ } else if (mResponseRequiringAuth != null) {
+ notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(),
+ FLAG_AUTHENTICATION_ERROR);
+ } else {
+ Slog.w(TAG, "onError(): no response or dataset");
+ }
+
+ mUi.dismissFingerprintRequest(mUserId, false);
+ }
+
+ @Override
+ public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+ if (DEBUG) Slog.d(TAG, "onRemoved()");
+ }
+
+ @Override
+ public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) {
+ if (DEBUG) Slog.d(TAG, "onEnumerated()");
+ }
+ };
+
+ private IAutoFillServerCallback mServerCallback = new IAutoFillServerCallback.Stub() {
+ @Override
+ public void showResponse(FillResponse response) {
+ // TODO(b/33197203): add MetricsLogger call
+ if (response == null) {
+ if (DEBUG) Slog.d(TAG, "showResponse(): null response");
+
+ removeSelf();
+ return;
+ }
+
+ synchronized (mLock) {
+ showResponseLocked(response, response.isAuthRequired());
+ }
+ }
+
+ @Override
+ public void showError(CharSequence message) {
+ // TODO(b/33197203): add MetricsLogger call
+ if (DEBUG) Slog.d(TAG, "showError(): " + message);
+
+ mUi.showError(message);
+
+ removeSelf();
+ }
+
+ @Override
+ public void highlightSavedFields(AutoFillId[] ids) {
+ // TODO(b/33197203): add MetricsLogger call
+ if (DEBUG) Slog.d(TAG, "highlightSavedFields(): " + Arrays.toString(ids));
+
+ mUi.highlightSavedFields(ids);
+
+ removeSelf();
+ }
+
+ @Override
+ public void unlockFillResponse(int flags) {
+ // TODO(b/33197203): add proper MetricsLogger calls?
+ if (DEBUG) Log.d(TAG, "unlockUser(): flags=" + flags);
+
+ synchronized (mLock) {
+ if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) {
+ if (mResponseRequiringAuth == null) {
+ Log.wtf(TAG, "unlockUser(): no mResponseRequiringAuth on flags "
+ + flags);
+ removeSelf();
+ return;
+ }
+ final List<Dataset> datasets = mResponseRequiringAuth.getDatasets();
+ if (datasets.isEmpty()) {
+ Log.w(TAG, "unlockUser(): no dataset on previous response: "
+ + mResponseRequiringAuth);
+ removeSelf();
+ return;
+ }
+ mAutoFillDirectly = true;
+ showResponseLocked(mResponseRequiringAuth, false);
+ }
+ // TODO(b/33197203): show UI error on authentication failure?
+ // Or let service handle it?
+ }
+ }
+
+ @Override
+ public void unlockDataset(Dataset dataset, int flags) {
+ // TODO(b/33197203): add proper MetricsLogger calls?
+ if (DEBUG) Log.d(TAG, "unlockDataset(): dataset=" + dataset + ", flags=" + flags);
+
+ if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) {
+ autoFillAppLocked(dataset != null ? dataset : mDatasetRequiringAuth, true);
+ return;
+ }
+ removeSelf();
+ }
+ };
+
+ private Session(int id, IBinder activityToken, AutoFillId autoFillId, Rect bounds) {
+ this.mId = id;
+ this.mActivityToken = new WeakReference<>(activityToken);
+ this.mCurrentAutoFillId = autoFillId;
+ this.mCurrentBounds = bounds;
+ this.mFingerprintService = IFingerprintService.Stub
+ .asInterface(ServiceManager.getService("fingerprint"));
}
- @Override
- public void showResponse(FillResponse response) {
- // TODO(b/33197203): add MetricsLogger call
- if (DEBUG) Slog.d(TAG, "showResponse(): " + response);
-
- mUi.showOptions(mUserId, id, response);
+ void setAppCallback(IBinder appBinder) {
+ try {
+ appBinder.linkToDeath(() -> {
+ if (DEBUG) Slog.d(TAG, "app callback died");
+ // TODO(b/33197203): more cleanup here?
+ mAppCallback = null;
+ }, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "linkToDeath() failed: " + e);
+ }
+ mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
}
- @Override
- public void showError(String message) {
- // TODO(b/33197203): add MetricsLogger call
- if (DEBUG) Slog.d(TAG, "showError(): " + message);
+ private void showResponseLocked(FillResponse response, boolean authRequired) {
+ if (DEBUG) Slog.d(TAG, "showResponse(directly=" + mAutoFillDirectly
+ + ", authRequired=" + authRequired +"):" + response);
- mUi.showError(message);
+ if (mAutoFillDirectly && response != null) {
+ final List<Dataset> datasets = response.getDatasets();
+ if (datasets.size() == 1) {
+ // User authenticated and provider returned just 1 dataset - auto-fill it now!
+ final Dataset dataset = datasets.get(0);
+ if (DEBUG) Slog.d(TAG, "auto-filling directly from auth: " + dataset);
- removeSelf();
+ autoFillAppLocked(dataset, true);
+ return;
+ }
+ }
+
+ if (!authRequired) {
+ // TODO(b/33197203): add MetricsLogger call
+ mCurrentResponse = response;
+ mUi.showResponse(mUserId, mId, mCurrentAutoFillId, mCurrentBounds, mCurrentResponse);
+ return;
+ }
+
+ // Handles response that requires authentication.
+ // TODO(b/33197203): add MetricsLogger call, including if fingerprint requested
+
+ mResponseRequiringAuth = response;
+ final boolean requiresFingerprint = response.hasCryptoObject();
+ if (requiresFingerprint) {
+ // TODO(b/33197203): check if fingerprint is available first and call error callback
+ // with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
+ // Start scanning for the fingerprint.
+ scanFingerprint(response.getCryptoObjectOpId());
+ }
+ // Displays the message asking the user to tap (or fingerprint) for AutoFill.
+ mUi.showFillResponseAuthenticationRequest(mUserId, mId, requiresFingerprint,
+ response.getExtras(), response.getFlags());
}
- @Override
- public void highlightSavedFields(AutoFillId[] ids) {
+ void autoFill(Dataset dataset) {
+ synchronized (mLock) {
+ // Autofill it directly...
+ if (!dataset.isAuthRequired()) {
+ autoFillAppLocked(dataset, true);
+ return;
+ }
+
+ // ...or handle authentication.
+
+ mDatasetRequiringAuth = dataset;
+ final boolean requiresFingerprint = dataset.hasCryptoObject();
+ if (requiresFingerprint) {
+ // TODO(b/33197203): check if fingerprint is available first and call error
+ // callback with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
+ // Start scanning for the fingerprint.
+ scanFingerprint(dataset.getCryptoObjectOpId());
+ // Displays the message asking the user to tap (or fingerprint) for AutoFill.
+ mUi.showDatasetFingerprintAuthenticationRequest(dataset);
+ } else {
+ try {
+ mService.authenticateDataset(dataset.getExtras(),
+ FLAG_AUTHENTICATION_REQUESTED);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error authenticating dataset: " + e);
+ }
+ }
+ }
+ }
+
+ void dumpLocked(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mId: "); pw.println(mId);
+ pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken.get());
+ pw.print(prefix); pw.print("mCurrentAutoFillId: "); pw.println(mCurrentAutoFillId);
+ pw.print(prefix); pw.print("mCurrentBounds: "); pw.println(mCurrentBounds);
+ pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
+ pw.print(prefix);
+ pw.print("mResponseRequiringAuth: "); pw.println(mResponseRequiringAuth);
+ pw.print(prefix);
+ pw.print("mDatasetRequiringAuth: "); pw.println(mDatasetRequiringAuth);
+ pw.print(prefix); pw.print("mAutoFillDirectly: "); pw.println(mAutoFillDirectly);
+ }
+
+ private void autoFillAppLocked(Dataset dataset, boolean removeSelf) {
+ try {
+ if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
+ mAppCallback.autoFill(dataset);
+
+ // TODO(b/33197203): temporarily hack: show the save notification after autofilled,
+ // since save is not automatically detected yet.
+ mUi.showSaveNotification(mUserId, mId); removeSelf = false;
+
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error auto-filling activity: " + e);
+ }
+ if (removeSelf) {
+ removeSelf();
+ }
+ }
+
+ private void scanFingerprint(long opId) {
// TODO(b/33197203): add MetricsLogger call
- if (DEBUG) Slog.d(TAG, "showSaved(): " + Arrays.toString(ids));
+ if (DEBUG) Slog.d(TAG, "Starting fingerprint scan for op id: " + opId);
- mUi.highlightSavedFields(ids);
-
- removeSelf();
+ // TODO(b/33197203): since we're clearing the AutoFillService's identity, make sure
+ // this method is only called at the proper times, otherwise a malicious provider could
+ // keep the callback refence to bypass the check
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // TODO(b/33197203): set a timeout?
+ mFingerprintService.authenticate(mAuthToken, opId, mUserId, mServiceReceiver, 0,
+ null);
+ } catch (RemoteException e) {
+ // Local call, shouldn't happen.
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private void removeSelf() {
synchronized (mLock) {
- removeServerCallbackLocked(id);
+ removeSessionLocked(mId);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
index 26f2451..4998e3f 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -74,7 +74,7 @@
private int requestAutoFill(int flags) throws RemoteException {
final int userId = getUserIdFromArgs();
- mService.requestAutoFill(null, userId, null, flags);
+ mService.requestAutoFillForUser(userId, flags);
return 0;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index 08e81d3..ba0601c 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -15,47 +15,37 @@
*/
package com.android.server.autofill;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
-import static com.android.server.autofill.AutoFillManagerService.DEBUG;
+import static com.android.server.autofill.Helper.DEBUG;
import android.app.Activity;
-import android.app.AppGlobals;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
+import android.graphics.Rect;
+import android.graphics.PixelFormat;
import android.os.Binder;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.service.autofill.AutoFillService;
-import android.text.TextUtils;
-import android.util.Log;
import android.util.Slog;
-import android.view.autofill.Dataset;
import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
import android.widget.Toast;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.UiThread;
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
-import java.util.Set;
/**
* Handles all auto-fill related UI tasks.
@@ -67,17 +57,26 @@
private final Context mContext;
+ private final WindowManager mWm;
+
+ /**
+ * Custom snackbar UI used for saving autofill or other informational messages.
+ */
+ private View mSnackbar;
+
AutoFillUI(Context context, AutoFillManagerService service, Object lock) {
mContext = context;
- mResolver = context.getContentResolver();
+ mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mService = service;
mLock = lock;
+
+ setNotificationListener();
}
/**
* Displays an error message to the user.
*/
- void showError(String message) {
+ void showError(CharSequence message) {
// TODO(b/33197203): proper implementation
UiThread.getHandler().runWithScissors(() -> {
Toast.makeText(mContext, "AutoFill error: " + message, Toast.LENGTH_LONG).show();
@@ -97,12 +96,150 @@
/**
* Shows the options from a {@link FillResponse} so the user can pick up the proper
- * {@link Dataset} (when the response has one).
+ * {@link Dataset} (when the response has one) for a given view (identified by
+ * {@code autoFillId}).
*/
- void showOptions(int userId, int callbackId, FillResponse response) {
+ void showResponse(int userId, int sessionId, AutoFillId autoFillId, Rect bounds,
+ FillResponse response) {
+ if (DEBUG) Slog.d(TAG, "showResponse: id=" + autoFillId + ", bounds=" + bounds);
// TODO(b/33197203): proper implementation
- // TODO(b/33197203): make sure if removes the callback from cache
- showOptionsNotification(userId, callbackId, response);
+ // TODO(b/33197203): make sure if removes the session from cache
+ showOptionsNotification(userId, sessionId, autoFillId, response);
+ }
+
+ /**
+ * Shows an UI affordance indicating that user action is required before a {@link FillResponse}
+ * can be used.
+ *
+ * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint or tap to
+ * autofill" or "Tap to autofill", depending on the value of {@code usesFingerprint}.
+ */
+ void showFillResponseAuthenticationRequest(int userId, int sessionId, boolean usesFingerprint,
+ Bundle extras, int flags) {
+ // TODO(b/33197203): proper implementation
+ showAuthNotification(userId, sessionId, usesFingerprint, extras, flags);
+ }
+
+ /**
+ * Shows an UI affordance asking indicating that user action is required before a
+ * {@link Dataset} can be used.
+ *
+ * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint to
+ * autofill".
+ */
+ void showDatasetFingerprintAuthenticationRequest(Dataset dataset) {
+ if (DEBUG) Slog.d(TAG, "showDatasetAuthenticationRequest(): dataset=" + dataset);
+
+ // TODO(b/33197203): proper implementation (either pop up a fingerprint dialog or replace
+ // the auto-fill bar with a new message.
+ UiThread.getHandler().runWithScissors(() -> {
+ Toast.makeText(mContext, "AutoFill: press fingerprint to unlock " + dataset.getName(),
+ Toast.LENGTH_LONG).show();
+ }, 0);
+ }
+
+ /**
+ * Shows the UI asking the user to save for auto-fill.
+ */
+ void showSaveUI(int userId, int sessionId) {
+ showSnackbar(new SavePrompt(mContext, new SavePrompt.OnSaveListener() {
+ @Override
+ public void onSaveClick() {
+ hideSnackbar();
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+ service.requestSaveLocked(sessionId);
+ }
+ }
+ @Override
+ public void onCancelClick() {
+ hideSnackbar();
+ }
+ }));
+ }
+
+ /**
+ * Called by service after the user user the fingerprint sensors to authenticate.
+ */
+ void dismissFingerprintRequest(int userId, boolean success) {
+ if (DEBUG) Slog.d(TAG, "dismissFingerprintRequest(): ok=" + success);
+
+ dismissAuthNotification(userId);
+
+ if (!success) {
+ // TODO(b/33197203): proper implementation (snack bar / i18n string)
+ UiThread.getHandler().runWithScissors(() -> {
+ Toast.makeText(mContext, "AutoFill: fingerprint failed", Toast.LENGTH_LONG).show();
+ }, 0);
+ }
+ }
+
+ private AutoFillManagerServiceImpl getServiceLocked(int userId) {
+ final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "no auto-fill service for user " + userId);
+ }
+ return service;
+ }
+
+ private void onSaveRequested(int userId, int sessionId) {
+ // TODO(b/33197203): displays the snack bar, until save notification is refactored
+ showSaveUI(userId, sessionId);
+ }
+
+ private void onDatasetPicked(int userId, Dataset dataset, int sessionId) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+ if (service == null) return;
+
+ service.autoFillApp(sessionId, dataset);
+ }
+ }
+
+ private void onSessionDone(int userId, int sessionId) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+ if (service == null) return;
+
+ service.removeSessionLocked(sessionId);
+ }
+ }
+
+ private void onResponseAuthenticationRequested(int userId, Bundle extras, int flags) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+ if (service == null) return;
+
+ service.notifyResponseAuthenticationResult(extras, flags);
+ }
+ }
+
+ //similar to a snackbar, but can be a bit custom since it is more than just text. This will
+ //allow two buttons for saving or not saving the autofill for instance as well.
+ private void showSnackbar(View snackBar) {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.FILL_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, // TODO(b/33197203) use TYPE_AUTO_FILL
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN,
+ PixelFormat.TRANSLUCENT);
+
+ params.gravity = Gravity.BOTTOM | Gravity.LEFT;
+
+ UiThread.getHandler().runWithScissors(() -> {
+ mSnackbar = snackBar;
+ mWm.addView(mSnackbar, params);
+ }, 0);
+ }
+
+ private void hideSnackbar() {
+ UiThread.getHandler().runWithScissors(() -> {
+ if (mSnackbar != null) {
+ mWm.removeView(mSnackbar);
+ mSnackbar = null;
+ }
+ }, 0);
}
/////////////////////////////////////////////////////////////////////////////////
@@ -117,144 +254,78 @@
// Extras used in the notification intents
private static final String EXTRA_USER_ID = "user_id";
private static final String EXTRA_NOTIFICATION_TYPE = "notification_type";
- private static final String EXTRA_CALLBACK_ID = "callback_id";
+ private static final String EXTRA_SESSION_ID = "session_id";
private static final String EXTRA_FILL_RESPONSE = "fill_response";
private static final String EXTRA_DATASET = "dataset";
+ private static final String EXTRA_AUTH_REQUIRED_EXTRAS = "auth_required_extras";
+ private static final String EXTRA_FLAGS = "flags";
- private static final String TYPE_EMULATE = "emulate";
private static final String TYPE_OPTIONS = "options";
- private static final String TYPE_DELETE_CALLBACK = "delete_callback";
+ private static final String TYPE_FINISH_SESSION = "finish_session";
private static final String TYPE_PICK_DATASET = "pick_dataset";
private static final String TYPE_SAVE = "save";
+ private static final String TYPE_AUTH_RESPONSE = "auth_response";
- static final int MSG_SHOW_ALL_NOTIFICATIONS = 42;
- static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000;
-
+ @GuardedBy("mServiceLock")
private BroadcastReceiver mNotificationReceiver;
- private final ContentResolver mResolver;
+ @GuardedBy("mServiceLock")
private final AutoFillManagerService mService;
private final Object mLock;
// Hack used to generate unique pending intents
static int sResultCode = 0;
+ private void setNotificationListener() {
+ synchronized (mLock) {
+ if (mNotificationReceiver == null) {
+ mNotificationReceiver = new NotificationReceiver();
+ mContext.registerReceiver(mNotificationReceiver,
+ new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
+ }
+ }
+ }
+
final class NotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
-
- final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
- if (service == null) {
- Slog.w(TAG, "no auto-fill service for user " + userId);
- return;
- }
-
- final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1);
+ final int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
if (type == null) {
Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
return;
}
- final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
- final Bundle datasetArgs = dataset == null ? null : dataset.getExtras();
- final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras();
-
- // Bundle sent on AutoFillService methods - only set if service provided a bundle
- final Bundle extras = (datasetArgs == null && fillDataArgs == null)
- ? null : new Bundle();
+ final int flags = intent.getIntExtra(EXTRA_FLAGS, 0);
if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
- + ", callbackId=" + callbackId);
+ + ", sessionId=" + sessionId);
synchronized (mLock) {
switch (type) {
- case TYPE_EMULATE:
- service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_FILL);
- break;
case TYPE_SAVE:
- if (datasetArgs != null) {
- if (DEBUG) Log.d(TAG, "filldata args on save notificataion: " +
- bundleToString(fillDataArgs));
- extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, fillDataArgs);
- }
- if (dataset != null) {
- if (DEBUG) Log.d(TAG, "dataset args on save notificataion: " +
- bundleToString(datasetArgs));
- extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetArgs);
- }
- service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_SAVE);
+ onSaveRequested(userId, sessionId);
break;
- case TYPE_DELETE_CALLBACK:
- service.removeServerCallbackLocked(callbackId);
+ case TYPE_FINISH_SESSION:
+ onSessionDone(userId, sessionId);
break;
case TYPE_PICK_DATASET:
- service.autoFillApp(callbackId, dataset);
+ onDatasetPicked(userId, dataset, sessionId);
+
// Must cancel notification because it might be comming from action
- if (DEBUG) Log.d(TAG, "Cancelling notification");
+ if (DEBUG) Slog.d(TAG, "Cancelling notification");
NotificationManager.from(mContext).cancel(TYPE_OPTIONS, userId);
- if (datasetArgs != null) {
- if (DEBUG) Log.d(TAG, "adding dataset's extra_data on save intent: "
- + bundleToString(datasetArgs));
- extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetArgs);
- }
-
- // Also show notification with option to save the data
- showSaveNotification(userId, fillData, dataset);
+ break;
+ case TYPE_AUTH_RESPONSE:
+ onResponseAuthenticationRequested(userId,
+ intent.getBundleExtra(EXTRA_AUTH_REQUIRED_EXTRAS), flags);
break;
default: {
Slog.w(TAG, "Unknown notification type: " + type);
}
}
}
- }
- }
-
- private ComponentName getProviderForUser(int userId) {
- ComponentName serviceComponent = null;
- ServiceInfo serviceInfo = null;
- final String componentName = Settings.Secure.getStringForUser(
- mResolver, Settings.Secure.AUTO_FILL_SERVICE, userId);
- if (!TextUtils.isEmpty(componentName)) {
- try {
- serviceComponent = ComponentName.unflattenFromString(componentName);
- serviceInfo =
- AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, userId);
- } catch (RuntimeException | RemoteException e) {
- Slog.wtf(TAG, "Bad auto-fill service name " + componentName, e);
- return null;
- }
- }
-
- if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
- + serviceComponent + ", info: " + serviceInfo);
- if (serviceInfo == null) {
- Slog.w(TAG, "no service info for " + serviceComponent);
- return null;
- }
- return serviceComponent;
- }
-
- void showAllNotifications() {
- final UserManager userManager =
- (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-
- final List<UserInfo> allUsers = userManager.getUsers(true);
-
- for (UserInfo user : allUsers) {
- final ComponentName serviceComponent = getProviderForUser(user.id);
- if (serviceComponent != null) {
- showMainNotification(serviceComponent, user.id);
- }
- }
- }
-
- void updateNotification(int userId) {
- final ComponentName serviceComponent = getProviderForUser(userId);
- if (serviceComponent == null) {
- cancelMainNotification(userId);
- } else {
- showMainNotification(serviceComponent, userId);
+ collapseStatusBar();
}
}
@@ -265,125 +336,46 @@
return intent;
}
- private PendingIntent newPickDatasetPI(int userId, int callbackId, FillResponse response,
+ private PendingIntent newPickDatasetPI(int userId, int sessionId, FillResponse response,
Dataset dataset) {
final int resultCode = ++ sResultCode;
- if (DEBUG) Log.d(TAG, "newPickDatasetPI: userId=" + userId + ", callback=" + callbackId
+ if (DEBUG) Slog.d(TAG, "newPickDatasetPI: userId=" + userId + ", sessionId=" + sessionId
+ ", resultCode=" + resultCode);
final Intent intent = newNotificationIntent(userId, TYPE_PICK_DATASET);
- intent.putExtra(EXTRA_CALLBACK_ID, callbackId);
+ intent.putExtra(EXTRA_SESSION_ID, sessionId);
intent.putExtra(EXTRA_FILL_RESPONSE, response);
intent.putExtra(EXTRA_DATASET, dataset);
return PendingIntent.getBroadcast(mContext, resultCode, intent,
PendingIntent.FLAG_ONE_SHOT);
}
- private static String bundleToString(Bundle bundle) {
- if (bundle == null) {
- return "null";
- }
- final Set<String> keySet = bundle.keySet();
- final StringBuilder builder = new StringBuilder("[Bundle with ").append(keySet.size())
- .append(" keys:");
- for (String key : keySet) {
- final Object value = bundle.get(key);
- builder.append(' ').append(key).append('=');
- builder.append((value instanceof Object[])
- ? Arrays.toString((Objects[]) value) : value);
- }
- return builder.append(']').toString();
- }
-
- /**
- * Shows a permanent notification that triggers the auto-fill workflow for the given user.
- *
- * <p>It emulates calling the auto-fill service when the IME is shown.
- */
- private void showMainNotification(ComponentName serviceComponent, int userId) {
- if (DEBUG) Log.d(TAG, "showNotification() for " + userId + ": " + serviceComponent);
-
- synchronized (mLock) {
- if (mNotificationReceiver == null) {
- mNotificationReceiver = new NotificationReceiver();
- mContext.registerReceiver(mNotificationReceiver,
- new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
- }
- }
-
- final Intent fillIntent = newNotificationIntent(userId, TYPE_EMULATE);
- final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext,
- -1, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- final String packageName = serviceComponent.getPackageName();
- String providerName = null;
- final PackageManager pm = mContext.getPackageManager();
- try {
- final ApplicationInfo info = pm.getApplicationInfoAsUser(packageName, 0, userId);
- if (info != null) {
- providerName = pm.getApplicationLabel(info).toString();
- }
- } catch (Exception e) {
- providerName = packageName;
- }
- final String title = "AutoFill IME Emulation";
- final String subTitle = "Tap notification to start auto-fill workflow (by '" + providerName
- + "' on top activity on user " + userId + ".\n"
- + "Once provider replies, a new notification will show your options.";
-
- final Notification notification = new Notification.Builder(mContext)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .setOngoing(true)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setLocalOnly(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setContentTitle(title)
- .setStyle(new Notification.BigTextStyle().bigText(subTitle))
- .setContentIntent(fillPendingIntent)
- .build();
- NotificationManager.from(mContext).notify(TYPE_EMULATE, userId, notification);
- }
-
- /**
- * Cancels the permament notification created by
- * {@link #showMainNotification(ComponentName, int)}.
- */
- private void cancelMainNotification(int userId) {
- if (DEBUG) Log.d(TAG, "cancelNotificationLocked(): " + userId);
- NotificationManager.from(mContext).cancel(TYPE_EMULATE, userId);
- }
-
/**
* Shows a notification with the results of an auto-fill request, using notications actions
* to emulate the auto-fill bar buttons displaying the dataset names.
*/
- private void showOptionsNotification(int userId, int callbackId, FillResponse response) {
+ private void showOptionsNotification(int userId, int callbackId, AutoFillId autoFillId,
+ FillResponse response) {
final long token = Binder.clearCallingIdentity();
try {
- showOptionsNotificationAsSystem(userId, callbackId, response);
+ showOptionsNotificationAsSystem(userId, callbackId, autoFillId, response);
} finally {
Binder.restoreCallingIdentity(token);
}
}
- private void showOptionsNotificationAsSystem(int userId, int callbackId,
- FillResponse response) {
+ private void showOptionsNotificationAsSystem(int userId, int sessionId,
+ AutoFillId autoFillId, FillResponse response) {
// Make sure server callback is removed from cache if user cancels the notification.
- final Intent deleteIntent = newNotificationIntent(userId, TYPE_DELETE_CALLBACK);
- deleteIntent.putExtra(EXTRA_CALLBACK_ID, callbackId);
+ final Intent deleteIntent = newNotificationIntent(userId, TYPE_FINISH_SESSION)
+ .putExtra(EXTRA_SESSION_ID, sessionId);
final PendingIntent deletePendingIntent = PendingIntent.getBroadcast(mContext,
++sResultCode, deleteIntent, PendingIntent.FLAG_ONE_SHOT);
final String title = "AutoFill Options";
- final Notification.Builder notification = new Notification.Builder(mContext)
- .setCategory(Notification.CATEGORY_SYSTEM)
+ final Notification.Builder notification = newNotificationBuilder()
.setOngoing(false)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setLocalOnly(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
.setDeleteIntent(deletePendingIntent)
.setContentTitle(title);
@@ -400,13 +392,13 @@
}
boolean showSave = false;
if (datasets == null ) {
- subTitle = "No options to auto-fill this activity.";
+ subTitle = "No options to auto-fill " + autoFillId;
} else if (datasets.isEmpty()) {
if (savableIds.length == 0) {
- subTitle = "No options to auto-fill this activity.";
+ subTitle = "No options to auto-fill " + autoFillId;
} else {
- subTitle = "No options to auto-fill this activity, but provider can save ids:\n"
- + Arrays.toString(savableIds);
+ subTitle = "No options to auto-fill " + autoFillId
+ + ", but provider can save ids:\n" + Arrays.toString(savableIds);
showSave = true;
}
} else {
@@ -417,11 +409,20 @@
} else {
autoCancel = false;
final int size = datasets.size();
- subTitle = "There are " + size + " option(s).\n"
- + "Use the notification action(s) to select the proper one.";
+ subTitle = "There are " + size + " option(s) to fill " + autoFillId + ".\n"
+ + "Use the notification action(s) to select the proper one."
+ + "Actions with (F) require fingerprint unlock, and with (P) require"
+ + "provider authentication to unlock";
for (Dataset dataset : datasets) {
- final CharSequence name = dataset.getName();
- final PendingIntent pi = newPickDatasetPI(userId, callbackId, response, dataset);
+ final StringBuilder name = new StringBuilder(dataset.getName());
+ if (dataset.isAuthRequired()) {
+ if (dataset.hasCryptoObject()) {
+ name.append("(F)");
+ } else {
+ name.append("(P)");
+ }
+ }
+ final PendingIntent pi = newPickDatasetPI(userId, sessionId, response, dataset);
notification.addAction(new Action.Builder(null, name, pi).build());
}
}
@@ -433,31 +434,32 @@
NotificationManager.from(mContext).notify(TYPE_OPTIONS, userId, notification.build());
if (showSave) {
- showSaveNotification(userId, response, null);
+ showSaveNotification(userId, sessionId);
}
}
- private void showSaveNotification(int userId, FillResponse response, Dataset dataset) {
- final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE);
- saveIntent.putExtra(EXTRA_FILL_RESPONSE, response);
- if (dataset != null) {
- saveIntent.putExtra(EXTRA_DATASET, dataset);
+ void showSaveNotification(int userId, int sessionId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ showSaveNotificationAsSystem(userId, sessionId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
+ }
+
+ private void showSaveNotificationAsSystem(int userId, int sessionId) {
+ final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE)
+ .putExtra(EXTRA_SESSION_ID, sessionId);
+
final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext,
++sResultCode, saveIntent, PendingIntent.FLAG_ONE_SHOT);
- final String title = "AutoFill Save";
- final String subTitle = "Tap notification to ask provider to save fields: \n"
- + Arrays.toString(response.getSavableIds());
+ final String title = "AutoFill Save Emulation";
+ final String subTitle = "Tap notification to launch the save snackbar.";
- final Notification notification = new Notification.Builder(mContext)
- .setCategory(Notification.CATEGORY_SYSTEM)
+ final Notification notification = newNotificationBuilder()
.setAutoCancel(true)
.setOngoing(false)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setLocalOnly(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)
.setContentIntent(savePendingIntent)
.setStyle(new Notification.BigTextStyle().bigText(subTitle))
@@ -465,6 +467,68 @@
NotificationManager.from(mContext).notify(TYPE_SAVE, userId, notification);
}
+ private void showAuthNotification(int userId, int sessionId, boolean usesFingerprint,
+ Bundle extras, int flags) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ showAuthNotificationAsSystem(userId, sessionId, usesFingerprint, extras, flags);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void showAuthNotificationAsSystem(int userId, int sessionId,
+ boolean usesFingerprint, Bundle extras, int flags) {
+ final String title = "AutoFill Authentication";
+ final StringBuilder subTitle = new StringBuilder("Provider require user authentication.\n");
+
+ final Intent authIntent = newNotificationIntent(userId, TYPE_AUTH_RESPONSE)
+ .putExtra(EXTRA_SESSION_ID, sessionId);
+ if (extras != null) {
+ authIntent.putExtra(EXTRA_AUTH_REQUIRED_EXTRAS, extras);
+ }
+ if (flags != 0) {
+ authIntent.putExtra(EXTRA_FLAGS, flags);
+ }
+ final PendingIntent authPendingIntent = PendingIntent.getBroadcast(mContext, ++sResultCode,
+ authIntent, PendingIntent.FLAG_ONE_SHOT);
+
+ if (usesFingerprint) {
+ subTitle.append("But kindly accepts your fingerprint instead"
+ + "\n(tap fingerprint sensor to trigger it)");
+
+ } else {
+ subTitle.append("Tap notification to launch its authentication UI.");
+ }
+
+ final Notification.Builder notification = newNotificationBuilder()
+ .setAutoCancel(true)
+ .setOngoing(false)
+ .setContentTitle(title)
+ .setStyle(new Notification.BigTextStyle().bigText(subTitle.toString()));
+ if (authPendingIntent != null) {
+ notification.setContentIntent(authPendingIntent);
+ }
+ NotificationManager.from(mContext).notify(TYPE_AUTH_RESPONSE, userId, notification.build());
+ }
+
+ private void dismissAuthNotification(int userId) {
+ NotificationManager.from(mContext).cancel(TYPE_AUTH_RESPONSE, userId);
+ }
+
+ private Notification.Builder newNotificationBuilder() {
+ return new Notification.Builder(mContext)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setLocalOnly(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ }
+
+ private void collapseStatusBar() {
+ final StatusBarManager sbm = (StatusBarManager) mContext.getSystemService("statusbar");
+ sbm.collapsePanels();
+ }
/////////////////////////////////////////
// End of temporary notification code. //
/////////////////////////////////////////
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
new file mode 100644
index 0000000..79095a1
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.os.Bundle;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+
+final class Helper {
+
+ static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
+ static final String REDACTED = "[REDACTED]";
+
+ static void append(StringBuilder builder, Bundle bundle) {
+ if (bundle == null) {
+ builder.append("N/A");
+ } else if (!DEBUG) {
+ builder.append(REDACTED);
+ } else {
+ final Set<String> keySet = bundle.keySet();
+ builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
+ for (String key : keySet) {
+ final Object value = bundle.get(key);
+ builder.append(' ').append(key).append('=');
+ builder.append((value instanceof Object[])
+ ? Arrays.toString((Objects[]) value) : value);
+ }
+ builder.append(']');
+ }
+ }
+
+ static String bundleToString(Bundle bundle) {
+ final StringBuilder builder = new StringBuilder();
+ append(builder, bundle);
+ return builder.toString();
+ }
+
+ private Helper() {
+ throw new UnsupportedOperationException("contains static members only");
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/SavePrompt.java b/services/autofill/java/com/android/server/autofill/SavePrompt.java
new file mode 100644
index 0000000..022d646
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/SavePrompt.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.widget.RelativeLayout;
+import android.widget.RelativeLayout.LayoutParams;
+import android.widget.TextView;
+import android.view.View;
+
+/**
+ * Autofill Save Prompt
+ */
+final class SavePrompt extends RelativeLayout {
+ public interface OnSaveListener {
+ void onSaveClick();
+
+ void onCancelClick();
+ }
+
+ private final TextView mTextView;
+ private final TextView mNoButton;
+ private final TextView mYesButton;
+ private final OnSaveListener mListener;
+
+ SavePrompt(Context context, OnSaveListener listener) {
+ super(context);
+ mListener = listener;
+ setBackgroundColor(Color.YELLOW);
+
+ // TODO(b/33197203): move layout to XML
+ mTextView = new TextView(context);
+ final LayoutParams textParams = new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ textParams.setMargins(50, 25, 50, 0);
+ mTextView.setLayoutParams(textParams);
+ // TODO(b/33197203): use R.string once final wording is done
+ mTextView.setText("Save for autofill?");
+ mTextView.setId(View.generateViewId());
+
+ mNoButton = new TextView(context);
+ // TODO(b/33197203): use R.string once final wording is done
+ mNoButton.setText("No thanks");
+ mNoButton.setBackgroundColor(Color.TRANSPARENT);
+ mNoButton.setAllCaps(true);
+ mNoButton.setOnClickListener((v) -> {
+ mListener.onCancelClick();
+ });
+
+ mYesButton = new TextView(context);
+ // TODO(b/33197203): use R.string once final wording is done
+ mYesButton.setText("Save");
+ mYesButton.setBackgroundColor(Color.TRANSPARENT);
+ mYesButton.setId(View.generateViewId());
+ mYesButton.setAllCaps(true);
+ mYesButton.setOnClickListener((v) -> {
+ mListener.onSaveClick();
+ });
+
+ addView(mTextView);
+ addView(mNoButton);
+ addView(mYesButton);
+
+ final LayoutParams yesLayoutParams = new LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ yesLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+ yesLayoutParams.addRule(RelativeLayout.BELOW, mTextView.getId());
+ yesLayoutParams.setMargins(25, 25, 50, 25);
+ mYesButton.setLayoutParams(yesLayoutParams);
+ final LayoutParams noLayoutParams = new LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ noLayoutParams.addRule(RelativeLayout.LEFT_OF, mYesButton.getId());
+ noLayoutParams.addRule(RelativeLayout.BELOW, mTextView.getId());
+ noLayoutParams.setMargins(50, 25, 25, 25);
+ mNoButton.setLayoutParams(noLayoutParams);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 31ecb75..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
@@ -577,15 +566,18 @@
public ArrayList<String> fullPackages;
public IBackupObserver observer;
public boolean userInitiated;
+ public boolean nonIncrementalBackup;
BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
- ArrayList<String> fullPackages, IBackupObserver observer, boolean userInitiated) {
+ ArrayList<String> fullPackages, IBackupObserver observer, boolean userInitiated,
+ boolean nonIncrementalBackup) {
this.transport = transport;
this.dirName = dirName;
this.kvPackages = kvPackages;
this.fullPackages = fullPackages;
this.observer = observer;
this.userInitiated = userInitiated;
+ this.nonIncrementalBackup = nonIncrementalBackup;
}
}
@@ -753,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) {
@@ -794,7 +786,7 @@
try {
String dirName = transport.transportDirName();
PerformBackupTask pbt = new PerformBackupTask(transport, dirName,
- queue, oldJournal, null, null, false);
+ queue, oldJournal, null, null, false, false /* nonIncremental */);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
} catch (Exception e) {
@@ -1033,7 +1025,8 @@
mWakelock.acquire();
PerformBackupTask pbt = new PerformBackupTask(params.transport, params.dirName,
- kvQueue, null, params.observer, params.fullPackages, true);
+ kvQueue, null, params.observer, params.fullPackages, true,
+ params.nonIncrementalBackup);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
break;
@@ -1198,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
@@ -1747,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);
@@ -1825,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() {
@@ -1895,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
}
@@ -2011,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
@@ -2047,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) {
@@ -2348,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
@@ -2492,7 +2276,7 @@
return token;
}
- public int requestBackup(String[] packages, IBackupObserver observer) {
+ public int requestBackup(String[] packages, IBackupObserver observer, int flags) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
if (packages == null || packages.length < 1) {
@@ -2501,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;
@@ -2510,6 +2294,10 @@
ArrayList<String> fullBackupList = new ArrayList<>();
ArrayList<String> kvBackupList = new ArrayList<>();
for (String packageName : packages) {
+ if (PACKAGE_MANAGER_SENTINEL.equals(packageName)) {
+ kvBackupList.add(packageName);
+ continue;
+ }
try {
PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES);
@@ -2543,9 +2331,12 @@
sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
return BackupManager.ERROR_TRANSPORT_ABORTED;
}
+
+ boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
+
Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
- true);
+ true, nonIncrementalBackup);
mBackupHandler.sendMessage(msg);
return BackupManager.SUCCESS;
}
@@ -2673,17 +2464,20 @@
ParcelFileDescriptor mNewState;
int mStatus;
boolean mFinished;
- boolean mUserInitiated;
+ final boolean mUserInitiated;
+ final boolean mNonIncremental;
public PerformBackupTask(IBackupTransport transport, String dirName,
ArrayList<BackupRequest> queue, File journal, IBackupObserver observer,
- ArrayList<String> pendingFullBackups, boolean userInitiated) {
+ ArrayList<String> pendingFullBackups, boolean userInitiated,
+ boolean nonIncremental) {
mTransport = transport;
mOriginalQueue = queue;
mJournal = journal;
mObserver = observer;
mPendingFullBackups = pendingFullBackups;
mUserInitiated = userInitiated;
+ mNonIncremental = nonIncremental;
mStateDir = new File(mBaseStateDir, dirName);
@@ -2748,6 +2542,10 @@
// the way.
mQueue = (ArrayList<BackupRequest>) mOriginalQueue.clone();
+ // When the transport is forcing non-incremental key/value payloads, we send the
+ // metadata only if it explicitly asks for it.
+ boolean skipPm = mNonIncremental;
+
// The app metadata pseudopackage might also be represented in the
// backup queue if apps have been added/removed since the last time
// we performed a backup. Drop it from the working queue now that
@@ -2758,6 +2556,7 @@
Slog.i(TAG, "Metadata in queue; eliding");
}
mQueue.remove(i);
+ skipPm = false;
break;
}
}
@@ -2785,22 +2584,27 @@
}
}
- // The package manager doesn't have a proper <application> etc, but since
- // it's running here in the system process we can just set up its agent
- // directly and use a synthetic BackupRequest. We always run this pass
- // because it's cheap and this way we guarantee that we don't get out of
- // step even if we're selecting among various transports at run time.
- if (mStatus == BackupTransport.TRANSPORT_OK) {
- PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
- mPackageManager);
- mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL,
- IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
- addBackupTrace("PMBA invoke: " + mStatus);
+ if (skipPm) {
+ Slog.d(TAG, "Skipping backup of package metadata.");
+ executeNextState(BackupState.RUNNING_QUEUE);
+ } else {
+ // The package manager doesn't have a proper <application> etc, but since
+ // it's running here in the system process we can just set up its agent
+ // directly and use a synthetic BackupRequest. We always run this pass
+ // because it's cheap and this way we guarantee that we don't get out of
+ // step even if we're selecting among various transports at run time.
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
+ mPackageManager);
+ mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL,
+ IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
+ addBackupTrace("PMBA invoke: " + mStatus);
- // Because the PMBA is a local instance, it has already executed its
- // backup callback and returned. Blow away the lingering (spurious)
- // pending timeout message for it.
- mBackupHandler.removeMessages(MSG_TIMEOUT);
+ // Because the PMBA is a local instance, it has already executed its
+ // backup callback and returned. Blow away the lingering (spurious)
+ // pending timeout message for it.
+ mBackupHandler.removeMessages(MSG_TIMEOUT);
+ }
}
if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
@@ -3001,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 {
@@ -3066,6 +2870,7 @@
if (DEBUG) Slog.d(TAG, "invokeAgentForBackup on " + packageName);
addBackupTrace("invoking " + packageName);
+ File blankStateName = new File(mStateDir, "blank_state");
mSavedStateName = new File(mStateDir, packageName);
mBackupDataName = new File(mDataDir, packageName + ".data");
mNewStateName = new File(mStateDir, packageName + ".new");
@@ -3088,9 +2893,10 @@
}
// In a full backup, we pass a null ParcelFileDescriptor as
- // the saved-state "file". This is by definition an incremental,
- // so we build a saved state file to pass.
- mSavedState = ParcelFileDescriptor.open(mSavedStateName,
+ // the saved-state "file". For key/value backups we pass the old state if
+ // an incremental backup is required, and a blank state otherwise.
+ mSavedState = ParcelFileDescriptor.open(
+ mNonIncremental ? blankStateName : mSavedStateName,
ParcelFileDescriptor.MODE_READ_ONLY |
ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary
@@ -3120,6 +2926,10 @@
e.toString());
agentErrorCleanup();
return BackupTransport.AGENT_ERROR;
+ } finally {
+ if (mNonIncremental) {
+ blankStateName.delete();
+ }
}
// At this point the agent is off and running. The next thing to happen will
@@ -4473,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;
@@ -5089,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");
}
@@ -9085,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;
@@ -9282,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,
@@ -9420,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) {
@@ -9688,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);
@@ -9744,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.
@@ -9781,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.
@@ -9804,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());
}
}
@@ -9831,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());
}
}
@@ -9853,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());
}
}
@@ -9877,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());
}
}
@@ -9949,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;
@@ -10003,7 +9831,7 @@
boolean needPermission = true;
if (transport == null) {
- transport = mCurrentTransport;
+ transport = mTransportManager.getCurrentTransportName();
if (packageName != null) {
PackageInfo app = null;
@@ -10097,7 +9925,7 @@
appIsStopped(packageInfo.applicationInfo)) {
return false;
}
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport != null) {
try {
return transport.isAppEligibleForBackup(packageInfo,
@@ -10126,7 +9954,7 @@
ActiveRestoreSession(String packageName, String transport) {
mPackageName = packageName;
- mRestoreTransport = getTransport(transport);
+ mRestoreTransport = mTransportManager.getTransportBinder(transport);
}
public void markTimedOut() {
@@ -10485,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());
}
@@ -10494,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 312b878..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;
@@ -338,9 +355,10 @@
}
@Override
- public int requestBackup(String[] packages, IBackupObserver observer) throws RemoteException {
+ public int requestBackup(String[] packages, IBackupObserver observer, int flags)
+ throws RemoteException {
BackupManagerService svc = mService;
- return (svc != null) ? svc.requestBackup(packages, observer) : null;
+ return (svc != null) ? svc.requestBackup(packages, observer, flags) : null;
}
@Override
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..b1560e6 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -275,7 +275,7 @@
} catch (IllegalArgumentException e) {
// Failed to parse the settings string, log this and move on
// with defaults.
- Slog.e(TAG, "Bad device idle settings", e);
+ Slog.e(TAG, "Bad alarm manager settings", e);
}
MIN_FUTURITY = mParser.getLong(KEY_MIN_FUTURITY, DEFAULT_MIN_FUTURITY);
@@ -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/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 570843e..dc0e3e1 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -41,6 +41,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.media.AudioAttributes;
import android.os.AsyncTask;
import android.os.Binder;
@@ -294,6 +295,25 @@
}
}
+ PackageManagerInternal packageManagerInternal = LocalServices.getService(
+ PackageManagerInternal.class);
+ packageManagerInternal.setExternalSourcesPolicy(
+ new PackageManagerInternal.ExternalSourcesPolicy() {
+ @Override
+ public int getPackageTrustedToInstallApps(String packageName, int uid) {
+ int appOpMode = checkOperation(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+ uid, packageName);
+ switch (appOpMode) {
+ case AppOpsManager.MODE_ALLOWED:
+ return PackageManagerInternal.ExternalSourcesPolicy.USER_TRUSTED;
+ case AppOpsManager.MODE_ERRORED:
+ return PackageManagerInternal.ExternalSourcesPolicy.USER_BLOCKED;
+ default:
+ return PackageManagerInternal.ExternalSourcesPolicy.USER_DEFAULT;
+ }
+ }
+ });
+
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
storageManagerInternal.addExternalStoragePolicy(
@@ -881,11 +901,9 @@
}
code = AppOpsManager.opToSwitch(code);
UidState uidState = getUidStateLocked(uid, false);
- if (uidState != null && uidState.opModes != null) {
- final int uidMode = uidState.opModes.get(code);
- if (uidMode != AppOpsManager.MODE_ALLOWED) {
- return uidMode;
- }
+ if (uidState != null && uidState.opModes != null
+ && uidState.opModes.indexOfKey(code) >= 0) {
+ return uidState.opModes.get(code);
}
Op op = getOpLocked(code, uid, resolvedPackageName, false);
if (op == null) {
@@ -2126,6 +2144,7 @@
UidState uidState = mUidStates.valueAt(i);
pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
+ needSep = true;
SparseIntArray opModes = uidState.opModes;
if (opModes != null) {
@@ -2166,6 +2185,55 @@
}
}
}
+ if (needSep) {
+ pw.println();
+ }
+
+ final int userRestrictionCount = mOpUserRestrictions.size();
+ for (int i = 0; i < userRestrictionCount; i++) {
+ IBinder token = mOpUserRestrictions.keyAt(i);
+ ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
+ pw.println(" User restrictions for token " + token + ":");
+
+ final int restrictionCount = restrictionState.perUserRestrictions != null
+ ? restrictionState.perUserRestrictions.size() : 0;
+ if (restrictionCount > 0) {
+ pw.println(" Restricted ops:");
+ for (int j = 0; j < restrictionCount; j++) {
+ int userId = restrictionState.perUserRestrictions.keyAt(j);
+ boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
+ if (restrictedOps == null) {
+ continue;
+ }
+ StringBuilder restrictedOpsValue = new StringBuilder();
+ restrictedOpsValue.append("[");
+ final int restrictedOpCount = restrictedOps.length;
+ for (int k = 0; k < restrictedOpCount; k++) {
+ if (restrictedOps[k]) {
+ if (restrictedOpsValue.length() > 1) {
+ restrictedOpsValue.append(", ");
+ }
+ restrictedOpsValue.append(AppOpsManager.opToName(k));
+ }
+ }
+ restrictedOpsValue.append("]");
+ pw.print(" "); pw.print("user: "); pw.print(userId);
+ pw.print(" restricted ops: "); pw.println(restrictedOpsValue);
+ }
+ }
+
+ final int excludedPackageCount = restrictionState.perUserExcludedPackages != null
+ ? restrictionState.perUserExcludedPackages.size() : 0;
+ if (excludedPackageCount > 0) {
+ pw.println(" Excluded packages:");
+ for (int j = 0; j < excludedPackageCount; j++) {
+ int userId = restrictionState.perUserExcludedPackages.keyAt(j);
+ String[] packageNames = restrictionState.perUserExcludedPackages.valueAt(j);
+ pw.print(" "); pw.print("user: "); pw.print(userId);
+ pw.print(" packages: "); pw.println(Arrays.toString(packageNames));
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index e7f1d16..f315553 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -50,6 +50,7 @@
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -59,6 +60,8 @@
import android.provider.Settings.SettingNotFoundException;
import android.util.Slog;
+import com.android.server.pm.PackageManagerService;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.ConcurrentHashMap;
@@ -218,6 +221,11 @@
@Override
public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
Bundle prevRestrictions) {
+ if (!newRestrictions.containsKey(UserManager.DISALLOW_BLUETOOTH)
+ && !prevRestrictions.containsKey(UserManager.DISALLOW_BLUETOOTH)) {
+ // The relevant restriction has not changed - do nothing.
+ return;
+ }
final boolean bluetoothDisallowed =
newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH);
if ((mEnable || mEnableExternal) && bluetoothDisallowed) {
@@ -228,6 +236,7 @@
e);
}
}
+ updateOppLauncherComponentState(bluetoothDisallowed);
}
};
@@ -953,7 +962,13 @@
UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
- if (isBluetoothDisallowed()) {
+ final boolean isBluetoothDisallowed = isBluetoothDisallowed();
+ PackageManagerService packageManagerService =
+ (PackageManagerService) ServiceManager.getService("package");
+ if (packageManagerService != null && !packageManagerService.isOnlyCoreApps()) {
+ updateOppLauncherComponentState(isBluetoothDisallowed);
+ }
+ if (isBluetoothDisallowed) {
return;
}
if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) {
@@ -2011,6 +2026,28 @@
}
}
+ /**
+ * Disables BluetoothOppLauncherActivity component, so the Bluetooth sharing option is not
+ * offered to the user if Bluetooth is disallowed. Puts the component to its default state if
+ * Bluetooth is not disallowed.
+ *
+ * @param bluetoothDisallowed whether the {@link UserManager.DISALLOW_BLUETOOTH} user
+ * restriction was set.
+ */
+ private void updateOppLauncherComponentState(boolean bluetoothDisallowed) {
+ final ComponentName oppLauncherComponent = new ComponentName("com.android.bluetooth",
+ "com.android.bluetooth.opp.BluetoothOppLauncherActivity");
+ final int newState = bluetoothDisallowed
+ ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+ try {
+ mContext.getPackageManager()
+ .setComponentEnabledSetting(oppLauncherComponent, newState, 0);
+ } catch (Exception e) {
+ // The component was not found, do nothing.
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
@@ -2034,21 +2071,32 @@
writer.println(" time since enabled: " + onDurationString + "\n");
}
- writer.println("Enable log:");
- for (ActiveLog log : mActiveLogs) {
- writer.println(log);
+ if (mActiveLogs.size() == 0) {
+ writer.println("Bluetooth never enabled!");
+ } else {
+ writer.println("Enable log:");
+ for (ActiveLog log : mActiveLogs) {
+ writer.println(" " + log);
+ }
}
- writer.println("\n" + mBleApps.size() + " BLE Apps registered:");
+ String bleAppString = "No BLE Apps registered.";
+ if (mBleApps.size() == 1) {
+ bleAppString = "1 BLE App registered:";
+ } else if (mBleApps.size() > 1) {
+ bleAppString = mBleApps.size() + " BLE Apps registered:";
+ }
+ writer.println("\n" + bleAppString);
for (ClientDeathRecipient app : mBleApps.values()) {
- writer.println(app.getPackageName());
+ writer.println(" " + app.getPackageName());
}
+ writer.println("");
writer.flush();
if (args.length == 0) {
- // Add arg to produce output
- args = new String[1];
- args[0] = "--print";
+ // Add arg to produce output
+ args = new String[1];
+ args[0] = "--print";
}
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 0a088e9..1f8702a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -690,11 +690,6 @@
}
private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
- @VisibleForTesting
- protected HandlerThread createHandlerThread() {
- return new HandlerThread("ConnectivityServiceThread");
- }
-
public ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
this(context, netManager, statsService, policyManager, new IpConnectivityLog());
@@ -715,7 +710,7 @@
mDefaultMobileDataRequest = createInternetRequestForTransport(
NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
- mHandlerThread = createHandlerThread();
+ mHandlerThread = new HandlerThread("ConnectivityServiceThread");
mHandlerThread.start();
mHandler = new InternalHandler(mHandlerThread.getLooper());
mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
@@ -3121,10 +3116,7 @@
Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
return tetherEnabledInSettings && mUserManager.isAdminUser() &&
- ((mTethering.getTetherableUsbRegexs().length != 0 ||
- mTethering.getTetherableWifiRegexs().length != 0 ||
- mTethering.getTetherableBluetoothRegexs().length != 0) &&
- mTethering.getUpstreamIfaceTypes().length != 0);
+ mTethering.hasTetherableConfiguration();
}
@Override
@@ -5515,6 +5507,18 @@
}
}
+ // Turn Always-on VPN off
+ if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+ mLockdownEnabled = false;
+ setLockdownTracker(null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
// Turn VPN off
VpnConfig vpnConfig = getVpnConfig(userId);
if (vpnConfig != null) {
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 9f63e30..97edb15 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -114,6 +115,7 @@
private AlarmManager mAlarmManager;
private IBatteryStats mBatteryStats;
+ private ActivityManagerInternal mLocalActivityManager;
private PowerManagerInternal mLocalPowerManager;
private PowerManager mPowerManager;
private ConnectivityService mConnectivityService;
@@ -1272,6 +1274,11 @@
DeviceIdleController.this.setAlarmsActive(active);
}
+ /** Is the app on any of the power save whitelists, whether system or user? */
+ public boolean isAppOnWhitelist(int appid) {
+ return DeviceIdleController.this.isAppOnWhitelistInternal(appid);
+ }
+
/**
* Returns the array of app ids whitelisted by user. Take care not to
* modify this, as it is a reference to the original copy. But the reference
@@ -1289,6 +1296,12 @@
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}
+ boolean isAppOnWhitelistInternal(int appid) {
+ synchronized (this) {
+ return Arrays.binarySearch(mPowerSaveWhitelistAllAppIdArray, appid) >= 0;
+ }
+ }
+
int[] getPowerSaveWhitelistUserAppIds() {
synchronized (this) {
return mPowerSaveWhitelistUserAppIdArray;
@@ -1362,6 +1375,7 @@
synchronized (this) {
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
mBatteryStats = BatteryStatsService.getService();
+ mLocalActivityManager = getLocalService(ActivityManagerInternal.class);
mLocalPowerManager = getLocalService(PowerManagerInternal.class);
mPowerManager = getContext().getSystemService(PowerManager.class);
mActiveIdleWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
@@ -1431,6 +1445,7 @@
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getContext().registerReceiver(mReceiver, filter);
+ mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
mDisplayManager.registerDisplayListener(mDisplayListener, null);
@@ -1652,7 +1667,7 @@
} catch (RemoteException e) {
}
postTempActiveTimeoutMessage(appId, duration);
- updateTempWhitelistAppIdsLocked();
+ updateTempWhitelistAppIdsLocked(appId, true);
if (mNetworkPolicyTempWhitelistCallback != null) {
if (!sync) {
mHandler.post(mNetworkPolicyTempWhitelistCallback);
@@ -1698,7 +1713,7 @@
if (DEBUG) {
Slog.d(TAG, "Removing UID " + uid + " from temp whitelist");
}
- updateTempWhitelistAppIdsLocked();
+ updateTempWhitelistAppIdsLocked(uid, false);
if (mNetworkPolicyTempWhitelistCallback != null) {
mHandler.post(mNetworkPolicyTempWhitelistCallback);
}
@@ -2318,6 +2333,13 @@
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);
+ if (mLocalActivityManager != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Setting activity manager whitelist to "
+ + Arrays.toString(mPowerSaveWhitelistAllAppIdArray));
+ }
+ mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
+ }
if (mLocalPowerManager != null) {
if (DEBUG) {
Slog.d(TAG, "Setting wakelock whitelist to "
@@ -2334,7 +2356,7 @@
}
}
- private void updateTempWhitelistAppIdsLocked() {
+ private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) {
final int size = mTempWhitelistAppIdEndTimes.size();
if (mTempWhitelistAppIdArray.length != size) {
mTempWhitelistAppIdArray = new int[size];
@@ -2342,6 +2364,14 @@
for (int i = 0; i < size; i++) {
mTempWhitelistAppIdArray[i] = mTempWhitelistAppIdEndTimes.keyAt(i);
}
+ if (mLocalActivityManager != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Setting activity manager temp whitelist to "
+ + Arrays.toString(mTempWhitelistAppIdArray));
+ }
+ mLocalActivityManager.updateDeviceIdleTempWhitelist(mTempWhitelistAppIdArray, appId,
+ adding);
+ }
if (mLocalPowerManager != null) {
if (DEBUG) {
Slog.d(TAG, "Setting wakelock temp whitelist to "
@@ -2512,8 +2542,9 @@
pw.println(" Add (prefix with +) or remove (prefix with -) packages.");
pw.println(" tempwhitelist");
pw.println(" Print packages that are temporarily whitelisted.");
- pw.println(" tempwhitelist [-u] [package ..]");
- pw.println(" Temporarily place packages in whitelist for 10 seconds.");
+ pw.println(" tempwhitelist [-u USER] [-d DURATION] [package ..]");
+ pw.println(" Temporarily place packages in whitelist for DURATION milliseconds.");
+ pw.println(" If no DURATION is specified, 10 seconds is used");
}
class Shell extends ShellCommand {
@@ -2796,6 +2827,7 @@
}
}
} else if ("tempwhitelist".equals(cmd)) {
+ long duration = 10000;
String opt;
while ((opt=shell.getNextOption()) != null) {
if ("-u".equals(opt)) {
@@ -2805,12 +2837,19 @@
return -1;
}
shell.userId = Integer.parseInt(opt);
+ } else if ("-d".equals(opt)) {
+ opt = shell.getNextArg();
+ if (opt == null) {
+ pw.println("-d requires a duration");
+ return -1;
+ }
+ duration = Long.parseLong(opt);
}
}
String arg = shell.getNextArg();
if (arg != null) {
try {
- addPowerSaveTempWhitelistAppChecked(arg, 10000L, shell.userId, "shell");
+ addPowerSaveTempWhitelistAppChecked(arg, duration, shell.userId, "shell");
} catch (RemoteException re) {
pw.println("Failed: " + re);
}
diff --git a/services/core/java/com/android/server/DiskStatsService.java b/services/core/java/com/android/server/DiskStatsService.java
index 962ac6f..1bdff6b 100644
--- a/services/core/java/com/android/server/DiskStatsService.java
+++ b/services/core/java/com/android/server/DiskStatsService.java
@@ -22,13 +22,20 @@
import android.os.StatFs;
import android.os.SystemClock;
import android.os.storage.StorageManager;
+import android.service.diskstats.DiskStatsAppSizesProto;
+import android.service.diskstats.DiskStatsCachedValuesProto;
+import android.service.diskstats.DiskStatsFreeSpaceProto;
+import android.service.diskstats.DiskStatsServiceDumpProto;
import android.util.Log;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.server.storage.DiskStatsFileLogger;
import com.android.server.storage.DiskStatsLoggingService;
import libcore.io.IoUtils;
+import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -78,30 +85,68 @@
long after = SystemClock.uptimeMillis();
if (tmp.exists()) tmp.delete();
- if (error != null) {
- pw.print("Test-Error: ");
- pw.println(error.toString());
+ boolean protoFormat = hasOption(args, "--proto");
+ ProtoOutputStream proto = null;
+
+ if (protoFormat) {
+ proto = new ProtoOutputStream(fd);
+ pw = null;
+ proto.write(DiskStatsServiceDumpProto.HAS_TEST_ERROR, error != null);
+ if (error != null) {
+ proto.write(DiskStatsServiceDumpProto.ERROR_MESSAGE, error.toString());
+ } else {
+ proto.write(DiskStatsServiceDumpProto.WRITE_512B_LATENCY_MILLIS, after - before);
+ }
} else {
- pw.print("Latency: ");
- pw.print(after - before);
- pw.println("ms [512B Data Write]");
+ if (error != null) {
+ pw.print("Test-Error: ");
+ pw.println(error.toString());
+ } else {
+ pw.print("Latency: ");
+ pw.print(after - before);
+ pw.println("ms [512B Data Write]");
+ }
}
- reportFreeSpace(Environment.getDataDirectory(), "Data", pw);
- reportFreeSpace(Environment.getDownloadCacheDirectory(), "Cache", pw);
- reportFreeSpace(new File("/system"), "System", pw);
+ reportFreeSpace(Environment.getDataDirectory(), "Data", pw, proto,
+ DiskStatsFreeSpaceProto.FOLDER_DATA);
+ reportFreeSpace(Environment.getDownloadCacheDirectory(), "Cache", pw, proto,
+ DiskStatsFreeSpaceProto.FOLDER_CACHE);
+ reportFreeSpace(new File("/system"), "System", pw, proto,
+ DiskStatsFreeSpaceProto.FOLDER_SYSTEM);
- if (StorageManager.isFileEncryptedNativeOnly()) {
+ boolean fileBased = StorageManager.isFileEncryptedNativeOnly();
+ boolean blockBased = fileBased ? false : StorageManager.isBlockEncrypted();
+ if (protoFormat) {
+ if (fileBased) {
+ proto.write(DiskStatsServiceDumpProto.ENCRYPTION,
+ DiskStatsServiceDumpProto.ENCRYPTION_FILE_BASED);
+ } else if (blockBased) {
+ proto.write(DiskStatsServiceDumpProto.ENCRYPTION,
+ DiskStatsServiceDumpProto.ENCRYPTION_FULL_DISK);
+ } else {
+ proto.write(DiskStatsServiceDumpProto.ENCRYPTION,
+ DiskStatsServiceDumpProto.ENCRYPTION_NONE);
+ }
+ } else if (fileBased) {
pw.println("File-based Encryption: true");
}
- reportCachedValues(pw);
+ if (protoFormat) {
+ reportCachedValuesProto(proto);
+ } else {
+ reportCachedValues(pw);
+ }
+ if (protoFormat) {
+ proto.flush();
+ }
// TODO: Read /proc/yaffs and report interesting values;
// add configurable (through args) performance test parameters.
}
- private void reportFreeSpace(File path, String name, PrintWriter pw) {
+ private void reportFreeSpace(File path, String name, PrintWriter pw,
+ ProtoOutputStream proto, int folderType) {
try {
StatFs statfs = new StatFs(path.getPath());
long bsize = statfs.getBlockSize();
@@ -112,22 +157,44 @@
"Invalid stat: bsize=" + bsize + " avail=" + avail + " total=" + total);
}
- pw.print(name);
- pw.print("-Free: ");
- pw.print(avail * bsize / 1024);
- pw.print("K / ");
- pw.print(total * bsize / 1024);
- pw.print("K total = ");
- pw.print(avail * 100 / total);
- pw.println("% free");
+ if (proto != null) {
+ long freeSpaceToken = proto.start(DiskStatsServiceDumpProto.PARTITIONS_FREE_SPACE);
+ proto.write(DiskStatsFreeSpaceProto.FOLDER, folderType);
+ proto.write(DiskStatsFreeSpaceProto.AVAILABLE_SPACE, avail * bsize / 1024);
+ proto.write(DiskStatsFreeSpaceProto.TOTAL_SPACE, total * bsize / 1024);
+ proto.end(freeSpaceToken);
+ } else {
+ pw.print(name);
+ pw.print("-Free: ");
+ pw.print(avail * bsize / 1024);
+ pw.print("K / ");
+ pw.print(total * bsize / 1024);
+ pw.print("K total = ");
+ pw.print(avail * 100 / total);
+ pw.println("% free");
+ }
} catch (IllegalArgumentException e) {
- pw.print(name);
- pw.print("-Error: ");
- pw.println(e.toString());
+ if (proto != null) {
+ // Empty proto
+ } else {
+ pw.print(name);
+ pw.print("-Error: ");
+ pw.println(e.toString());
+ }
return;
}
}
+ private boolean hasOption(String[] args, String arg) {
+ for (String opt : args) {
+ if (arg.equals(opt)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // If you change this method, make sure to modify the Proto version of this method as well.
private void reportCachedValues(PrintWriter pw) {
try {
String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
@@ -159,4 +226,52 @@
}
}
+ private void reportCachedValuesProto(ProtoOutputStream proto) {
+ try {
+ String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
+ JSONObject json = new JSONObject(jsonString);
+ long cachedValuesToken = proto.start(DiskStatsServiceDumpProto.CACHED_FOLDER_SIZES);
+
+ proto.write(DiskStatsCachedValuesProto.AGG_APPS_SIZE,
+ json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+ proto.write(DiskStatsCachedValuesProto.AGG_APPS_CACHE_SIZE,
+ json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
+ proto.write(DiskStatsCachedValuesProto.PHOTOS_SIZE,
+ json.getLong(DiskStatsFileLogger.PHOTOS_KEY));
+ proto.write(DiskStatsCachedValuesProto.VIDEOS_SIZE,
+ json.getLong(DiskStatsFileLogger.VIDEOS_KEY));
+ proto.write(DiskStatsCachedValuesProto.AUDIO_SIZE,
+ json.getLong(DiskStatsFileLogger.AUDIO_KEY));
+ proto.write(DiskStatsCachedValuesProto.DOWNLOADS_SIZE,
+ json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY));
+ proto.write(DiskStatsCachedValuesProto.SYSTEM_SIZE,
+ json.getLong(DiskStatsFileLogger.SYSTEM_KEY));
+ proto.write(DiskStatsCachedValuesProto.OTHER_SIZE,
+ json.getLong(DiskStatsFileLogger.MISC_KEY));
+
+ JSONArray packageNamesArray = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
+ JSONArray appSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
+ JSONArray cacheSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
+ final int len = packageNamesArray.length();
+ if (len == appSizesArray.length() && len == cacheSizesArray.length()) {
+ for (int i = 0; i < len; i++) {
+ long packageToken = proto.start(DiskStatsCachedValuesProto.APP_SIZES);
+
+ proto.write(DiskStatsAppSizesProto.PACKAGE_NAME,
+ packageNamesArray.getString(i));
+ proto.write(DiskStatsAppSizesProto.APP_SIZE, appSizesArray.getLong(i));
+ proto.write(DiskStatsAppSizesProto.CACHE_SIZE, cacheSizesArray.getLong(i));
+
+ proto.end(packageToken);
+ }
+ } else {
+ Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray and cacheSizesArray "
+ + "are not the same");
+ }
+
+ proto.end(cachedValuesToken);
+ } catch (IOException | JSONException e) {
+ Log.w(TAG, "exception reading diskstats cache file", e);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index d42fe11..4559254 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -99,6 +99,13 @@
2810 watchdog_vmstat (runtime|2|3),(pgfree|1|1),(pgactivate|1|1),(pgdeactivate|1|1),(pgfault|1|1),(pgmajfault|1|1)
2811 watchdog_requested_reboot (NoWait|1|1),(ScheduleInterval|1|3),(RecheckInterval|1|3),(StartTime|1|3),(Window|1|3),(MinScreenOff|1|3),(MinNextAlarm|1|3)
+# ---------------------------
+# RescueParty.java
+# ---------------------------
+2900 rescue_note (uid|1),(count|1),(window|2)
+2901 rescue_level (level|1),(trigger_uid|1)
+2902 rescue_success (level|1)
+2903 rescue_failure (level|1),(msg|3)
# ---------------------------
# BackupManagerService.java
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..4ab894f 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;
@@ -131,6 +132,8 @@
private static final String FUSED_LOCATION_SERVICE_ACTION =
"com.android.location.service.FusedLocationProvider";
+ private static final String GMSCORE_PACKAGE = "com.android.google.gms";
+
private static final int MSG_LOCATION_CHANGED = 1;
private static final long NANOS_PER_MILLI = 1000000L;
@@ -138,6 +141,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 = 10 * 60 * 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 +163,7 @@
private GeofenceManager mGeofenceManager;
private PackageManager mPackageManager;
private PowerManager mPowerManager;
+ private ActivityManager mActivityManager;
private UserManager mUserManager;
private GeocoderProxy mGeocodeProvider;
private IGnssStatusProvider mGnssStatusProvider;
@@ -171,47 +178,50 @@
// --- 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
+ // all providers that operate over proxy, for authorizing incoming location and whitelisting
+ // throttling
private final ArrayList<LocationProviderProxy> mProxyProviders =
- new ArrayList<LocationProviderProxy>();
+ new ArrayList<>();
+
+ private String[] mBackgroundThrottlePackageWhitelist = new String[]{};
// current active user on the device - other users are denied location data
private int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -252,6 +262,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 +300,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 +353,37 @@
}
}
}, 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);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
+ true,
+ new ContentObserver(mLocationHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ String setting = Settings.Global.getString(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+ if (setting == null) {
+ setting = "";
+ }
+
+ mBackgroundThrottlePackageWhitelist = setting.split(",");
+ updateProvidersLocked();
+ }
+ }
+ }, UserHandle.USER_ALL);
mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
// listen for user change
@@ -334,6 +413,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 +566,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 +734,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 +774,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
@@ -1017,7 +1097,6 @@
* 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 +1118,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 +1275,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 +1286,7 @@
}
}
- if (getAllowedResolutionLevel(pid, uid) < allowedResolutionLevel) {
- return false;
- }
-
- return true;
+ return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
}
/**
@@ -1228,7 +1298,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 +1321,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 +1440,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 +1478,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 +1493,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 +1554,35 @@
p.setRequest(providerRequest, worksource);
}
+ private boolean isThrottlingExemptLocked(Receiver receiver) {
+ if (receiver.mUid == Process.SYSTEM_UID) {
+ return true;
+ }
+
+ if (receiver.mPackageName.equals(GMSCORE_PACKAGE)) {
+ return true;
+ }
+
+ for (LocationProviderProxy provider : mProxyProviders) {
+ if (receiver.mPackageName.equals(provider.getConnectedPackageName())) {
+ return true;
+ }
+ }
+
+ for (String whitelistedPackage : mBackgroundThrottlePackageWhitelist) {
+ if (receiver.mPackageName.equals(whitelistedPackage)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private class UpdateRecord {
final String mProvider;
final LocationRequest mRequest;
final Receiver mReceiver;
+ boolean mIsForegroundUid;
Location mLastFixBroadcast;
long mLastStatusBroadcast;
@@ -1482,10 +1593,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 +1630,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 +1638,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 +1789,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")
+ + (isThrottlingExemptLocked(receiver) ? " [whitelisted]" : "") + ")");
+
UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
if (oldRecord != null) {
oldRecord.disposeLocked(false);
@@ -1743,7 +1854,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 +2195,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 +2306,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 +2321,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 +2401,7 @@
continue;
}
- Location notifyLocation = null;
+ Location notifyLocation;
if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
notifyLocation = coarseLocation; // use coarse location
} else {
@@ -2333,14 +2438,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 +2522,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 +2799,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/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 51503c0..8ef34dc 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -23,6 +23,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -70,6 +71,7 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
@@ -123,20 +125,23 @@
private final Object mSeparateChallengeLock = new Object();
+ private final Injector mInjector;
private final Context mContext;
private final Handler mHandler;
- private final LockSettingsStorage mStorage;
+ @VisibleForTesting
+ protected final LockSettingsStorage mStorage;
private final LockSettingsStrongAuth mStrongAuth;
private final SynchronizedStrongAuthTracker mStrongAuthTracker;
- private LockPatternUtils mLockPatternUtils;
+ private final LockPatternUtils mLockPatternUtils;
+ private final NotificationManager mNotificationManager;
+ private final UserManager mUserManager;
+ private final IActivityManager mActivityManager;
+
+ private final KeyStore mKeyStore;
+
private boolean mFirstCallToVold;
- private IGateKeeperService mGateKeeperService;
- private NotificationManager mNotificationManager;
- private UserManager mUserManager;
-
- private final KeyStore mKeyStore = KeyStore.getInstance();
-
+ protected IGateKeeperService mGateKeeperService;
/**
* The UIDs that are used for system credential storage in keystore.
*/
@@ -177,7 +182,9 @@
}
}
- private class SynchronizedStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+ @VisibleForTesting
+ protected static class SynchronizedStrongAuthTracker
+ extends LockPatternUtils.StrongAuthTracker {
public SynchronizedStrongAuthTracker(Context context) {
super(context);
}
@@ -196,8 +203,8 @@
}
}
- void register() {
- mStrongAuth.registerStrongAuthTracker(this.mStub);
+ void register(LockSettingsStrongAuth strongAuth) {
+ strongAuth.registerStrongAuthTracker(this.mStub);
}
}
@@ -211,7 +218,7 @@
public void tieManagedProfileLockIfNecessary(int managedUserId, String managedUserPassword) {
if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId);
// Only for managed profile
- if (!UserManager.get(mContext).getUserInfo(managedUserId).isManagedProfile()) {
+ if (!mUserManager.getUserInfo(managedUserId).isManagedProfile()) {
return;
}
// Do not tie managed profile when work challenge is enabled
@@ -258,38 +265,103 @@
}
}
- public LockSettingsService(Context context) {
- mContext = context;
- mHandler = new Handler();
- mStrongAuth = new LockSettingsStrongAuth(context);
- // Open the database
+ static class Injector {
- mLockPatternUtils = new LockPatternUtils(context);
+ protected Context mContext;
+
+ public Injector(Context context) {
+ mContext = context;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Handler getHandler() {
+ return new Handler();
+ }
+
+ public LockSettingsStorage getStorage() {
+ final LockSettingsStorage storage = new LockSettingsStorage(mContext);
+ storage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
+ @Override
+ public void initialize(SQLiteDatabase db) {
+ // Get the lockscreen default from a system property, if available
+ boolean lockScreenDisable = SystemProperties.getBoolean(
+ "ro.lockscreen.disable.default", false);
+ if (lockScreenDisable) {
+ storage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
+ }
+ }
+ });
+ return storage;
+ }
+
+ public LockSettingsStrongAuth getStrongAuth() {
+ return new LockSettingsStrongAuth(mContext);
+ }
+
+ public SynchronizedStrongAuthTracker getStrongAuthTracker() {
+ return new SynchronizedStrongAuthTracker(mContext);
+ }
+
+ public IActivityManager getActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ public LockPatternUtils getLockPatternUtils() {
+ return new LockPatternUtils(mContext);
+ }
+
+ public NotificationManager getNotificationManager() {
+ return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ public UserManager getUserManager() {
+ return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ public KeyStore getKeyStore() {
+ return KeyStore.getInstance();
+ }
+
+ public IStorageManager getStorageManager() {
+ final IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ return IStorageManager.Stub.asInterface(service);
+ }
+ return null;
+ }
+ }
+
+ public LockSettingsService(Context context) {
+ this(new Injector(context));
+ }
+
+ @VisibleForTesting
+ protected LockSettingsService(Injector injector) {
+ mInjector = injector;
+ mContext = injector.getContext();
+ mKeyStore = injector.getKeyStore();
+ mHandler = injector.getHandler();
+ mStrongAuth = injector.getStrongAuth();
+ mActivityManager = injector.getActivityManager();
+
+ mLockPatternUtils = injector.getLockPatternUtils();
mFirstCallToVold = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_STARTING);
filter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+ injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
+ null, null);
- mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() {
- @Override
- public void initialize(SQLiteDatabase db) {
- // Get the lockscreen default from a system property, if available
- boolean lockScreenDisable = SystemProperties.getBoolean(
- "ro.lockscreen.disable.default", false);
- if (lockScreenDisable) {
- mStorage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
- }
- }
- });
- mNotificationManager = (NotificationManager)
- mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mStrongAuthTracker = new SynchronizedStrongAuthTracker(mContext);
- mStrongAuthTracker.register();
-
+ mStorage = injector.getStorage();
+ mNotificationManager = injector.getNotificationManager();
+ mUserManager = injector.getUserManager();
+ mStrongAuthTracker = injector.getStrongAuthTracker();
+ mStrongAuthTracker.register(mStrongAuth);
}
/**
@@ -748,7 +820,8 @@
ks.unlock(userHandle, password);
}
- private String getDecryptedPasswordForTiedProfile(int userId)
+ @VisibleForTesting
+ protected String getDecryptedPasswordForTiedProfile(int userId)
throws KeyStoreException, UnrecoverableKeyException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
@@ -814,7 +887,7 @@
};
try {
- ActivityManager.getService().unlockUser(userId, token, secret, listener);
+ mActivityManager.unlockUser(userId, token, secret, listener);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -961,7 +1034,8 @@
}
}
- private void tieProfileLockToParent(int userId, String password) {
+ @VisibleForTesting
+ protected void tieProfileLockToParent(int userId, String password) {
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
byte[] encryptionResult;
@@ -1085,8 +1159,8 @@
private void addUserKeyAuth(int userId, byte[] token, byte[] secret)
throws RemoteException {
- final UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId);
- final IStorageManager storageManager = getStorageManager();
+ final UserInfo userInfo = mUserManager.getUserInfo(userId);
+ final IStorageManager storageManager = mInjector.getStorageManager();
final long callingId = Binder.clearCallingIdentity();
try {
storageManager.addUserKeyAuth(userId, userInfo.serialNumber, token, secret);
@@ -1097,7 +1171,7 @@
private void fixateNewestUserKeyAuth(int userId)
throws RemoteException {
- final IStorageManager storageManager = getStorageManager();
+ final IStorageManager storageManager = mInjector.getStorageManager();
final long callingId = Binder.clearCallingIdentity();
try {
storageManager.fixateNewestUserKeyAuth(userId);
@@ -1196,9 +1270,11 @@
VerifyCredentialResponse response = verifyCredential(userId, storedHash, credentialToVerify,
hasChallenge, challenge, progressCallback);
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK
- && shouldReEnrollBaseZero) {
- setLockCredentialInternal(credential, storedHash.type, credentialToVerify, userId);
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
+ if (shouldReEnrollBaseZero) {
+ setLockCredentialInternal(credential, storedHash.type, credentialToVerify, userId);
+ }
}
return response;
@@ -1394,7 +1470,7 @@
// we should, within the first minute of decrypting the phone if this
// service can't connect to vold, it restarts, and then the new instance
// does successfully connect.
- final IStorageManager service = getStorageManager();
+ final IStorageManager service = mInjector.getStorageManager();
String password;
long identity = Binder.clearCallingIdentity();
try {
@@ -1559,14 +1635,6 @@
Secure.LOCK_SCREEN_OWNER_INFO
};
- private IStorageManager getStorageManager() {
- final IBinder service = ServiceManager.getService("mount");
- if (service != null) {
- return IStorageManager.Stub.asInterface(service);
- }
- return null;
- }
-
private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java
index f72663a..e131251 100644
--- a/services/core/java/com/android/server/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/LockSettingsShellCommand.java
@@ -77,7 +77,8 @@
}
return 0;
} catch (Exception e) {
- getErrPrintWriter().println("Error while executing command: " + e);
+ getErrPrintWriter().println("Error while executing command: " + cmd);
+ e.printStackTrace(getErrPrintWriter());
return -1;
}
}
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index 3d973a0..c858036 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -119,9 +119,13 @@
boolean isBaseZeroPattern;
}
- public LockSettingsStorage(Context context, Callback callback) {
+ public LockSettingsStorage(Context context) {
mContext = context;
- mOpenHelper = new DatabaseHelper(context, callback);
+ mOpenHelper = new DatabaseHelper(context);
+ }
+
+ public void setDatabaseOnCreateCallback(Callback callback) {
+ mOpenHelper.setCallback(callback);
}
public void writeKeyValue(String key, String value, int userId) {
@@ -472,11 +476,14 @@
private static final int DATABASE_VERSION = 2;
- private final Callback mCallback;
+ private Callback mCallback;
- public DatabaseHelper(Context context, Callback callback) {
+ public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
setWriteAheadLoggingEnabled(true);
+ }
+
+ public void setCallback(Callback callback) {
mCallback = callback;
}
@@ -492,7 +499,9 @@
@Override
public void onCreate(SQLiteDatabase db) {
createTable(db);
- mCallback.initialize(db);
+ if (mCallback != null) {
+ mCallback.initialize(db);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/LockSettingsStrongAuth.java b/services/core/java/com/android/server/LockSettingsStrongAuth.java
index 551ceb8..1314110 100644
--- a/services/core/java/com/android/server/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/LockSettingsStrongAuth.java
@@ -16,23 +16,30 @@
package com.android.server;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
+
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.content.Context;
+import android.os.Binder;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseIntArray;
import java.util.ArrayList;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-
/**
* Keeps track of requests for strong authentication.
*/
@@ -45,12 +52,22 @@
private static final int MSG_UNREGISTER_TRACKER = 3;
private static final int MSG_REMOVE_USER = 4;
+ private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
+ "LockSettingsStrongAuth.timeoutForUser";
+
private final ArrayList<IStrongAuthTracker> mStrongAuthTrackers = new ArrayList<>();
private final SparseIntArray mStrongAuthForUser = new SparseIntArray();
+ private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
+ mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>();
private final int mDefaultStrongAuthFlags;
+ private final Context mContext;
+
+ private AlarmManager mAlarmManager;
public LockSettingsStrongAuth(Context context) {
+ mContext = context;
mDefaultStrongAuthFlags = StrongAuthTracker.getDefaultFlags(context);
+ mAlarmManager = context.getSystemService(AlarmManager.class);
}
private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
@@ -151,6 +168,46 @@
requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
}
+ public void reportSuccessfulStrongAuthUnlock(int userId) {
+ scheduleStrongAuthTimeout(userId);
+ }
+
+ private void scheduleStrongAuthTimeout(int userId) {
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ long when = SystemClock.elapsedRealtime() + dpm.getRequiredStrongAuthTimeout(null, userId);
+ // cancel current alarm listener for the user (if there was one)
+ StrongAuthTimeoutAlarmListener alarm = mStrongAuthTimeoutAlarmListenerForUser.get(userId);
+ if (alarm != null) {
+ mAlarmManager.cancel(alarm);
+ } else {
+ alarm = new StrongAuthTimeoutAlarmListener(userId);
+ mStrongAuthTimeoutAlarmListenerForUser.put(userId, alarm);
+ }
+ // schedule a new alarm listener for the user
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, STRONG_AUTH_TIMEOUT_ALARM_TAG,
+ alarm, mHandler);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
+
+ private final int mUserId;
+
+ public StrongAuthTimeoutAlarmListener(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void onAlarm() {
+ requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, mUserId);
+ }
+ }
+
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index e8ecc3e..25016f6 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;
@@ -56,6 +60,7 @@
import android.provider.Settings;
import android.provider.Settings.Global;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.TimedRemoteCaller;
@@ -73,10 +78,13 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
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.Supplier;
+import java.util.function.UnaryOperator;
/**
* Backing service for {@link android.net.NetworkScoreManager}.
@@ -85,6 +93,7 @@
public class NetworkScoreService extends INetworkScoreService.Stub {
private static final String TAG = "NetworkScoreService";
private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
private final Context mContext;
private final NetworkScorerAppManager mNetworkScorerAppManager;
@@ -391,6 +400,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 +409,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 =
+ FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
+ entry.getKey());
+ sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
}
return true;
@@ -419,6 +421,210 @@
}
}
+ /**
+ * 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
+ static class FilteringCacheUpdatingConsumer
+ implements BiConsumer<INetworkScoreCache, Object> {
+ private final Context mContext;
+ private final List<ScoredNetwork> mScoredNetworkList;
+ private final int mNetworkType;
+ // TODO: 1/23/17 - Consider a Map if we implement more filters.
+ // These are created on-demand to defer the construction cost until
+ // an instance is actually needed.
+ private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
+ private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
+
+ static FilteringCacheUpdatingConsumer create(Context context,
+ List<ScoredNetwork> scoredNetworkList, int networkType) {
+ return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
+ null, null);
+ }
+
+ @VisibleForTesting
+ FilteringCacheUpdatingConsumer(Context context,
+ List<ScoredNetwork> scoredNetworkList, int networkType,
+ UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
+ UnaryOperator<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(filteredNetworkList);
+ }
+ } catch (RemoteException e) {
+ if (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
+ static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
+ private final NetworkKey mCurrentNetwork;
+
+ 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();
+ }
+
+ for (int i = 0; i < scoredNetworks.size(); i++) {
+ final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+ if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
+ return Collections.singletonList(scoredNetwork);
+ }
+ }
+
+ 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 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
+ static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
+ private final Set<NetworkKey> mScanResultKeys;
+
+ ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
+ List<ScanResult> scanResults = resultsSupplier.get();
+ final int size = scanResults.size();
+ mScanResultKeys = new ArraySet<>(size);
+ for (int i = 0; i < 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();
+ }
+
+ List<ScoredNetwork> filteredScores = new ArrayList<>();
+ for (int i = 0; i < scoredNetworks.size(); i++) {
+ final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+ if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
+ filteredScores.add(scoredNetwork);
+ }
+ }
+
+ return filteredScores;
+ }
+ }
+
private boolean isCallerSystemUid() {
// REQUEST_NETWORK_SCORES is a signature only permission.
return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
@@ -499,9 +705,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) {
@@ -574,7 +780,7 @@
return caller.getRecommendationResult(provider, request);
} catch (RemoteException | TimeoutException e) {
Log.w(TAG, "Failed to request a recommendation.", e);
- // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ // TODO: 12/15/16 - Keep track of failures.
}
}
@@ -628,7 +834,7 @@
return;
} catch (RemoteException e) {
Log.w(TAG, "Failed to request a recommendation.", e);
- // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ // TODO: 12/15/16 - Keep track of failures.
// Remove the timeout message
mHandler.removeMessages(timeoutMsg.what, pair);
// Will fall through and send back the default recommendation.
@@ -651,12 +857,12 @@
if (provider != null) {
try {
provider.requestScores(networks);
- // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to
+ // TODO: 12/15/16 - Consider pushing null scores into the cache to
// prevent repeated requests for the same scores.
return true;
} catch (RemoteException e) {
Log.w(TAG, "Failed to request scores.", e);
- // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ // TODO: 12/15/16 - Keep track of failures.
}
}
return false;
@@ -668,32 +874,37 @@
@Override
protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
- NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
- if (currentScorer == null) {
- writer.println("Scoring is disabled.");
- return;
- }
- writer.println("Current scorer: " + currentScorer.packageName);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
+ if (currentScorer == null) {
+ writer.println("Scoring is disabled.");
+ return;
+ }
+ writer.println("Current scorer: " + currentScorer.packageName);
- sendCallback(new Consumer<INetworkScoreCache>() {
- @Override
- public void accept(INetworkScoreCache networkScoreCache) {
- try {
- TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
- } catch (IOException | RemoteException e) {
- writer.println("Failed to dump score cache: " + e);
+ sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
+ @Override
+ public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
+ try {
+ TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
+ } catch (IOException | RemoteException e) {
+ writer.println("Failed to dump score cache: " + e);
+ }
+ }
+ }, getScoreCacheLists());
+
+ synchronized (mServiceConnectionLock) {
+ if (mServiceConnection != null) {
+ mServiceConnection.dump(fd, writer, args);
+ } else {
+ writer.println("ScoringServiceConnection: null");
}
}
- }, getScoreCacheLists());
-
- synchronized (mServiceConnectionLock) {
- if (mServiceConnection != null) {
- mServiceConnection.dump(fd, writer, args);
- } else {
- writer.println("ScoringServiceConnection: null");
- }
+ writer.flush();
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- writer.flush();
}
/**
@@ -708,14 +919,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
new file mode 100644
index 0000000..c77a407
--- /dev/null
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -0,0 +1,327 @@
+/*
+ * 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.ContentResolver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
+import android.os.Build;
+import android.os.RecoverySystem;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.util.ExceptionUtils;
+import android.util.MathUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Utilities to help rescue the system from crash loops. Callers are expected to
+ * report boot events and persistent app crashes, and if they happen frequently
+ * enough this class will slowly escalate through several rescue operations
+ * before finally rebooting and prompting the user if they want to wipe data as
+ * a last resort.
+ *
+ * @hide
+ */
+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";
+
+ private static final int LEVEL_NONE = 0;
+ private static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;
+ private static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;
+ private static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
+ private static final int LEVEL_FACTORY_RESET = 4;
+
+ /** 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() {
+ // We're disabled on all engineering devices
+ if (Build.IS_ENG) return true;
+
+ // We're disabled on userdebug devices connected over USB, since that's
+ // a decent signal that someone is actively trying to debug the device,
+ // or that it's in a lab environment.
+ if (Build.IS_USERDEBUG) {
+ try {
+ if (LocalServices.getService(BatteryManagerInternal.class)
+ .getPlugType() == BatteryManager.BATTERY_PLUGGED_USB) {
+ return true;
+ } else {
+ }
+ } catch (Throwable ignored) {
+ }
+ }
+
+ // One last-ditch check
+ return 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);
+ executeRescueLevel(context);
+ }
+ }
+
+ /**
+ * Take note of a persistent app crash. If we notice too many of these
+ * 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);
+ sApps.put(uid, t);
+ }
+ if (t.incrementAndTest()) {
+ t.reset();
+ incrementRescueLevel(t.uid);
+ executeRescueLevel(context);
+ }
+ }
+
+ /**
+ * Check if we're currently attempting to reboot for a factory reset.
+ */
+ public static boolean isAttemptingFactoryReset() {
+ return SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) == LEVEL_FACTORY_RESET;
+ }
+
+ /**
+ * Escalate to the next rescue level. After incrementing the level you'll
+ * probably want to call {@link #executeRescueLevel(Context)}.
+ */
+ private static void incrementRescueLevel(int triggerUid) {
+ final int level = MathUtils.constrain(
+ SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
+ LEVEL_NONE, LEVEL_FACTORY_RESET);
+ SystemProperties.set(PROP_RESCUE_LEVEL, Integer.toString(level));
+
+ EventLogTags.writeRescueLevel(level, triggerUid);
+ Slog.w(TAG, "Incremented rescue level to " + levelToString(level));
+ }
+
+ /**
+ * Called when {@code SettingsProvider} has been published, which is a good
+ * opportunity to reset any settings depending on our rescue level.
+ */
+ public static void onSettingsProviderPublished(Context context) {
+ executeRescueLevel(context);
+ }
+
+ private static void executeRescueLevel(Context context) {
+ final int level = SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE);
+ if (level == LEVEL_NONE) return;
+
+ Slog.w(TAG, "Attempting rescue level " + levelToString(level));
+ try {
+ executeRescueLevelInternal(context, level);
+ EventLogTags.writeRescueSuccess(level);
+ Slog.d(TAG, "Finished rescue level " + levelToString(level));
+ } catch (Throwable t) {
+ EventLogTags.writeRescueFailure(level, ExceptionUtils.getCompleteMessage(t));
+ Slog.e(TAG, "Failed rescue level " + levelToString(level), t);
+ }
+ }
+
+ private static void executeRescueLevelInternal(Context context, int level) throws Exception {
+ switch (level) {
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+ break;
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_CHANGES);
+ break;
+ case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS);
+ break;
+ case LEVEL_FACTORY_RESET:
+ RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
+ break;
+ }
+ }
+
+ private static void resetAllSettings(Context context, int mode) throws Exception {
+ // Try our best to reset all settings possible, and once finished
+ // rethrow any exception that we encountered
+ Exception res = null;
+ final ContentResolver resolver = context.getContentResolver();
+ try {
+ Settings.Global.resetToDefaultsAsUser(resolver, null, mode, UserHandle.USER_SYSTEM);
+ } catch (Exception e) {
+ res = new RuntimeException("Failed to reset global settings", e);
+ }
+ for (int userId : getAllUserIds(context)) {
+ try {
+ Settings.Secure.resetToDefaultsAsUser(resolver, null, mode, userId);
+ } catch (Exception e) {
+ res = new RuntimeException("Failed to reset secure settings for " + userId, e);
+ }
+ }
+ if (res != null) {
+ throw res;
+ }
+ }
+
+ /**
+ * Threshold that can be triggered if a number of events occur within a
+ * window of time.
+ */
+ private abstract static class Threshold {
+ public abstract int getCount();
+ public abstract void setCount(int count);
+ public abstract long getStart();
+ public abstract void setStart(long start);
+
+ private final int uid;
+ private final int triggerCount;
+ private final long triggerWindow;
+
+ public Threshold(int uid, int triggerCount, long triggerWindow) {
+ this.uid = uid;
+ this.triggerCount = triggerCount;
+ this.triggerWindow = triggerWindow;
+ }
+
+ public void reset() {
+ setCount(0);
+ setStart(0);
+ }
+
+ /**
+ * @return if this threshold has been triggered
+ */
+ public boolean incrementAndTest() {
+ final long now = SystemClock.elapsedRealtime();
+ final long window = now - getStart();
+ if (window > triggerWindow) {
+ setCount(1);
+ setStart(now);
+ return false;
+ } else {
+ int count = getCount() + 1;
+ setCount(count);
+ EventLogTags.writeRescueNote(uid, count, window);
+ Slog.w(TAG, "Noticed " + count + " events for UID " + uid + " in last "
+ + (window / 1000) + " sec");
+ return (count >= triggerCount);
+ }
+ }
+ }
+
+ /**
+ * Specialization of {@link Threshold} for monitoring boot events. It stores
+ * counters in system properties for robustness.
+ */
+ private static class BootThreshold extends Threshold {
+ public BootThreshold() {
+ // We're interested in 5 events in any 300 second period; this
+ // window is super relaxed because booting can take a long time if
+ // forced to dexopt things.
+ super(android.os.Process.ROOT_UID, 5, 300 * DateUtils.SECOND_IN_MILLIS);
+ }
+
+ @Override
+ public int getCount() {
+ return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0);
+ }
+
+ @Override
+ public void setCount(int count) {
+ SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count));
+ }
+
+ @Override
+ public long getStart() {
+ return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0);
+ }
+
+ @Override
+ public void setStart(long start) {
+ SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(start));
+ }
+ }
+
+ /**
+ * Specialization of {@link Threshold} for monitoring app crashes. It stores
+ * counters in memory.
+ */
+ private static class AppThreshold extends Threshold {
+ private int count;
+ private long start;
+
+ public AppThreshold(int uid) {
+ // We're interested in 5 events in any 30 second period; apps crash
+ // pretty quickly so we can keep a tight leash on them.
+ super(uid, 5, 30 * DateUtils.SECOND_IN_MILLIS);
+ }
+
+ @Override public int getCount() { return count; }
+ @Override public void setCount(int count) { this.count = count; }
+ @Override public long getStart() { return start; }
+ @Override public void setStart(long start) { this.start = start; }
+ }
+
+ private static int[] getAllUserIds(Context context) {
+ int[] userIds = { UserHandle.USER_SYSTEM };
+ try {
+ final UserManager um = context.getSystemService(UserManager.class);
+ for (UserInfo user : um.getUsers()) {
+ if (user.id != UserHandle.USER_SYSTEM) {
+ userIds = ArrayUtils.appendInt(userIds, user.id);
+ }
+ }
+ } catch (Throwable t) {
+ Slog.w(TAG, "Trouble discovering users", t);
+ }
+ return userIds;
+ }
+
+ private static String levelToString(int level) {
+ switch (level) {
+ case LEVEL_NONE: return "NONE";
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: return "RESET_SETTINGS_UNTRUSTED_CHANGES";
+ case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: return "RESET_SETTINGS_TRUSTED_DEFAULTS";
+ case LEVEL_FACTORY_RESET: return "FACTORY_RESET";
+ default: return Integer.toString(level);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index f9b9d6f..629da86 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;
@@ -107,6 +108,8 @@
import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.pm.PackageManagerService;
import com.android.server.storage.AppFuseBridge;
+import com.android.server.storage.FileCollector;
+
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -816,6 +819,9 @@
}
private void handleSystemReady() {
+ // Register kernel mapping from extensions to statistics GIDs
+ FileCollector.updateKernelExtensions();
+
initIfReadyAndConnected();
resetIfReadyAndConnected();
@@ -2079,9 +2085,6 @@
@Override
public String getPrimaryStorageUuid() {
- enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
-
synchronized (mLock) {
return mPrimaryStorageUuid;
}
@@ -2991,38 +2994,6 @@
}
}
- @Override
- public ParcelFileDescriptor mountAppFuse(final String name) throws RemoteException {
- try {
- final int uid = Binder.getCallingUid();
- final int pid = Binder.getCallingPid();
- final NativeDaemonEvent event =
- mConnector.execute("appfuse", "mount", uid, pid, name);
- if (event.getFileDescriptors() == null) {
- throw new RemoteException("AppFuse FD from vold is null.");
- }
- return ParcelFileDescriptor.fromFd(
- event.getFileDescriptors()[0],
- mHandler,
- new ParcelFileDescriptor.OnCloseListener() {
- @Override
- public void onClose(IOException e) {
- try {
- final NativeDaemonEvent event = mConnector.execute(
- "appfuse", "unmount", uid, pid, name);
- } catch (NativeDaemonConnectorException unmountException) {
- Log.e(TAG, "Failed to unmount appfuse.");
- }
- }
- });
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- } catch (IOException e) {
- throw new RemoteException(e.getMessage());
- }
- }
-
-
class CloseableHolder<T extends AutoCloseable> implements AutoCloseable {
@Nullable T mCloseable;
@@ -3302,6 +3273,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/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 62f4f19..0834eb8 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1360,6 +1360,7 @@
}
Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
Bundle data = new Bundle();
state.fillInNotifierBundle(data);
intent.putExtras(data);
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 440ac90..8f99127 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -45,12 +45,16 @@
import android.service.dreams.Sandman;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
+import android.text.TextUtils;
import android.util.Slog;
import android.view.WindowManagerInternal;
import android.view.WindowManagerPolicy;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
import com.android.internal.R;
import com.android.internal.app.DisableCarModeActivity;
@@ -343,6 +347,28 @@
}
@Override
+ public String[] getAvailableThemes() {
+ if (getContext().checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_THEME_OVERLAY)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.e(TAG, "getAvailableThemes requires MODIFY_THEME_OVERLAY permission");
+ return null;
+ }
+ String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
+ if (TextUtils.isEmpty(def)) {
+ def = null;
+ }
+ String[] fileList = new File("/vendor/overlay").list();
+ if (fileList == null) return new String[0];
+ ArrayList<String> options = new ArrayList(fileList.length + 1);
+ Collections.addAll(options, fileList);
+ if (!options.contains(def)) {
+ options.add(0, def);
+ }
+ return options.toArray(new String[options.size()]);
+ }
+
+ @Override
public int getNightMode() {
synchronized (mLock) {
return mNightMode;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 11e1a9d..bfa3f04 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -63,8 +63,8 @@
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -76,6 +76,7 @@
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -111,15 +112,16 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
-import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -174,17 +176,18 @@
private static final String PRE_N_DATABASE_NAME = "accounts.db";
private static final Intent ACCOUNTS_CHANGED_INTENT;
+ private static final int SIGNATURE_CHECK_MISMATCH = 0;
+ private static final int SIGNATURE_CHECK_MATCH = 1;
+ private static final int SIGNATURE_CHECK_UID_MATCH = 2;
+
static {
ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
}
-
private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
private final AtomicInteger mNotificationIds = new AtomicInteger(1);
- private static final String NEW_ACCOUNT_VISIBLE = "android.accounts.NEW_ACCOUNT_VISIBLE";
-
static class UserAccounts {
private final int userId;
final AccountsDb accountsDb;
@@ -204,6 +207,11 @@
/** protected by the {@link #cacheLock} */
private final TokenCache accountTokenCaches = new TokenCache();
+ /** protected by the {@link #cacheLock} */
+ // TODO use callback to set up the map.
+ private final Map<String, LinkedHashSet<String>> mApplicationAccountRequestMappings =
+ new HashMap<>();
+
/**
* protected by the {@link #cacheLock}
*
@@ -261,8 +269,6 @@
sThis.set(this);
- addRequestsForPreInstalledApplications();
-
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
@@ -285,7 +291,7 @@
@Override
public void run() {
purgeOldGrantsAll();
- // TODO remove visibility entries.
+ // Notify authenticator about removed app?
}
};
mHandler.post(purgingRunnable);
@@ -294,29 +300,6 @@
}
}, intentFilter);
- IntentFilter packageAddedOrChangedFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- packageAddedOrChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageAddedOrChangedFilter.addDataScheme("package");
- mContext.registerReceiverAsUser(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context1, Intent intent) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- int uidOfInstalledApplication =
- intent.getIntExtra(Intent.EXTRA_UID, -1);
- if(uidOfInstalledApplication != -1) {
- registerAccountTypesSupported(
- uidOfInstalledApplication,
- getUserAccounts(
- UserHandle.getUserId(uidOfInstalledApplication)));
- }
- }
- });
- }
- }, UserHandle.ALL, packageAddedOrChangedFilter, null, null);
-
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@@ -380,11 +363,13 @@
final long identity = Binder.clearCallingIdentity();
try {
for (String packageName : packageNames) {
- if (mPackageManager.checkPermission(
- Manifest.permission.GET_ACCOUNTS, packageName)
- != PackageManager.PERMISSION_GRANTED) {
- continue;
- }
+ // if app asked for permission we need to cancel notification even
+ // for O+ applications.
+ if (mPackageManager.checkPermission(
+ Manifest.permission.GET_ACCOUNTS,
+ packageName) != PackageManager.PERMISSION_GRANTED) {
+ continue;
+ }
if (accounts == null) {
accounts = getAccountsAsUser(null, userId, "android");
@@ -443,106 +428,376 @@
}
@Override
- public boolean addAccountExplicitlyWithVisibility(Account account, String password, Bundle extras,
- Map uidToVisibility) {
- // TODO implementation
- return false;
+ public boolean addAccountExplicitlyWithVisibility(Account account, String password,
+ Bundle extras, Map uidToVisibility) {
+ Bundle.setDefusable(extras, true);
+
+ final int callingUid = Binder.getCallingUid();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccountExplicitly: " + account + ", caller's uid " + callingUid
+ + ", pid " + Binder.getCallingPid());
+ }
+ Preconditions.checkNotNull(account, "account cannot be null");
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
+ String msg = String.format("uid %s cannot explicitly add accounts of type: %s",
+ callingUid, account.type);
+ throw new SecurityException(msg);
+ }
+ /*
+ * Child users are not allowed to add accounts. Only the accounts that are shared by the
+ * parent profile can be added to child profile.
+ *
+ * TODO: Only allow accounts that were shared to be added by a limited user.
+ */
+ // fails if the account already exists
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ return addAccountInternal(accounts, account, password, extras, callingUid,
+ (Map<Integer, Integer>) uidToVisibility);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
}
@Override
public Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
String accountType) {
- // TODO Implement.
- return new HashMap<Account, Integer>();
+ int callingUid = Binder.getCallingUid();
+ boolean isSystemUid = UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
+ List<String> managedTypes =
+ getTypesForCaller(callingUid, UserHandle.getUserId(callingUid), isSystemUid);
+
+ if ((accountType != null && !managedTypes.contains(accountType))
+ || (accountType == null && !isSystemUid)) {
+ throw new SecurityException(
+ "getAccountsAndVisibilityForPackage() called from unauthorized uid "
+ + callingUid + " with packageName=" + packageName);
+ }
+ if (accountType != null) {
+ managedTypes = new ArrayList<String>();
+ managedTypes.add(accountType);
+ }
+
+ return getAccountsAndVisibilityForPackage(packageName, managedTypes, callingUid,
+ getUserAccounts(UserHandle.getUserId(callingUid)));
+ }
+
+ /*
+ * accountTypes may not be null
+ */
+ private Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
+ List<String> accountTypes, Integer callingUid, UserAccounts accounts) {
+ int uid = 0;
+ try {
+ uid = mPackageManager.getPackageUidAsUser(packageName,
+ UserHandle.getUserId(callingUid));
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
+ return new HashMap<>();
+ }
+
+ Map<Account, Integer> result = new HashMap<>();
+ for (String accountType : accountTypes) {
+ synchronized (accounts.cacheLock) {
+ final Account[] accountsOfType = accounts.accountCache.get(accountType);
+ if (accountsOfType != null) {
+ for (Account account : accountsOfType) {
+ result.put(account,
+ resolveAccountVisibility(account, uid, packageName, accounts));
+ }
+ }
+ }
+ }
+ return filterSharedAccounts(accounts, result, callingUid, packageName);
}
@Override
- public int[] getRequestingUidsForType(String accountType) {
+ public Map<Integer, Integer> getUidsAndVisibilityForAccount(Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
int callingUid = Binder.getCallingUid();
- if (!isAccountManagedByCaller(accountType, callingUid, UserHandle.getUserId(callingUid))) {
- String msg = String.format(
- "uid %s cannot get secrets for accounts of type: %s",
- callingUid,
- accountType);
+ int userId = UserHandle.getUserId(callingUid);
+ UserAccounts accounts = getUserAccounts(userId);
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)
+ && !isSystemUid(callingUid)) {
+ String msg =
+ String.format("uid %s cannot get secrets for account %s", callingUid, account);
throw new SecurityException(msg);
}
- // TODO Implement.
- return new int[]{};
+ return getUidsAndVisibilityForAccount(account, accounts);
+ }
+
+ /**
+ * Returns all UIDs and visibility values, which were set for given account
+ *
+ * @param account account
+ * @param accounts UserAccount that currently hosts the account and application
+ *
+ * @return Map from uid to visibility.
+ */
+ private Map<Integer, Integer> getUidsAndVisibilityForAccount(Account account,
+ UserAccounts accounts) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ return accounts.accountsDb.findAllVisibilityValuesForAccount(account);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+
}
@Override
public int getAccountVisibility(Account a, int uid) {
- // TODO Implement.
- return 0;
+ if (a == null) throw new IllegalArgumentException("account is null");
+ int callingUid = Binder.getCallingUid();
+ if (!isAccountManagedByCaller(a.type, callingUid, UserHandle.getUserId(callingUid))
+ && !isSystemUid(callingUid)) {
+ String msg = String.format(
+ "uid %s cannot get secrets for accounts of type: %s",
+ callingUid,
+ a.type);
+ throw new SecurityException(msg);
+ }
+ return getAccountVisibility(a, uid, getUserAccounts(UserHandle.getUserId(callingUid)));
+ }
+
+ /**
+ * Method gets visibility for given account and UID from the database
+ *
+ * @param account The account to check visibility of
+ * @param uid UID to check visibility of
+ * @param accounts UserAccount that currently hosts the account and application
+ *
+ * @return Visibility value, AccountManager.VISIBILITY_UNDEFINED if no value was stored.
+ *
+ */
+ private int getAccountVisibility(Account account, int uid, UserAccounts accounts) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ Integer visibility = accounts.accountsDb.findAccountVisibility(account, uid);
+ return visibility != null ? visibility : AccountManager.VISIBILITY_UNDEFINED;
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ /**
+ * Method which handles default values for Account visibility.
+ *
+ * @param account The account to check visibility.
+ * @param uid UID to check visibility.
+ * @param packageName Package name to check visibility - the method assumes that it has the same
+ * uid as specified in the parameter.
+ * @param accounts UserAccount that currently hosts the account and application
+ *
+ * @return Visibility value, the method never returns AccountManager.VISIBILITY_UNDEFINED
+ *
+ */
+ private Integer resolveAccountVisibility(Account account, int uid, String packageName,
+ UserAccounts accounts) {
+ if (packageName == null) {
+ packageName = getPackageNameForUid(uid);
+ }
+ // System visibility can not be restricted.
+ if (UserHandle.isSameApp(uid, Process.SYSTEM_UID)) {
+ return AccountManager.VISIBILITY_VISIBLE;
+ }
+
+ int signatureCheckResult =
+ checkPackageSignature(account.type, uid, accounts.userId);
+
+ // Authenticator can not restrict visibility to itself.
+ if (signatureCheckResult == SIGNATURE_CHECK_UID_MATCH) {
+ return AccountManager.VISIBILITY_VISIBLE; // Authenticator can always see the account
+ }
+
+ // Return stored value if it was set.
+ int visibility = getAccountVisibility(account, uid, accounts);
+
+ if (AccountManager.VISIBILITY_UNDEFINED != visibility) {
+ return visibility;
+ }
+
+ if (isPermittedForPackage(packageName, accounts.userId,
+ Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) {
+ return AccountManager.VISIBILITY_VISIBLE;
+
+ }
+ // Profile owner gets visibility by default.
+ if(isProfileOwner(uid)) {
+ return AccountManager.VISIBILITY_VISIBLE;
+ }
+ // Apps with READ_CONTACTS permission get visibility by default even post O.
+ boolean canReadContacts = checkReadContactsPermission(packageName, accounts.userId);
+
+ boolean preO = isPreOApplication(packageName);
+ if ((signatureCheckResult != SIGNATURE_CHECK_MISMATCH)
+ || (preO && checkGetAccountsPermission(packageName, accounts.userId))
+ || canReadContacts) {
+ // Use legacy for preO apps with GET_ACCOUNTS permission or pre/postO with signature
+ // match.
+ visibility = getAccountVisibility(account,
+ AccountManager.UID_KEY_DEFAULT_LEGACY_VISIBILITY, accounts);
+ if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
+ visibility = AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
+ }
+ } else {
+ visibility = getAccountVisibility(account, AccountManager.UID_KEY_DEFAULT_VISIBILITY,
+ accounts);
+ if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
+ visibility = AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE;
+ }
+ }
+ return visibility;
+ }
+
+ /**
+ * Checks targetSdk for a package;
+ *
+ * @param packageName Package Name
+ *
+ * @return True if package's target SDK is below {@link android.os.Build.VERSION_CODES#O}, or
+ * undefined
+ */
+ private boolean isPreOApplication(String packageName) {
+ try {
+ long identityToken = clearCallingIdentity();
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = mPackageManager.getApplicationInfo(packageName, 0);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+
+ if (applicationInfo != null) {
+ int version = applicationInfo.targetSdkVersion;
+ return version < android.os.Build.VERSION_CODES.O;
+ }
+ return true;
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
+ return true;
+ }
}
@Override
- public boolean setAccountVisibility(Account a, int uid, int visibility) {
- // TODO Implement.
- return false;
- }
-
- /**
- * Registers the requested login account types requested by all the applications already
- * installed on the device.
- */
- private void addRequestsForPreInstalledApplications() {
- List<PackageInfo> allInstalledPackages = mPackageManager.getInstalledPackages(0);
- for(PackageInfo pi : allInstalledPackages) {
- int currentUid = pi.applicationInfo.uid;
- if(currentUid != -1) {
- registerAccountTypesSupported(currentUid,
- getUserAccounts(UserHandle.getUserId(currentUid)));
- }
+ public boolean setAccountVisibility(Account a, int uid, int newVisibility) {
+ if (a == null) throw new IllegalArgumentException("account is null");
+ int callingUid = Binder.getCallingUid();
+ if (!isAccountManagedByCaller(a.type, callingUid, UserHandle.getUserId(callingUid))
+ && !isSystemUid(callingUid)) {
+ String msg = String.format(
+ "uid %s cannot get secrets for accounts of type: %s",
+ callingUid,
+ a.type);
+ throw new SecurityException(msg);
}
+ return setAccountVisibility(a, uid, newVisibility, true /* notify */,
+ getUserAccounts(UserHandle.getUserId(callingUid)));
}
/**
- * Registers an application, represented by a UID, to support account types detailed in the
- * applications manifest as well as allowing it to opt for notifications.
+ * Gives a certain UID, represented a application, access to an account. This method
+ * is called indirectly by the Authenticator.
*
- * @param uid UID of application
- * @param ua UserAccount that currently hosts the account and application
+ * @param account Account to update visibility
+ * @param uid to add visibility of the Account
+ * @param newVisibility new visibility
+ * @param notify if the flag is set applications will get notification about visibility change
+ * @param accounts UserAccount that currently hosts the account and application
+ *
+ * @return True if account visibility was changed.
*/
- private void registerAccountTypesSupported(int uid, UserAccounts ua) {
- /* Account types supported are drawn from the Android Manifest of the Application */
- String interestedPackages = null;
- try {
- String[] allPackages = mPackageManager.getPackagesForUid(uid);
- if (allPackages != null) {
- for (String aPackage : allPackages) {
- ApplicationInfo ai = mPackageManager.getApplicationInfo(aPackage,
- PackageManager.GET_META_DATA);
- Bundle b = ai.metaData;
- if (b == null) {
- return;
+ private boolean setAccountVisibility(Account account, int uid, int newVisibility,
+ boolean notify, UserAccounts accounts) {
+ synchronized (accounts.cacheLock) {
+ LinkedHashSet<String> interestedPackages;
+ if (notify) {
+ if (uid < 0) {
+ interestedPackages = getRequestingPackageNames(account.type, accounts);
+ } else {
+ interestedPackages = new LinkedHashSet<>();
+ String[] subPackages = mPackageManager.getPackagesForUid(uid);
+ if (subPackages != null) {
+ Collections.addAll(interestedPackages, subPackages);
}
- interestedPackages = b.getString(AccountManager.SUPPORTED_ACCOUNT_TYPES);
+ }
+ } else {
+ // Notifications will not be send.
+ interestedPackages = new LinkedHashSet<>();
+ }
+ Integer[] interestedPackagesVisibility = new Integer[interestedPackages.size()];
+
+ final long accountId = accounts.accountsDb.findDeAccountId(account);
+ if (accountId < 0) {
+ return false;
+ }
+ int index = 0;
+ for (String packageName : interestedPackages) {
+ interestedPackagesVisibility[index++] =
+ resolveAccountVisibility(account, uid, packageName, accounts);
+ }
+
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ if (!accounts.accountsDb.setAccountVisibility(accountId, uid, newVisibility)) {
+ return false;
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+
+ index = 0;
+ for (String packageName : interestedPackages) {
+ int visibility = resolveAccountVisibility(account, uid, packageName, accounts);
+ if (visibility != interestedPackagesVisibility[index++]) {
+ sendNotification(packageName, account, accounts.userId);
}
}
- } catch (PackageManager.NameNotFoundException e) {
- Log.d("NameNotFoundException", e.getMessage());
- }
- if (interestedPackages != null) {
- // TODO request visibility
- // requestAccountVisibility(interestedPackages.split(";"), uid, ua);
+ if (notify) {
+ sendAccountsChangedBroadcast(accounts.userId);
+ }
+ return true;
}
}
/**
* Sends a direct intent to a package, notifying it of a visible account change.
*
- * @param desiredPackage to send Account to
- * @param visibleAccount to send to package
+ * @param packageName to send Account to
+ * @param account to send to package
+ * @param userId User
*/
- private void sendNotification(String desiredPackage, Account visibleAccount) {
- Intent intent = new Intent();
- intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- intent.setAction(AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED);
- intent.setPackage(desiredPackage);
- // TODO update documentation, add account extra if new account became visible
- // intent.putExtra("android.accounts.KEY_ACCOUNT", (Account) visibleAccount);
- mContext.sendBroadcast(intent);
+ private void sendNotification(String packageName, Account account, int userId) {
+ // TODO send notification so apps subscribed in runtime.
+ }
+
+ private void sendNotification(Account account, UserAccounts accounts) {
+ LinkedHashSet<String> interestedPackages = getRequestingPackageNames(account.type,
+ accounts);
+ for (String packageName : interestedPackages) {
+ try {
+ final int uid = mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
+ int visibility = resolveAccountVisibility(account, uid, packageName, accounts);
+ if (visibility != AccountManager.VISIBILITY_NOT_VISIBLE) {
+ sendNotification(packageName, account, accounts.userId);
+ }
+ } catch (NameNotFoundException e) {
+ // ignore
+ }
+ }
+ }
+
+ LinkedHashSet<String> getRequestingPackageNames(String accountType, UserAccounts accouns) {
+ // TODO return packages registered to get notifications.
+ return new LinkedHashSet<String>();
+ }
+
+ private void sendAccountsChangedBroadcast(int userId) {
+ Log.i(TAG, "the accounts changed, sending broadcast of "
+ + ACCOUNTS_CHANGED_INTENT.getAction());
+ mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
}
@Override
@@ -682,6 +937,12 @@
accounts.userDataCache.remove(account);
accounts.authTokenCache.remove(account);
accounts.accountTokenCaches.remove(account);
+ LinkedHashSet<String> interestedPackages =
+ getRequestingPackageNames(account.type, accounts);
+ for (String packageName : interestedPackages) {
+ sendNotification(packageName, null, accounts.userId);
+ }
+
} else {
ArrayList<String> accountNames = accountNamesByType.get(account.type);
if (accountNames == null) {
@@ -786,7 +1047,7 @@
AccountsDb.TABLE_ACCOUNTS);
for (Account account : accountsToRemove) {
- removeAccountInternal(accounts, account, Process.myUid());
+ removeAccountInternal(accounts, account, Process.SYSTEM_UID);
}
}
}
@@ -1043,7 +1304,7 @@
private boolean isCrossUser(int callingUid, int userId) {
return (userId != UserHandle.getCallingUserId()
- && callingUid != Process.myUid()
+ && callingUid != Process.SYSTEM_UID
&& mContext.checkCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED);
@@ -1051,42 +1312,7 @@
@Override
public boolean addAccountExplicitly(Account account, String password, Bundle extras) {
- Bundle.setDefusable(extras, true);
- // clears the visible list functionality for this account because this method allows
- // default account access to all applications for account.
-
- final int callingUid = Binder.getCallingUid();
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "addAccountExplicitly: " + account
- + ", caller's uid " + callingUid
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- int userId = UserHandle.getCallingUserId();
- if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
- String msg = String.format(
- "uid %s cannot explicitly add accounts of type: %s",
- callingUid,
- account.type);
- throw new SecurityException(msg);
- }
-
- /*
- * Child users are not allowed to add accounts. Only the accounts that are
- * shared by the parent profile can be added to child profile.
- *
- * TODO: Only allow accounts that were shared to be added by
- * a limited user.
- */
-
- // fails if the account already exists
- long identityToken = clearCallingIdentity();
- try {
- UserAccounts accounts = getUserAccounts(userId);
- return addAccountInternal(accounts, account, password, extras, callingUid);
- } finally {
- restoreCallingIdentity(identityToken);
- }
+ return addAccountExplicitlyWithVisibility(account, password, extras, null);
}
@Override
@@ -1227,6 +1453,8 @@
// TODO: Anything to do if if succedded?
// TODO: If it failed: Show error notification? Should we remove the shadow
// account to avoid retries?
+ // TODO: what we do with the visibility?
+
super.onResult(result);
}
@@ -1244,7 +1472,7 @@
}
private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
- Bundle extras, int callingUid) {
+ Bundle extras, int callingUid, Map<Integer, Integer> uidToVisibility) {
Bundle.setDefusable(extras, true);
if (account == null) {
return false;
@@ -1284,10 +1512,17 @@
}
}
}
+
+ if (uidToVisibility != null) {
+ for (Entry<Integer, Integer> entry : uidToVisibility.entrySet()) {
+ setAccountVisibility(account, entry.getKey() /* uid */,
+ entry.getValue() /* visibility */, false /* notify */, accounts);
+ }
+ }
accounts.accountsDb.setTransactionSuccessful();
- logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS,
- accountId, accounts, callingUid);
+ logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS, accountId,
+ accounts, callingUid);
insertAccountIntoCacheLocked(accounts, account);
} finally {
@@ -1298,8 +1533,10 @@
addAccountToLinkedRestrictedUsers(account, accounts.userId);
}
+ sendNotification(account, accounts);
// Only send LOGIN_ACCOUNTS_CHANGED when the database changed.
sendAccountsChangedBroadcast(accounts.userId);
+
return true;
}
@@ -1476,6 +1713,10 @@
synchronized (accounts.cacheLock) {
accounts.accountsDb.beginTransaction();
Account renamedAccount = new Account(newName, accountToRename.type);
+ if ((accounts.accountsDb.findCeAccountId(renamedAccount) >= 0)) {
+ Log.e(TAG, "renameAccount failed - account with new name already exists");
+ return null;
+ }
try {
final long accountId = accounts.accountsDb.findDeAccountId(accountToRename);
if (accountId >= 0) {
@@ -1487,6 +1728,9 @@
Log.e(TAG, "renameAccount failed");
return null;
}
+ } else {
+ Log.e(TAG, "renameAccount failed - old account does not exist");
+ return null;
}
} finally {
accounts.accountsDb.endTransaction();
@@ -1529,6 +1773,9 @@
}
}
}
+
+ // Notify authenticator.
+ sendNotification(resultAccount, accounts);
sendAccountsChangedBroadcast(accounts.userId);
}
return resultAccount;
@@ -1727,6 +1974,24 @@
+ " is still locked. CE data will be removed later");
}
synchronized (accounts.cacheLock) {
+ LinkedHashSet<String> interestedPackages =
+ accounts.mApplicationAccountRequestMappings.get(account.type);
+ if (interestedPackages == null) {
+ interestedPackages = new LinkedHashSet<>();
+ }
+ int[] visibilityForInterestedPackages = new int[interestedPackages.size()];
+ int index = 0;
+ for (String packageName : interestedPackages) {
+ int visibility = AccountManager.VISIBILITY_NOT_VISIBLE;
+ try {
+ final int uid = mPackageManager.getPackageUidAsUser(packageName,
+ UserHandle.getUserId(callingUid));
+ visibility = resolveAccountVisibility(account, uid, packageName, accounts);
+ } catch (NameNotFoundException e) {
+ // ignore
+ }
+ visibilityForInterestedPackages[index++] = visibility;
+ }
accounts.accountsDb.beginTransaction();
// Set to a dummy value, this will only be used if the database
// transaction succeeds.
@@ -1750,6 +2015,17 @@
}
if (isChanged) {
removeAccountFromCacheLocked(accounts, account);
+ index = 0;
+ for (String packageName : interestedPackages) {
+ if ((visibilityForInterestedPackages[index]
+ != AccountManager.VISIBILITY_NOT_VISIBLE)
+ && (visibilityForInterestedPackages[index]
+ != AccountManager.VISIBILITY_UNDEFINED)) {
+ sendNotification(packageName, account, accounts.userId);
+ }
+ ++index;
+ }
+
// Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occured.
sendAccountsChangedBroadcast(accounts.userId);
String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
@@ -2016,18 +2292,13 @@
accounts.accountsDb.endTransaction();
if (isChanged) {
// Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
+ sendNotification(account, accounts);
sendAccountsChangedBroadcast(accounts.userId);
}
}
}
}
- private void sendAccountsChangedBroadcast(int userId) {
- Log.i(TAG, "the accounts changed, sending broadcast of "
- + ACCOUNTS_CHANGED_INTENT.getAction());
- mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
- }
-
@Override
public void clearPassword(Account account) {
final int callingUid = Binder.getCallingUid();
@@ -2406,8 +2677,10 @@
checkKeyIntent(
Binder.getCallingUid(),
intent);
- doNotification(mAccounts,
- account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
+ doNotification(
+ mAccounts,
+ account,
+ result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
intent, "android", accounts.userId);
}
}
@@ -3292,7 +3565,8 @@
if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
int userId = UserHandle.getCallingUserId();
- if (!isAccountManagedByCaller(accountType, callingUid, userId) && !isSystemUid(callingUid)) {
+ if (!isAccountManagedByCaller(accountType, callingUid, userId)
+ && !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot edit authenticator properites for account type: %s",
callingUid,
@@ -3335,23 +3609,50 @@
Preconditions.checkArgumentInRange(userId, 0, Integer.MAX_VALUE, "user must be concrete");
try {
- final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
+ int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
return hasAccountAccess(account, packageName, uid);
} catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
return false;
}
}
+ // Returns package with oldest target SDK for given UID.
+ private String getPackageNameForUid(int uid) {
+ String[] packageNames = mPackageManager.getPackagesForUid(uid);
+ if (ArrayUtils.isEmpty(packageNames)) {
+ return null;
+ }
+ // For app op checks related to permissions all packages in the UID
+ // have the same app op state, so doesn't matter which one we pick.
+ // Update: due to visibility changes we want to use package with oldest target SDK,
+
+ String packageName = packageNames[0];
+ int oldestVersion = Integer.MAX_VALUE;
+ for (String name : packageNames) {
+ try {
+ ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(name, 0);
+ if (applicationInfo != null) {
+ int version = applicationInfo.targetSdkVersion;
+ if (version < oldestVersion) {
+ oldestVersion = version;
+ packageName = name;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // skip
+ }
+ }
+ return packageName;
+ }
+
private boolean hasAccountAccess(@NonNull Account account, @Nullable String packageName,
int uid) {
if (packageName == null) {
- String[] packageNames = mPackageManager.getPackagesForUid(uid);
- if (ArrayUtils.isEmpty(packageNames)) {
+ packageName = getPackageNameForUid(uid);
+ if (packageName == null) {
return false;
}
- // For app op checks related to permissions all packages in the UID
- // have the same app op state, so doesn't matter which one we pick.
- packageName = packageNames[0];
}
// Use null token which means any token. Having a token means the package
@@ -3360,27 +3661,12 @@
return true;
}
// In addition to the permissions required to get an auth token we also allow
- // the account to be accessed by holders of the get accounts permissions.
- return checkUidPermission(Manifest.permission.GET_ACCOUNTS_PRIVILEGED, uid, packageName)
- || checkUidPermission(Manifest.permission.GET_ACCOUNTS, uid, packageName);
- }
+ // the account to be accessed by apps for which user or authenticator granted visibility.
- private boolean checkUidPermission(String permission, int uid, String opPackageName) {
- final long identity = Binder.clearCallingIdentity();
- try {
- IPackageManager pm = ActivityThread.getPackageManager();
- if (pm.checkUidPermission(permission, uid) != PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- final int opCode = AppOpsManager.permissionToOpCode(permission);
- return (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOpNoThrow(
- opCode, uid, opPackageName) == AppOpsManager.MODE_ALLOWED);
- } catch (RemoteException e) {
- /* ignore - local call */
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- return false;
+ int visibility = resolveAccountVisibility(account, uid, packageName,
+ getUserAccounts(UserHandle.getUserId(uid)));
+ return (visibility == AccountManager.VISIBILITY_VISIBLE
+ || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
}
@Override
@@ -3476,21 +3762,28 @@
private volatile ArrayList<Account> mAccountsWithFeatures = null;
private volatile int mCurrentAccount = 0;
private final int mCallingUid;
+ private final String mPackageName;
- public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
- IAccountManagerResponse response, String type, String[] features, int callingUid) {
+ public GetAccountsByTypeAndFeatureSession(
+ UserAccounts accounts,
+ IAccountManagerResponse response,
+ String type,
+ String[] features,
+ int callingUid,
+ String packageName) {
super(accounts, response, type, false /* expectActivityLaunch */,
true /* stripAuthTokenFromResult */, null /* accountName */,
false /* authDetailsRequired */);
mCallingUid = callingUid;
mFeatures = features;
+ mPackageName = packageName;
}
@Override
public void run() throws RemoteException {
synchronized (mAccounts.cacheLock) {
mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid,
- null);
+ mPackageName, false /* include managed not visible*/);
}
// check whether each account matches the requested features
mAccountsWithFeatures = new ArrayList<>(mAccountsOfType.length);
@@ -3563,7 +3856,6 @@
}
}
-
@Override
protected String toDebugString(long now) {
return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
@@ -3589,8 +3881,9 @@
return getAccountsInternal(
accounts,
callingUid,
- null, // packageName
- visibleAccountTypes);
+ opPackageName,
+ visibleAccountTypes,
+ false /* includeUserManagedNotVisible */);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -3632,7 +3925,9 @@
if (userAccounts == null) continue;
synchronized (userAccounts.cacheLock) {
Account[] accounts = getAccountsFromCacheLocked(userAccounts, null,
- Binder.getCallingUid(), null);
+ Binder.getCallingUid(),
+ null,
+ false /* incl managed not visible*/);
for (int a = 0; a < accounts.length; a++) {
runningAccounts.add(new AccountAndUser(accounts[a], userId));
}
@@ -3646,7 +3941,9 @@
@Override
@NonNull
public Account[] getAccountsAsUser(String type, int userId, String opPackageName) {
- return getAccountsAsUser(type, userId, null, -1, opPackageName);
+ // Need calling package
+ return getAccountsAsUser(type, userId, null /* callingPackage */, -1, opPackageName,
+ false /* includeUserManagedNotVisible */);
}
@NonNull
@@ -3655,11 +3952,12 @@
int userId,
String callingPackage,
int packageUid,
- String opPackageName) {
+ String opPackageName,
+ boolean includeUserManagedNotVisible) {
int callingUid = Binder.getCallingUid();
// Only allow the system process to read accounts of other users
if (userId != UserHandle.getCallingUserId()
- && callingUid != Process.myUid()
+ && callingUid != Process.SYSTEM_UID
&& mContext.checkCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
@@ -3672,9 +3970,15 @@
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
}
- // If the original calling app was using the framework account chooser activity, we'll
- // be passed in the original caller's uid here, which is what should be used for filtering.
- if (packageUid != -1 && UserHandle.isSameApp(callingUid, Process.myUid())) {
+
+ // If the original calling app was using account choosing activity
+ // provided by the framework or authenticator we'll passing in
+ // the original caller's uid here, which is what should be used for filtering.
+ List<String> managedTypes =
+ getTypesManagedByCaller(callingUid, UserHandle.getUserId(callingUid));
+ if (packageUid != -1 &&
+ ((UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
+ || (type != null && managedTypes.contains(type))))) {
callingUid = packageUid;
opPackageName = callingPackage;
}
@@ -3682,7 +3986,7 @@
opPackageName);
if (visibleAccountTypes.isEmpty()
|| (type != null && !visibleAccountTypes.contains(type))) {
- return new Account[0];
+ return new Account[]{};
} else if (visibleAccountTypes.contains(type)) {
// Prune the list down to just the requested type.
visibleAccountTypes = new ArrayList<>();
@@ -3697,7 +4001,8 @@
accounts,
callingUid,
callingPackage,
- visibleAccountTypes);
+ visibleAccountTypes,
+ includeUserManagedNotVisible);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -3708,12 +4013,14 @@
UserAccounts userAccounts,
int callingUid,
String callingPackage,
- List<String> visibleAccountTypes) {
+ List<String> visibleAccountTypes,
+ boolean includeUserManagedNotVisible) {
synchronized (userAccounts.cacheLock) {
ArrayList<Account> visibleAccounts = new ArrayList<>();
for (String visibleType : visibleAccountTypes) {
Account[] accountsForType = getAccountsFromCacheLocked(
- userAccounts, visibleType, callingUid, callingPackage);
+ userAccounts, visibleType, callingUid, callingPackage,
+ includeUserManagedNotVisible);
if (accountsForType != null) {
visibleAccounts.addAll(Arrays.asList(accountsForType));
}
@@ -3804,29 +4111,34 @@
@NonNull
public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) {
int callingUid = Binder.getCallingUid();
- if (!UserHandle.isSameApp(callingUid, Process.myUid())) {
+ if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)) {
throw new SecurityException("getAccountsForPackage() called from unauthorized uid "
+ callingUid + " with uid=" + uid);
}
return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid,
- opPackageName);
+ opPackageName, true /* includeUserManagedNotVisible */);
}
@Override
@NonNull
public Account[] getAccountsByTypeForPackage(String type, String packageName,
String opPackageName) {
+ int callingUid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
int packageUid = -1;
try {
- packageUid = AppGlobals.getPackageManager().getPackageUid(
- packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES,
- UserHandle.getCallingUserId());
- } catch (RemoteException re) {
+ packageUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+ } catch (NameNotFoundException re) {
Slog.e(TAG, "Couldn't determine the packageUid for " + packageName + re);
return new Account[0];
}
- return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName,
- packageUid, opPackageName);
+ if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
+ && !isAccountManagedByCaller(type, callingUid, userId)) {
+ return new Account[0];
+ }
+
+ return getAccountsAsUser(type, userId,
+ packageName, packageUid, opPackageName, true /* includeUserManagedNotVisible */);
}
@Override
@@ -3866,7 +4178,8 @@
if (features == null || features.length == 0) {
Account[] accounts;
synchronized (userAccounts.cacheLock) {
- accounts = getAccountsFromCacheLocked(userAccounts, type, callingUid, null);
+ accounts = getAccountsFromCacheLocked(
+ userAccounts, type, callingUid, opPackageName, false);
}
Bundle result = new Bundle();
result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
@@ -3878,7 +4191,8 @@
response,
type,
features,
- callingUid).bind();
+ callingUid,
+ opPackageName).bind();
} finally {
restoreCallingIdentity(identityToken);
}
@@ -3897,6 +4211,7 @@
if (Objects.equals(account.getAccessId(), token)) {
// An app just accessed the account. At this point it knows about
// it and there is not need to hide this account from the app.
+ // Do we need to update account visibility here?
if (!hasAccountAccess(account, null, uid)) {
updateAppPermission(account, AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE,
uid, true);
@@ -4423,7 +4738,7 @@
userAccounts.accountsDb.dumpDeAccountsTable(fout);
} else {
Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
- Process.myUid(), null);
+ Process.SYSTEM_UID, null, false);
fout.println("Accounts: " + accounts.length);
for (Account account : accounts) {
fout.println(" " + account);
@@ -4516,6 +4831,24 @@
}
}
+ private boolean isPermittedForPackage(String packageName, int userId, String... permissions) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ for (String perm : permissions) {
+ if (pm.checkPermission(perm, packageName, userId)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ }
+
private boolean isPermitted(String opPackageName, int callingUid, String... permissions) {
for (String perm : permissions) {
if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
@@ -4550,6 +4883,7 @@
userPackageManager = mContext.createPackageContextAsUser(
"android", 0, new UserHandle(callingUserId)).getPackageManager();
} catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
return false;
}
@@ -4563,6 +4897,7 @@
return true;
}
} catch (PackageManager.NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
return false;
}
}
@@ -4617,6 +4952,53 @@
}
}
+ // Method checks visibility for applications targeing API level below {@link
+ // android.os.Build.VERSION_CODES#O},
+ // returns true if the the app has GET_ACCOUNTS or GET_ACCOUNTS_PRIVELEGED permission.
+ private boolean checkGetAccountsPermission(String packageName, int userId) {
+ return isPermittedForPackage(packageName, userId, Manifest.permission.GET_ACCOUNTS,
+ Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
+ }
+
+ private boolean checkReadContactsPermission(String packageName, int userId) {
+ return isPermittedForPackage(packageName, userId, Manifest.permission.READ_CONTACTS);
+ }
+
+ /**
+ * Method checks package uid and signature with Authenticator which manages accountType.
+ *
+ * @return SIGNATURE_CHECK_UID_MATCH for uid match, SIGNATURE_CHECK_MATCH for signature match,
+ * SIGNATURE_CHECK_MISMATCH otherwise.
+ */
+ private int checkPackageSignature(String accountType, int callingUid, int userId) {
+ if (accountType == null) {
+ return SIGNATURE_CHECK_MISMATCH;
+ }
+
+ long identityToken = Binder.clearCallingIdentity();
+ Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> serviceInfos;
+ try {
+ serviceInfos = mAuthenticatorCache.getAllServices(userId);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ // Check for signature match with Authenticator.
+ for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo
+ : serviceInfos) {
+ if (accountType.equals(serviceInfo.type.type)) {
+ if (serviceInfo.uid == callingUid) {
+ return SIGNATURE_CHECK_UID_MATCH;
+ }
+ final int sigChk = mPackageManager.checkSignatures(serviceInfo.uid, callingUid);
+ if (sigChk == PackageManager.SIGNATURE_MATCH) {
+ return SIGNATURE_CHECK_MATCH;
+ }
+ }
+ }
+ return SIGNATURE_CHECK_MISMATCH;
+ }
+
+ // returns true for applications with the same signature as authenticator.
private boolean isAccountManagedByCaller(String accountType, int callingUid, int userId) {
if (accountType == null) {
return false;
@@ -4627,10 +5009,7 @@
private List<String> getTypesVisibleToCaller(int callingUid, int userId,
String opPackageName) {
- boolean isPermitted =
- isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS,
- Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
- return getTypesForCaller(callingUid, userId, isPermitted);
+ return getTypesForCaller(callingUid, userId, true /* isOtherwisePermitted*/);
}
private List<String> getTypesManagedByCaller(int callingUid, int userId) {
@@ -4649,8 +5028,8 @@
}
for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
serviceInfos) {
- final int sigChk = mPackageManager.checkSignatures(serviceInfo.uid, callingUid);
- if (isOtherwisePermitted || sigChk == PackageManager.SIGNATURE_MATCH) {
+ if (isOtherwisePermitted || (mPackageManager.checkSignatures(serviceInfo.uid,
+ callingUid) == PackageManager.SIGNATURE_MATCH)) {
managedAccountTypes.add(serviceInfo.type.type);
}
}
@@ -4732,7 +5111,7 @@
!= 0) {
return true;
}
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
Log.w(TAG, String.format("Could not find package [%s]", name), e);
}
}
@@ -4923,15 +5302,41 @@
return newAccountsForType[oldLength];
}
- private Account[] filterSharedAccounts(UserAccounts userAccounts, Account[] unfiltered,
- int callingUid, String callingPackage) {
+ private Account[] filterAccounts(UserAccounts accounts, Account[] unfiltered, int callingUid,
+ String callingPackage, boolean includeManagedNotVisible) {
+ // filter based on visibility.
+ Map<Account, Integer> firstPass = new HashMap<>();
+ for (Account account : unfiltered) {
+ int visibility =
+ resolveAccountVisibility(account, callingUid, callingPackage, accounts);
+ if ((visibility == AccountManager.VISIBILITY_VISIBLE
+ || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE)
+ || (includeManagedNotVisible
+ && (visibility
+ == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE))) {
+ firstPass.put(account, visibility);
+ }
+ }
+ Map<Account, Integer> secondPass =
+ filterSharedAccounts(accounts, firstPass, callingUid, callingPackage);
+
+ Account[] filtered = new Account[secondPass.size()];
+ filtered = secondPass.keySet().toArray(filtered);
+ return filtered;
+ }
+
+ private Map<Account, Integer> filterSharedAccounts(UserAccounts userAccounts,
+ Map<Account, Integer> unfiltered, int callingUid, String callingPackage) {
+ // first part is to filter shared accounts.
+ // unfiltered type check is not necessary.
if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0
- || callingUid == Process.myUid()) {
+ || callingUid == Process.SYSTEM_UID) {
return unfiltered;
}
UserInfo user = getUserManager().getUserInfo(userAccounts.userId);
if (user != null && user.isRestricted()) {
- String[] packages = mPackageManager.getPackagesForUid(callingUid);
+ String[] packages =
+ mPackageManager.getPackagesForUid(callingUid);
// If any of the packages is a visible listed package, return the full set,
// otherwise return non-shared accounts only.
// This might be a temporary way to specify a visible list
@@ -4942,9 +5347,10 @@
return unfiltered;
}
}
- ArrayList<Account> allowed = new ArrayList<>();
Account[] sharedAccounts = getSharedAccountsAsUser(userAccounts.userId);
- if (sharedAccounts == null || sharedAccounts.length == 0) return unfiltered;
+ if (ArrayUtils.isEmpty(sharedAccounts)) {
+ return unfiltered;
+ }
String requiredAccountType = "";
try {
// If there's an explicit callingPackage specified, check if that package
@@ -4964,11 +5370,14 @@
}
}
}
- } catch (NameNotFoundException nnfe) {
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found " + e.getMessage());
}
- for (Account account : unfiltered) {
+ Map<Account, Integer> filtered = new HashMap<>();
+ for (Map.Entry<Account, Integer> entry : unfiltered.entrySet()) {
+ Account account = entry.getKey();
if (account.type.equals(requiredAccountType)) {
- allowed.add(account);
+ filtered.put(account, entry.getValue());
} else {
boolean found = false;
for (Account shared : sharedAccounts) {
@@ -4978,12 +5387,10 @@
}
}
if (!found) {
- allowed.add(account);
+ filtered.put(account, entry.getValue());
}
}
}
- Account[] filtered = new Account[allowed.size()];
- allowed.toArray(filtered);
return filtered;
} else {
return unfiltered;
@@ -4995,14 +5402,14 @@
* that the package is not allowed to access.
*/
protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
- int callingUid, String callingPackage) {
+ int callingUid, String callingPackage, boolean includeManagedNotVisible) {
if (accountType != null) {
final Account[] accounts = userAccounts.accountCache.get(accountType);
if (accounts == null) {
return EMPTY_ACCOUNT_ARRAY;
} else {
- return filterSharedAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
- callingUid, callingPackage);
+ return filterAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
+ callingUid, callingPackage, includeManagedNotVisible);
}
} else {
int totalLength = 0;
@@ -5019,7 +5426,8 @@
accountsOfType.length);
totalLength += accountsOfType.length;
}
- return filterSharedAccounts(userAccounts, accounts, callingUid, callingPackage);
+ return filterAccounts(userAccounts, accounts, callingUid, callingPackage,
+ includeManagedNotVisible);
}
}
@@ -5247,19 +5655,21 @@
if (userId == 0) {
// Migrate old file, if it exists, to the new location.
// Make sure the new file doesn't already exist. A dummy file could have been
- // accidentally created in the old location, causing the new one to become corrupted
- // as well.
+ // accidentally created in the old location,
+ // causing the new one to become corrupted as well.
File oldFile = new File(systemDir, PRE_N_DATABASE_NAME);
if (oldFile.exists() && !databaseFile.exists()) {
// Check for use directory; create if it doesn't exist, else renameTo will fail
File userDir = Environment.getUserSystemDirectory(userId);
if (!userDir.exists()) {
if (!userDir.mkdirs()) {
- throw new IllegalStateException("User dir cannot be created: " + userDir);
+ throw new IllegalStateException(
+ "User dir cannot be created: " + userDir);
}
}
if (!oldFile.renameTo(databaseFile)) {
- throw new IllegalStateException("User dir cannot be migrated: " + databaseFile);
+ throw new IllegalStateException(
+ "User dir cannot be migrated: " + databaseFile);
}
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountsDb.java b/services/core/java/com/android/server/accounts/AccountsDb.java
index 5ca74711..85e4b5f 100644
--- a/services/core/java/com/android/server/accounts/AccountsDb.java
+++ b/services/core/java/com/android/server/accounts/AccountsDb.java
@@ -355,7 +355,7 @@
boolean deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType) {
SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
return db.delete(CE_TABLE_AUTHTOKENS,
- AUTHTOKENS_ACCOUNTS_ID + "=?" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
+ AUTHTOKENS_ACCOUNTS_ID + "=?" + " AND " + AUTHTOKENS_TYPE + "=?",
new String[]{String.valueOf(accountId), authtokenType}) > 0;
}
@@ -946,12 +946,13 @@
/**
* Returns a map from uid to visibility value.
*/
- Map<Integer, Integer> findAccountVisibilityForAccountId(long accountId) {
+ Map<Integer, Integer> findAllVisibilityValuesForAccount(Account account) {
SQLiteDatabase db = mDeDatabase.getReadableDatabase();
Map<Integer, Integer> result = new HashMap<>();
- final Cursor cursor = db.query(TABLE_VISIBILITY,
- new String[] {VISIBILITY_UID, VISIBILITY_VALUE}, VISIBILITY_ACCOUNTS_ID + "=? ",
- new String[] {String.valueOf(accountId)}, null, null, null);
+ final Cursor cursor =
+ db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_UID, VISIBILITY_VALUE},
+ SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
+ new String[] {account.name, account.type}, null, null, null);
try {
while (cursor.moveToNext()) {
result.put(cursor.getInt(0), cursor.getInt(1));
@@ -1306,4 +1307,4 @@
return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7661127..5655dc5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -305,6 +305,7 @@
}
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
+ int id, Notification notification,
int callingPid, int callingUid, String callingPackage, final int userId)
throws TransactionTooLargeException {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
@@ -324,7 +325,6 @@
callerFg = true;
}
-
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType, callingPackage,
callingPid, callingUid, userId, true, callerFg, false);
@@ -343,19 +343,27 @@
return null;
}
- if (!r.startRequested) {
+ // Non-null notification means this is a start directly into the foreground
+ if (!r.startRequested && notification == null) {
final long token = Binder.clearCallingIdentity();
try {
- // Before going further -- if this app is not allowed to run in the
+ // 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()
+ " from pid=" + callingPid + " uid=" + callingUid
+ " pkg=" + callingPackage);
- return null;
+ if (allowed == ActivityManager.APP_START_MODE_DELAYED) {
+ // In this case we are silently disabling the app, to disrupt as
+ // little as possible existing apps.
+ return null;
+ }
+ // This app knows it is in the new model where this operation is not
+ // allowed, so tell it what has happened.
+ return new ComponentName("?", "app is in background");
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -450,7 +458,11 @@
}
}
- return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
+ ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
+ if (notification != null) {
+ setServiceForegroundInnerLocked(r, callingUid, notification, 0);
+ }
+ return cmp;
}
private boolean requestStartTargetPermissionsReviewIfNeededLocked(ServiceRecord r,
@@ -595,9 +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 (service.appInfo.isEphemeralApp() ||
- mAm.mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
- uid, service.packageName) != AppOpsManager.MODE_ALLOWED) {
+ 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);
@@ -696,50 +708,55 @@
try {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
- if (id != 0) {
- if (notification == null) {
- throw new IllegalArgumentException("null notification");
- }
- if (r.foregroundId != id) {
- cancelForegroudNotificationLocked(r);
- r.foregroundId = id;
- }
- notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
- r.foregroundNoti = notification;
- r.isForeground = true;
- r.postNotification();
- if (r.app != null) {
- updateServiceForegroundLocked(r.app, true);
- }
- getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
- mAm.notifyPackageUse(r.serviceInfo.packageName,
- PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
- } else {
- if (r.isForeground) {
- r.isForeground = false;
- if (r.app != null) {
- mAm.updateLruProcessLocked(r.app, false, null);
- updateServiceForegroundLocked(r.app, true);
- }
- }
- if ((flags & Service.STOP_FOREGROUND_REMOVE) != 0) {
- cancelForegroudNotificationLocked(r);
- r.foregroundId = 0;
- r.foregroundNoti = null;
- } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
- r.stripForegroundServiceFlagFromNotification();
- if ((flags & Service.STOP_FOREGROUND_DETACH) != 0) {
- r.foregroundId = 0;
- r.foregroundNoti = null;
- }
- }
- }
+ setServiceForegroundInnerLocked(r, id, notification, flags);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
+ private void setServiceForegroundInnerLocked(ServiceRecord r, int id,
+ Notification notification, int flags) {
+ if (id != 0) {
+ if (notification == null) {
+ throw new IllegalArgumentException("null notification");
+ }
+ if (r.foregroundId != id) {
+ cancelForegroudNotificationLocked(r);
+ r.foregroundId = id;
+ }
+ notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+ r.foregroundNoti = notification;
+ r.isForeground = true;
+ r.postNotification();
+ if (r.app != null) {
+ updateServiceForegroundLocked(r.app, true);
+ }
+ getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
+ mAm.notifyPackageUse(r.serviceInfo.packageName,
+ PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
+ } else {
+ if (r.isForeground) {
+ r.isForeground = false;
+ if (r.app != null) {
+ mAm.updateLruProcessLocked(r.app, false, null);
+ updateServiceForegroundLocked(r.app, true);
+ }
+ }
+ if ((flags & Service.STOP_FOREGROUND_REMOVE) != 0) {
+ cancelForegroudNotificationLocked(r);
+ r.foregroundId = 0;
+ r.foregroundNoti = null;
+ } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
+ r.stripForegroundServiceFlagFromNotification();
+ if ((flags & Service.STOP_FOREGROUND_DETACH) != 0) {
+ r.foregroundId = 0;
+ r.foregroundNoti = null;
+ }
+ }
+ }
+ }
+
private void cancelForegroudNotificationLocked(ServiceRecord r) {
if (r.foregroundId != 0) {
// First check to see if this app has any other active foreground services
@@ -1444,6 +1461,8 @@
// If service is not currently running, can't yet bind.
return false;
}
+ if (DEBUG_SERVICE) Slog.d(TAG_SERVICE, "requestBind " + i + ": requested=" + i.requested
+ + " rebind=" + rebind);
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
bumpServiceExecutingLocked(r, execInFg, "bind");
@@ -1907,6 +1926,7 @@
mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants,
si.getUriPermissionsLocked());
}
+ // TODO b/34123112; Insert ephemeral grant here
bumpServiceExecutingLocked(r, execInFg, "start");
if (!oomAdjusted) {
oomAdjusted = true;
@@ -2014,6 +2034,7 @@
bumpServiceExecutingLocked(r, false, "bring down unbind");
mAm.updateOomAdjLocked(r.app);
ibr.hasBound = false;
+ ibr.requested = false;
r.app.thread.scheduleUnbindService(r,
ibr.intent.getIntent());
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
new file mode 100644
index 0000000..3c90f93
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -0,0 +1,157 @@
+/*
+ * 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.am;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+
+/**
+ * Settings constants that can modify the activity manager's behavior.
+ */
+final class ActivityManagerConstants extends ContentObserver {
+ // Key names stored in the settings value.
+ private static final String KEY_ENFORCE_BG_CHECK = "enforce_bg_check";
+ private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
+
+ private static final boolean DEFAULT_ENFORCE_BG_CHECK = SystemProperties.getBoolean(
+ "debug.bgcheck", false);
+ private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
+
+ // Enforce background check on apps targeting O?
+ public boolean ENFORCE_BG_CHECK = DEFAULT_ENFORCE_BG_CHECK;
+
+ // Maximum number of cached processes we will allow.
+ public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
+
+ private final ActivityManagerService mService;
+ private ContentResolver mResolver;
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+ private int mOverrideMaxCachedProcesses = -1;
+
+ // The maximum number of cached processes we will keep around before killing them.
+ // NOTE: this constant is *only* a control to not let us go too crazy with
+ // keeping around processes on devices with large amounts of RAM. For devices that
+ // are tighter on RAM, the out of memory killer is responsible for killing background
+ // processes as RAM is needed, and we should *never* be relying on this limit to
+ // kill them. Also note that this limit only applies to cached background processes;
+ // we have no limit on the number of service, visible, foreground, or other such
+ // processes and the number of those processes does not count against the cached
+ // process limit.
+ public int CUR_MAX_CACHED_PROCESSES;
+
+ // The maximum number of empty app processes we will let sit around.
+ public int CUR_MAX_EMPTY_PROCESSES;
+
+ // The number of empty apps at which we don't consider it necessary to do
+ // memory trimming.
+ public int CUR_TRIM_EMPTY_PROCESSES;
+
+ // The number of cached at which we don't consider it necessary to do
+ // memory trimming.
+ public int CUR_TRIM_CACHED_PROCESSES;
+
+ public ActivityManagerConstants(ActivityManagerService service, Handler handler) {
+ super(handler);
+ mService = service;
+ updateMaxCachedProcesses();
+ }
+
+ public void start(ContentResolver resolver) {
+ mResolver = resolver;
+ mResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.ACTIVITY_MANAGER_CONSTANTS), false, this);
+ updateConstants();
+ }
+
+ public void setOverrideMaxCachedProcesses(int value) {
+ mOverrideMaxCachedProcesses = value;
+ updateMaxCachedProcesses();
+ }
+
+ public int getOverrideMaxCachedProcesses() {
+ return mOverrideMaxCachedProcesses;
+ }
+
+ public static int computeEmptyProcessLimit(int totalProcessLimit) {
+ return totalProcessLimit/2;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateConstants();
+ }
+
+ private void updateConstants() {
+ synchronized (mService) {
+ try {
+ mParser.setString(Settings.Global.getString(mResolver,
+ Settings.Global.ACTIVITY_MANAGER_CONSTANTS));
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on
+ // with defaults.
+ Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);
+ }
+
+ ENFORCE_BG_CHECK = mParser.getBoolean(KEY_ENFORCE_BG_CHECK, DEFAULT_ENFORCE_BG_CHECK);
+ MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES,
+ DEFAULT_MAX_CACHED_PROCESSES);
+ updateMaxCachedProcesses();
+ }
+ }
+
+ private void updateMaxCachedProcesses() {
+ CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
+ ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses;
+ CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
+
+ // Note the trim levels do NOT depend on the override process limit, we want
+ // to consider the same level the point where we do trimming regardless of any
+ // additional enforced limit.
+ final int rawMaxEmptyProcesses = computeEmptyProcessLimit(MAX_CACHED_PROCESSES);
+ CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses/2;
+ CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
+ + Settings.Global.ACTIVITY_MANAGER_CONSTANTS + ":");
+
+ pw.print(" "); pw.print(KEY_ENFORCE_BG_CHECK); pw.print("=");
+ pw.println(ENFORCE_BG_CHECK);
+
+ pw.print(" "); pw.print(KEY_MAX_CACHED_PROCESSES); pw.print("=");
+ pw.println(MAX_CACHED_PROCESSES);
+
+ pw.println();
+ if (mOverrideMaxCachedProcesses >= 0) {
+ pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
+ }
+ pw.print(" CUR_MAX_CACHED_PROCESSES="); pw.println(CUR_MAX_CACHED_PROCESSES);
+ pw.print(" CUR_MAX_EMPTY_PROCESSES="); pw.println(CUR_MAX_EMPTY_PROCESSES);
+ pw.print(" CUR_TRIM_EMPTY_PROCESSES="); pw.println(CUR_TRIM_EMPTY_PROCESSES);
+ pw.print(" CUR_TRIM_CACHED_PROCESSES="); pw.println(CUR_TRIM_CACHED_PROCESSES);
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 43bb5ee..88e0d03 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -48,6 +48,7 @@
static final boolean DEBUG_ADD_REMOVE = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_ANR = false;
static final boolean DEBUG_APP = DEBUG_ALL_ACTIVITIES || false;
+ static final boolean DEBUG_BACKGROUND_CHECK = DEBUG_ALL || false;
static final boolean DEBUG_BACKUP = DEBUG_ALL || false;
static final boolean DEBUG_BROADCAST = DEBUG_ALL || false;
static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7fd91cb..140ae9b 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;
@@ -62,6 +63,7 @@
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
@@ -119,6 +121,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.FORCE_NEW_TASK_FLAGS;
+import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
@@ -151,7 +154,6 @@
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityManager.TaskThumbnailInfo;
import android.app.ActivityManagerInternal;
-import android.app.ActivityManagerInternal.PictureInPictureArguments;
import android.app.ActivityManagerInternal.SleepToken;
import android.app.ActivityOptions;
import android.app.ActivityThread;
@@ -182,6 +184,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.PictureInPictureArgs;
import android.app.ProfilerInfo;
import android.app.RemoteAction;
import android.app.WaitResult;
@@ -229,7 +232,6 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
import android.graphics.Point;
import android.graphics.Rect;
import android.location.LocationManager;
@@ -310,7 +312,6 @@
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
-
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.AssistUtils;
@@ -340,7 +341,9 @@
import com.android.server.IntentResolver;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
+import com.android.server.RescueParty;
import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.Watchdog;
@@ -384,10 +387,10 @@
import java.util.concurrent.atomic.AtomicLong;
import dalvik.system.VMRuntime;
+
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
@@ -758,6 +761,18 @@
ProcessRecord mHeavyWeightProcess = null;
/**
+ * Non-persistent app uid whitelist for background restrictions
+ */
+ int[] mBackgroundUidWhitelist = new int[] {
+ Process.BLUETOOTH_UID
+ };
+
+ /**
+ * Broadcast actions that will always be deliverable to unlaunched/background apps
+ */
+ final ArraySet<String> mBackgroundLaunchBroadcasts;
+
+ /**
* All of the processes we currently have running organized by pid.
* The keys are the pid running the application.
*
@@ -1135,6 +1150,16 @@
DeviceIdleController.LocalService mLocalDeviceIdleController;
/**
+ * Set of app ids that are whitelisted for device idle and thus background check.
+ */
+ int[] mDeviceIdleWhitelist = new int[0];
+
+ /**
+ * Set of app ids that are temporarily allowed to escape bg check due to high-pri message
+ */
+ int[] mDeviceIdleTempWhitelist = new int[0];
+
+ /**
* Information about and control over application operations
*/
final AppOpsService mAppOpsService;
@@ -1397,6 +1422,7 @@
ParcelFileDescriptor mProfileFd;
int mSamplingInterval = 0;
boolean mAutoStopProfiler = false;
+ boolean mStreamingOutput = false;
int mProfileType = 0;
final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>();
String mMemWatchDumpProcName;
@@ -1485,9 +1511,6 @@
*/
boolean mBooted = false;
- int mProcessLimit = ProcessList.MAX_CACHED_APPS;
- int mProcessLimitOverride = -1;
-
WindowManagerService mWindowManager;
final ActivityThread mSystemThread;
@@ -1609,6 +1632,8 @@
final MainHandler mHandler;
final UiHandler mUiHandler;
+ final ActivityManagerConstants mConstants;
+
PackageManagerInternal mPackageManagerInt;
// VoiceInteraction session ID that changes for each new request except when
@@ -2590,12 +2615,26 @@
mPermissionReviewRequired = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_permissionReviewRequired);
+ mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.d(TAG, "Enforcing O+ bg restrictions: " + mConstants.ENFORCE_BG_CHECK);
+ StringBuilder sb = new StringBuilder(200);
+ sb.append(" ");
+ for (String a : mBackgroundLaunchBroadcasts) {
+ sb.append(' '); sb.append(a);
+ }
+ Slog.d(TAG, "Background implicit broadcasts:");
+ Slog.d(TAG, sb.toString());
+ }
+
mHandlerThread = new ServiceThread(TAG,
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
mHandlerThread.start();
mHandler = new MainHandler(mHandlerThread.getLooper());
mUiHandler = new UiHandler();
+ mConstants = new ActivityManagerConstants(this, mHandler);
+
/* static; one-time init here */
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
@@ -4190,7 +4229,7 @@
validateUid = mValidateUids.get(item.uid);
if (validateUid == null && change != UidRecord.CHANGE_GONE
&& change != UidRecord.CHANGE_GONE_IDLE) {
- validateUid = new UidRecord(item.uid);
+ validateUid = new UidRecord(item.uid, false);
mValidateUids.put(item.uid, validateUid);
}
}
@@ -4894,7 +4933,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) {
@@ -4907,7 +4946,7 @@
}
synchronized(this) {
- mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, message);
+ mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId, message);
}
}
@@ -5504,6 +5543,7 @@
final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
Uri.fromParts("package", packageName, null));
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
intent.putExtra(Intent.EXTRA_UID, pkgUidF);
intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(pkgUidF));
broadcastIntentInPackage("android", Process.SYSTEM_UID, intent,
@@ -5524,11 +5564,9 @@
removeUriPermissionsForPackageLocked(packageName, userId, true);
}
- // Remove all zen rules created by this package; revoke it's zen access.
+ // Reset notification settings.
INotificationManager inm = NotificationManager.getService();
- inm.removeAutomaticZenRules(packageName);
- inm.setNotificationPolicyAccessGranted(packageName, false);
-
+ inm.clearData(packageName, pkgUidF);
} catch (RemoteException e) {
}
} finally {
@@ -6279,10 +6317,13 @@
}
UidRecord uidRec = mActiveUids.get(proc.uid);
if (uidRec == null) {
- uidRec = new UidRecord(proc.uid);
+ uidRec = new UidRecord(proc.uid, proc.persistent);
// This is the first appearance of the uid, report it now!
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
"Creating new process uid: " + uidRec);
+ if (Arrays.binarySearch(mDeviceIdleTempWhitelist, UserHandle.getAppId(proc.uid)) >= 0) {
+ uidRec.setWhitelist = uidRec.curWhitelist = true;
+ }
mActiveUids.put(proc.uid, uidRec);
noteUidProcessState(uidRec.uid, uidRec.curProcState);
enqueueUidChangeLocked(uidRec, -1, UidRecord.CHANGE_ACTIVE);
@@ -6520,12 +6561,14 @@
ParcelFileDescriptor profileFd = null;
int samplingInterval = 0;
boolean profileAutoStop = false;
+ boolean profileStreamingOutput = false;
if (mProfileApp != null && mProfileApp.equals(processName)) {
mProfileProc = app;
profileFile = mProfileFile;
profileFd = mProfileFd;
samplingInterval = mSamplingInterval;
profileAutoStop = mAutoStopProfiler;
+ profileStreamingOutput = mStreamingOutput;
}
boolean enableTrackAllocation = false;
if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) {
@@ -6555,7 +6598,8 @@
profileFd = profileFd.dup();
}
ProfilerInfo profilerInfo = profileFile == null ? null
- : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
+ : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop,
+ profileStreamingOutput);
// We deprecated Build.SERIAL and only apps that target pre NMR1
// SDK can see it. Since access to the serial is now behind a
@@ -7231,27 +7275,29 @@
/**
* Whitelists {@code targetUid} to temporarily bypass Power Save mode.
- *
- * <p>{@code callerUid} must be allowed to request such whitelist by calling
- * {@link #addTempPowerSaveWhitelistGrantorUid(int)}.
*/
void tempWhitelistAppForPowerSave(int callerPid, int callerUid, int targetUid, long duration) {
if (DEBUG_WHITELISTS) {
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;
+ }
}
}
@@ -7420,8 +7466,7 @@
enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
"setProcessLimit()");
synchronized (this) {
- mProcessLimit = max < 0 ? ProcessList.MAX_CACHED_APPS : max;
- mProcessLimitOverride = max;
+ mConstants.setOverrideMaxCachedProcesses(max);
}
trimApplications();
}
@@ -7429,7 +7474,7 @@
@Override
public int getProcessLimit() {
synchronized (this) {
- return mProcessLimitOverride;
+ return mConstants.getOverrideMaxCachedProcesses();
}
}
@@ -7556,45 +7601,30 @@
}
@Override
- public void enterPictureInPictureMode(IBinder token) {
- enterPictureInPictureMode(token, DEFAULT_DISPLAY, -1f /* aspectRatio */,
- false /* checkAspectRatio */);
- }
-
- @Override
- public void enterPictureInPictureModeWithAspectRatio(IBinder token, float aspectRatio) {
- enterPictureInPictureMode(token, DEFAULT_DISPLAY, aspectRatio, true /* checkAspectRatio */);
- }
-
- @Override
- public void enterPictureInPictureModeOnMoveToBackground(IBinder token,
- boolean enterPictureInPictureOnMoveToBg) {
+ public boolean enterPictureInPictureMode(IBinder token, final PictureInPictureArgs args) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
- final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "enterPictureInPictureModeOnMoveToBackground", token, -1f /* aspectRatio */,
- false /* checkAspectRatio */, false /* checkActivityVisibility */);
+ final ActivityRecord r = ensureValidPictureInPictureActivityArgsLocked(
+ "enterPictureInPictureMode", token, args);
- r.supportsPipOnMoveToBackground = enterPictureInPictureOnMoveToBg;
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
+ // Activity supports picture-in-picture, now check that we can enter PiP at this
+ // point, if it is
+ if (!r.checkEnterPictureInPictureState("enterPictureInPictureMode")) {
+ return false;
+ }
- private void enterPictureInPictureMode(IBinder token, int displayId, float aspectRatio,
- boolean checkAspectRatio) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized(this) {
- final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "enterPictureInPictureMode", token, aspectRatio, checkAspectRatio,
- true /* checkActivityVisibility */);
final Runnable enterPipRunnable = () -> {
- r.pictureInPictureArgs.aspectRatio = aspectRatio;
- enterPictureInPictureModeLocked(r, displayId, r.pictureInPictureArgs,
- true /* moveHomeStackToFront */, "enterPictureInPictureMode");
+ // Only update the saved args from the args that are set
+ r.pictureInPictureArgs.copyOnlySet(args);
+ final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
+ final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
+ final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
+ ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, aspectRatio)
+ : mWindowManager.getPictureInPictureDefaultBounds(DEFAULT_DISPLAY);
+ mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
+ bounds, true /* moveHomeStackToFront */);
+ mStackSupervisor.getStack(PINNED_STACK_ID).setPictureInPictureActions(actions);
};
if (isKeyguardLocked()) {
@@ -7625,35 +7655,7 @@
// Enter picture in picture immediately otherwise
enterPipRunnable.run();
}
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- void enterPictureInPictureModeLocked(ActivityRecord r, int displayId,
- PictureInPictureArguments pipArgs, boolean moveHomeStackToFront, String reason) {
- final Rect bounds = isValidPictureInPictureAspectRatio(pipArgs.aspectRatio)
- ? mWindowManager.getPictureInPictureBounds(displayId, pipArgs.aspectRatio)
- : mWindowManager.getPictureInPictureDefaultBounds(displayId);
- mStackSupervisor.moveActivityToPinnedStackLocked(r, reason, bounds, moveHomeStackToFront);
- mWindowManager.setPictureInPictureActions(pipArgs.userActions);
- }
-
- @Override
- public void setPictureInPictureAspectRatio(IBinder token, float aspectRatio) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized(this) {
- final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "setPictureInPictureAspectRatio", token, aspectRatio,
- true /* checkAspectRatio */, false /* checkActivityVisibility */);
-
- r.pictureInPictureArgs.aspectRatio = aspectRatio;
- if (r.getStack().getStackId() == PINNED_STACK_ID) {
- // If the activity is already in picture-in-picture, update the pinned stack now
- mWindowManager.setPictureInPictureAspectRatio(aspectRatio);
- }
+ return true;
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7661,26 +7663,20 @@
}
@Override
- public void setPictureInPictureActions(IBinder token, ParceledListSlice actionsList) {
+ public void setPictureInPictureArgs(IBinder token, final PictureInPictureArgs args) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
- final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "setPictureInPictureActions", token, -1 /* aspectRatio */,
- false /* checkAspectRatio */, false /* checkActivityVisibility */);
+ final ActivityRecord r = ensureValidPictureInPictureActivityArgsLocked(
+ "setPictureInPictureArgs", token, args);
- final List<RemoteAction> actions = actionsList.getList();
- if (actions.size() > ActivityManager.getMaxNumPictureInPictureActions()) {
- throw new IllegalArgumentException("setPictureInPictureActions: Invalid number"
- + " of picture-in-picture actions. Only a maximum of "
- + ActivityManager.getMaxNumPictureInPictureActions()
- + " actions allowed");
- }
-
- r.pictureInPictureArgs.userActions = actions;
- if (r.getStack().getStackId() == PINNED_STACK_ID) {
+ // Only update the saved args from the args that are set
+ r.pictureInPictureArgs.copyOnlySet(args);
+ 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.setPictureInPictureActions(actions);
+ stack.setPictureInPictureAspectRatio(r.pictureInPictureArgs.getAspectRatio());
+ stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions());
}
}
} finally {
@@ -7696,14 +7692,10 @@
* Checks the state of the system and the activity associated with the given {@param token} to
* verify that picture-in-picture is supported for that activity.
*
- * @param checkAspectRatio whether or not to check {@param aspectRatio} is within a valid range
- * @param checkActivityVisibility whether or not to enforce that the activity is currently
- * visible
- *
* @return the activity record for the given {@param token} if all the checks pass.
*/
- private ActivityRecord ensureValidPictureInPictureActivityLocked(String caller, IBinder token,
- float aspectRatio, boolean checkAspectRatio, boolean checkActivityVisibility) {
+ private ActivityRecord ensureValidPictureInPictureActivityArgsLocked(String caller,
+ IBinder token, PictureInPictureArgs args) {
if (!mSupportsPictureInPicture) {
throw new IllegalStateException(caller
+ ": Device doesn't support picture-in-picture mode.");
@@ -7715,10 +7707,9 @@
+ ": Can't find activity for token=" + token);
}
- if (!r.canEnterPictureInPicture(checkActivityVisibility)) {
- throw new IllegalArgumentException(caller
- + ": Current activity does not support picture-in-picture or is not "
- + "visible r=" + r);
+ if (!r.supportsPictureInPicture()) {
+ throw new IllegalStateException(caller
+ + ": Current activity does not support picture-in-picture.");
}
if (r.getStack().isHomeStack()) {
@@ -7726,12 +7717,20 @@
+ ": Activities on the home stack not supported");
}
- if (checkAspectRatio && !isValidPictureInPictureAspectRatio(aspectRatio)) {
+ if (args.hasSetAspectRatio()
+ && !isValidPictureInPictureAspectRatio(args.getAspectRatio())) {
throw new IllegalArgumentException(String.format(caller
+ ": Aspect ratio is too extreme (must be between %f and %f).",
mMinPipAspectRatio, mMaxPipAspectRatio));
}
+ if (args.hasSetActions()
+ && args.getActions().size() > ActivityManager.getMaxNumPictureInPictureActions()) {
+ throw new IllegalArgumentException(String.format(caller + ": Invalid number of"
+ + "picture-in-picture actions. Only a maximum of %d actions allowed",
+ ActivityManager.getMaxNumPictureInPictureActions()));
+ }
+
return r;
}
@@ -8020,15 +8019,80 @@
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;
}
}
- int checkAllowBackgroundLocked(int uid, String packageName, int callingPid,
- boolean alwaysRestrict) {
+ // Unified app-op and target sdk check
+ int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
+ // Apps that target O+ are always subject to background check
+ if (mConstants.ENFORCE_BG_CHECK && packageTargetSdk >= 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);
+ 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, int packageTargetSdk) {
+ // Persistent app? NB: expects that persistent uids are always active.
+ final UidRecord uidRec = mActiveUids.get(uid);
+ if (uidRec != null && uidRec.persistent) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "App " + uid + "/" + packageName
+ + " is persistent; not restricted in background");
+ }
+ return ActivityManager.APP_START_MODE_NORMAL;
+ }
+
+ // Non-persistent but background whitelisted?
+ if (uidOnBackgroundWhitelist(uid)) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "App " + uid + "/" + packageName
+ + " on background whitelist; not restricted in background");
+ }
+ return ActivityManager.APP_START_MODE_NORMAL;
+ }
+
+ // Is this app on the battery whitelist?
+ if (isOnDeviceIdleWhitelistLocked(uid)) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "App " + uid + "/" + packageName
+ + " on idle whitelist; not restricted in background");
+ }
+ return ActivityManager.APP_START_MODE_NORMAL;
+ }
+
+ // None of the service-policy criteria apply, so we apply the common criteria
+ return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
+ }
+
+ 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="
+ + (uidRec != null ? uidRec.idle : false));
if (uidRec == null || alwaysRestrict || uidRec.idle) {
boolean ephemeral;
if (uidRec == null) {
@@ -8042,27 +8106,49 @@
// We are hard-core about ephemeral apps not running in the background.
return ActivityManager.APP_START_MODE_DISABLED;
} else {
- 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, 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;
+ }
}
}
- if (mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, uid,
- packageName) != AppOpsManager.MODE_ALLOWED) {
- return ActivityManager.APP_START_MODE_DELAYED;
- }
+ return startMode;
}
}
return ActivityManager.APP_START_MODE_NORMAL;
}
+ boolean isOnDeviceIdleWhitelistLocked(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ return Arrays.binarySearch(mDeviceIdleWhitelist, appId) >= 0
+ || Arrays.binarySearch(mDeviceIdleTempWhitelist, appId) >= 0;
+ }
+
private ProviderInfo getProviderInfoLocked(String authority, int userHandle, int pmFlags) {
ProviderInfo pi = null;
ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle);
@@ -8079,6 +8165,12 @@
return pi;
}
+ void grantEphemeralAccessLocked(int userId, Intent intent,
+ int targetAppId, int ephemeralAppId) {
+ getPackageManagerInternalLocked().
+ grantEphemeralAccess(userId, intent, targetAppId, ephemeralAppId);
+ }
+
private UriPermission findUriPermissionLocked(int targetUid, GrantUri grantUri) {
final ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
if (targetUris != null) {
@@ -9242,7 +9334,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;
@@ -9659,15 +9751,17 @@
enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
+ final TaskRecord task;
synchronized (this) {
- final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(
+ task = mStackSupervisor.anyTaskForIdLocked(
taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
if (task == null) {
Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");
return null;
}
- return task.getSnapshot();
}
+ // Don't call this while holding the lock as this operation might hit the disk.
+ return task.getSnapshot();
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10120,7 +10214,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.");
@@ -11458,9 +11554,14 @@
mSystemThread.installSystemProviders(providers);
}
+ mConstants.start(mContext.getContentResolver());
mCoreSettingsObserver = new CoreSettingsObserver(this);
mFontScaleSettingObserver = new FontScaleSettingObserver();
+ // Now that the settings provider is published we can consider sending
+ // in a rescue party.
+ RescueParty.onSettingsProviderPublished(mContext);
+
//mUsageStatsService.monitorPackages();
}
@@ -11651,6 +11752,16 @@
return r;
}
+ private boolean uidOnBackgroundWhitelist(final int uid) {
+ final int N = mBackgroundUidWhitelist.length;
+ for (int i = 0; i < N; i++) {
+ if (uid == mBackgroundUidWhitelist[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
String abiOverride) {
ProcessRecord app;
@@ -11929,14 +12040,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);
@@ -11945,12 +12054,12 @@
}
@Override
- public void startConfirmDeviceCredentialIntent(Intent intent) {
+ public void startConfirmDeviceCredentialIntent(Intent intent, Bundle options) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "startConfirmDeviceCredentialIntent");
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
- mActivityStarter.startConfirmCredentialIntent(intent);
+ mActivityStarter.startConfirmCredentialIntent(intent, options);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -12089,6 +12198,7 @@
mProfileFd = profilerInfo.profileFd;
mSamplingInterval = profilerInfo.samplingInterval;
mAutoStopProfiler = profilerInfo.autoStopProfiler;
+ mStreamingOutput = profilerInfo.streamingOutput;
mProfileType = 0;
}
}
@@ -13289,13 +13399,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
@@ -14381,6 +14490,10 @@
synchronized (this) {
dumpAssociationsLocked(fd, pw, args, opti, true, dumpClient, dumpPackage);
}
+ } else if ("settings".equals(cmd)) {
+ synchronized (this) {
+ mConstants.dump(pw);
+ }
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
ActiveServices.ServiceDumper dumper;
@@ -14421,6 +14534,11 @@
} else if (dumpClient) {
ActiveServices.ServiceDumper sdumper;
synchronized (this) {
+ mConstants.dump(pw);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
@@ -14479,6 +14597,11 @@
} else {
synchronized (this) {
+ mConstants.dump(pw);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
@@ -14977,6 +15100,8 @@
}
}
}
+ pw.println(" mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
+ pw.println(" mDeviceIdleTempWhitelist=" + Arrays.toString(mDeviceIdleTempWhitelist));
}
if (dumpPackage == null) {
pw.println(" mWakefulness="
@@ -15052,7 +15177,7 @@
pw.println(" mProfileApp=" + mProfileApp + " mProfileProc=" + mProfileProc);
pw.println(" mProfileFile=" + mProfileFile + " mProfileFd=" + mProfileFd);
pw.println(" mSamplingInterval=" + mSamplingInterval + " mAutoStopProfiler="
- + mAutoStopProfiler);
+ + mAutoStopProfiler + " mStreamingOutput=" + mStreamingOutput);
pw.println(" mProfileType=" + mProfileType);
}
}
@@ -17284,7 +17409,8 @@
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
- String resolvedType, String callingPackage, int userId)
+ String resolvedType, int id, Notification notification,
+ String callingPackage, int userId)
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
// Refuse possible leaked file descriptors
@@ -17303,7 +17429,8 @@
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
ComponentName res = mServices.startServiceLocked(caller, service,
- resolvedType, callingPid, callingUid, callingPackage, userId);
+ resolvedType, id, notification,
+ callingPid, callingUid, callingPackage, userId);
Binder.restoreCallingIdentity(origId);
return res;
}
@@ -17317,7 +17444,7 @@
"startServiceInPackage: " + service + " type=" + resolvedType);
final long origId = Binder.clearCallingIdentity();
ComponentName res = mServices.startServiceLocked(null, service,
- resolvedType, -1, uid, callingPackage, userId);
+ resolvedType, 0, null, -1, uid, callingPackage, userId);
Binder.restoreCallingIdentity(origId);
return res;
}
@@ -17637,6 +17764,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) {
@@ -18176,6 +18304,13 @@
}
if (action != null) {
+ if (mBackgroundLaunchBroadcasts.contains(action)) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
+ }
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ }
+
switch (action) {
case Intent.ACTION_UID_REMOVED:
case Intent.ACTION_PACKAGE_REMOVED:
@@ -19300,7 +19435,8 @@
UserHandle.USER_ALL);
if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
if (initLocale || !mProcessesReady) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
@@ -19467,10 +19603,15 @@
/** 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 newStackBounds = new Rect();
+ final Rect newTempTaskBounds = new Rect();
+ mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration(newStackBounds,
+ newTempTaskBounds);
mStackSupervisor.resizeStackLocked(
- stackId, newBounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
- false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);
+ stackId, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */,
+ !newTempTaskBounds.isEmpty() ? newTempTaskBounds : null /* tempTaskBounds */,
+ null /* tempTaskInsetBounds */, false /* preserveWindows */,
+ false /* allowResizeInDockedMode */, deferResume);
}
/**
@@ -19488,7 +19629,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;
}
@@ -21266,17 +21408,8 @@
mNewNumServiceProcs = 0;
mNewNumAServiceProcs = 0;
- final int emptyProcessLimit;
- final int cachedProcessLimit;
- if (mProcessLimit <= 0) {
- emptyProcessLimit = cachedProcessLimit = 0;
- } else if (mProcessLimit == 1) {
- emptyProcessLimit = 1;
- cachedProcessLimit = 0;
- } else {
- emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit);
- cachedProcessLimit = mProcessLimit - emptyProcessLimit;
- }
+ final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
+ final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit;
// Let's determine how many processes we have running vs.
// how many slots we have for background processes; we may want
@@ -21384,7 +21517,7 @@
}
break;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
- if (numEmpty > ProcessList.TRIM_EMPTY_APPS
+ if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
&& app.lastActivityTime < oldTime) {
app.kill("empty for "
+ ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
@@ -21437,8 +21570,8 @@
// memory they want.
final int numCachedAndEmpty = numCached + numEmpty;
int memFactor;
- if (numCached <= ProcessList.TRIM_CACHED_APPS
- && numEmpty <= ProcessList.TRIM_EMPTY_APPS) {
+ if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES
+ && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) {
if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
} else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
@@ -21623,12 +21756,18 @@
for (int i=mActiveUids.size()-1; i>=0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
int uidChange = UidRecord.CHANGE_PROCSTATE;
- if (uidRec.setProcState != uidRec.curProcState) {
+ if (uidRec.setProcState != uidRec.curProcState
+ || uidRec.setWhitelist != uidRec.curWhitelist) {
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
"Changes in " + uidRec + ": proc state from " + uidRec.setProcState
- + " to " + uidRec.curProcState);
- if (ActivityManager.isProcStateBackground(uidRec.curProcState)) {
- if (!ActivityManager.isProcStateBackground(uidRec.setProcState)) {
+ + " to " + uidRec.curProcState + ", whitelist from " + uidRec.setWhitelist
+ + " to " + uidRec.curWhitelist);
+ if (ActivityManager.isProcStateBackground(uidRec.curProcState)
+ && !uidRec.curWhitelist) {
+ // UID is now in the background (and not on the temp whitelist). Was it
+ // previously in the foreground (or on the temp whitelist)?
+ if (!ActivityManager.isProcStateBackground(uidRec.setProcState)
+ || uidRec.setWhitelist) {
uidRec.lastBackgroundTime = nowElapsed;
if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {
// Note: the background settle time is in elapsed realtime, while
@@ -21646,6 +21785,7 @@
uidRec.lastBackgroundTime = 0;
}
uidRec.setProcState = uidRec.curProcState;
+ uidRec.setWhitelist = uidRec.curWhitelist;
enqueueUidChangeLocked(uidRec, -1, uidChange);
noteUidProcessState(uidRec.uid, uidRec.curProcState);
}
@@ -21789,6 +21929,20 @@
enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
}
+ final void setAppIdTempWhitelistStateLocked(int appId, boolean onWhitelist) {
+ boolean changed = false;
+ for (int i=mActiveUids.size()-1; i>=0; i--) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (UserHandle.getAppId(uidRec.uid) == appId && uidRec.curWhitelist != onWhitelist) {
+ uidRec.curWhitelist = onWhitelist;
+ changed = true;
+ }
+ }
+ if (changed) {
+ updateOomAdjLocked();
+ }
+ }
+
final void trimApplications() {
synchronized (this) {
int i;
@@ -21877,6 +22031,7 @@
mProfileFile = null;
mProfileType = 0;
mAutoStopProfiler = false;
+ mStreamingOutput = false;
mSamplingInterval = 0;
}
@@ -22448,6 +22603,13 @@
}
@Override
+ public IBinder getTopVisibleActivity(int uid) {
+ synchronized (ActivityManagerService.this) {
+ return mStackSupervisor.getTopVisibleActivity(uid);
+ }
+ }
+
+ @Override
public void notifyDockedStackMinimizedChanged(boolean minimized) {
synchronized (ActivityManagerService.this) {
mStackSupervisor.setDockedStackMinimized(minimized);
@@ -22494,6 +22656,21 @@
}
@Override
+ public void setDeviceIdleWhitelist(int[] appids) {
+ synchronized (ActivityManagerService.this) {
+ mDeviceIdleWhitelist = appids;
+ }
+ }
+
+ @Override
+ public void updateDeviceIdleTempWhitelist(int[] appids, int changingAppId, boolean adding) {
+ synchronized (ActivityManagerService.this) {
+ mDeviceIdleTempWhitelist = appids;
+ setAppIdTempWhitelistStateLocked(changingAppId, adding);
+ }
+ }
+
+ @Override
public void updatePersistentConfigurationForUser(@NonNull Configuration values,
int userId) {
Preconditions.checkNotNull(values, "Configuration must not be null");
@@ -22776,6 +22953,52 @@
return mUserController.restartUser(userId, /* foreground */ false);
}
+ @Override
+ public void scheduleApplicationInfoChanged(List<String> packageNames, int userId) {
+ enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
+ "scheduleApplicationInfoChanged()");
+
+ synchronized (this) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ updateApplicationInfoLocked(packageNames, userId);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) {
+ final boolean updateFrameworkRes = packagesToUpdate.contains("android");
+ for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
+ final ProcessRecord app = mLruProcesses.get(i);
+ if (app.thread == null) {
+ continue;
+ }
+
+ if (userId != UserHandle.USER_ALL && app.userId != userId) {
+ continue;
+ }
+
+ final int packageCount = app.pkgList.size();
+ for (int j = 0; j < packageCount; j++) {
+ final String packageName = app.pkgList.keyAt(j);
+ if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
+ try {
+ final ApplicationInfo ai = mPackageManagerInt.getApplicationInfo(
+ packageName, app.userId);
+ if (ai != null) {
+ app.thread.scheduleApplicationInfoChanged(ai);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
+ packageName, app));
+ }
+ }
+ }
+ }
+ }
+
/**
* Attach an agent to the specified process (proces name or PID)
*/
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 29a4781..8c34776 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -115,6 +115,7 @@
private String mProfileFile;
private int mSamplingInterval;
private boolean mAutoStop;
+ private boolean mStreaming; // Streaming the profiling output to a file.
private int mDisplayId;
private int mStackId;
@@ -167,11 +168,13 @@
return runBugReport(pw);
case "force-stop":
return runForceStop(pw);
+ case "crash":
+ return runCrash(pw);
case "kill":
return runKill(pw);
case "kill-all":
return runKillAll(pw);
- case "make-idle":
+ case "make-uid-idle":
return runMakeIdle(pw);
case "monitor":
return runMonitor(pw);
@@ -235,6 +238,8 @@
return runSupportsMultiwindow(pw);
case "supports-split-screen-multi-window":
return runSupportsSplitScreenMultiwindow(pw);
+ case "update-appinfo":
+ return runUpdateApplicationInfo(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -252,6 +257,7 @@
mProfileFile = null;
mSamplingInterval = 0;
mAutoStop = false;
+ mStreaming = false;
mUserId = defUser;
mDisplayId = INVALID_DISPLAY;
mStackId = INVALID_STACK_ID;
@@ -273,6 +279,8 @@
mAutoStop = false;
} else if (opt.equals("--sampling")) {
mSamplingInterval = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("--streaming")) {
+ mStreaming = true;
} else if (opt.equals("-R")) {
mRepeat = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("-S")) {
@@ -350,7 +358,8 @@
if (fd == null) {
return 1;
}
- profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
+ profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop,
+ mStreaming);
}
pw.println("Starting: " + intent);
@@ -490,7 +499,7 @@
pw.println("Starting service: " + intent);
pw.flush();
ComponentName cn = mInterface.startService(null, intent, intent.getType(),
- SHELL_PACKAGE_NAME, mUserId);
+ -1, null, SHELL_PACKAGE_NAME, mUserId);
if (cn == null) {
err.println("Error: Not found; no service started.");
return -1;
@@ -500,6 +509,9 @@
} else if (cn.getPackageName().equals("!!")) {
err.println("Error: " + cn.getClassName());
return -1;
+ } else if (cn.getPackageName().equals("?")) {
+ err.println("Error: " + cn.getClassName());
+ return -1;
}
return 0;
}
@@ -652,6 +664,7 @@
int userId = UserHandle.USER_CURRENT;
int profileType = 0;
mSamplingInterval = 0;
+ mStreaming = false;
String process = null;
@@ -665,6 +678,8 @@
userId = UserHandle.parseUserArg(getNextArgRequired());
} else if (opt.equals("--wall")) {
wall = true;
+ } else if (opt.equals("--streaming")) {
+ mStreaming = true;
} else if (opt.equals("--sampling")) {
mSamplingInterval = Integer.parseInt(getNextArgRequired());
} else {
@@ -709,7 +724,7 @@
if (fd == null) {
return -1;
}
- profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false);
+ profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming);
}
try {
@@ -846,6 +861,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;
@@ -2323,6 +2364,19 @@
return 0;
}
+ int runUpdateApplicationInfo(PrintWriter pw) throws RemoteException {
+ int userid = UserHandle.parseUserArg(getNextArgRequired());
+ ArrayList<String> packages = new ArrayList<>();
+ packages.add(getNextArgRequired());
+ String packageName;
+ while ((packageName = getNextArg()) != null) {
+ packages.add(packageName);
+ }
+ mInternal.scheduleApplicationInfoChanged(packages, userid);
+ pw.println("Packages updated with most recent ApplicationInfos.");
+ return 0;
+ }
+
private Resources getResources(PrintWriter pw) throws RemoteException {
// system resources does not contain all the device configuration, construct it manually.
Configuration config = mInterface.getConfiguration();
@@ -2360,6 +2414,7 @@
pw.println(" provider [COMP_SPEC]: provider client-side state");
pw.println(" s[ervices] [COMP_SPEC ...]: service state");
pw.println(" as[sociations]: tracked app associations");
+ pw.println(" settings: currently applied config settings");
pw.println(" service [COMP_SPEC]: service client-side state");
pw.println(" package [PACKAGE_NAME]: all state related to given package");
pw.println(" all: dump all activities");
@@ -2378,7 +2433,7 @@
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" start-activity [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]");
- pw.println(" [--sampling INTERVAL] [-R COUNT] [-S]");
+ pw.println(" [--sampling INTERVAL] [--streaming] [-R COUNT] [-S]");
pw.println(" [--track-allocation] [--user <USER_ID> | current] <INTENT>");
pw.println(" Start an Activity. Options are:");
pw.println(" -D: enable debugging");
@@ -2387,6 +2442,8 @@
pw.println(" --start-profiler <FILE>: start profiler and send results to <FILE>");
pw.println(" --sampling INTERVAL: use sample profiling with INTERVAL microseconds");
pw.println(" between samples (use with --start-profiler)");
+ pw.println(" --streaming: stream the profiling output to the specified file");
+ pw.println(" (use with --start-profiler)");
pw.println(" -P <FILE>: like above, but profiling stops when app goes idle");
pw.println(" -R: repeat the activity launch <COUNT> times. Prior to each repeat,");
pw.println(" the top activity will be finished.");
@@ -2433,11 +2490,14 @@
pw.println(" stop: stop tracing IPC transactions and dump the results to file.");
pw.println(" --dump-file <FILE>: Specify the file the trace should be dumped to.");
pw.println(" profile [start|stop] [--user <USER_ID> current] [--sampling INTERVAL]");
- pw.println(" <PROCESS> <FILE>");
+ pw.println(" [--streaming] <PROCESS> <FILE>");
pw.println(" Start and stop profiler on a process. The given <PROCESS> argument");
pw.println(" may be either a process name or pid. Options are:");
pw.println(" --user <USER_ID> | current: When supplying a process name,");
pw.println(" specify user of process to profile; uses current user if not specified.");
+ pw.println(" --sampling INTERVAL: use sample profiling with INTERVAL microseconds");
+ pw.println(" between samples");
+ pw.println(" --streaming: stream the profiling output to the specified file");
pw.println(" dumpheap [--user <USER_ID> current] [-n] <PROCESS> <FILE>");
pw.println(" Dump the heap of a process. The given <PROCESS> argument may");
pw.println(" be either a process name or pid. Options are:");
@@ -2462,10 +2522,15 @@
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");
pw.println(" Kill all processes that are safe to kill (cached, etc).");
+ pw.println(" make-uid-idle [--user <USER_ID> | all | current] <PACKAGE>");
+ pw.println(" If the given application's uid is in the background and waiting to");
+ pw.println(" become idle (not allowing background services), do that now.");
pw.println(" monitor [--gdb <port>]");
pw.println(" Start monitoring for crashes or ANRs.");
pw.println(" --gdb: start gdbserv on the given port at crash/ANR");
@@ -2584,6 +2649,9 @@
pw.println(" Test command for sizing <TASK_ID> by <STEP_SIZE>");
pw.println(" increments within the screen applying the optional [DELAY_MS] between");
pw.println(" each step.");
+ pw.println(" update-appinfo <USER_ID> <PACKAGE_NAME> [<PACKAGE_NAME>...]");
+ pw.println(" Update the ApplicationInfo objects of the listed packages for <USER_ID>");
+ pw.println(" without restarting any processes.");
pw.println(" write");
pw.println(" Write all pending state to storage.");
pw.println();
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 a2fb9f9..8ca77c5 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;
@@ -63,9 +64,9 @@
import android.annotation.NonNull;
import android.app.ActivityManager.TaskDescription;
-import android.app.ActivityManagerInternal.PictureInPictureArguments;
import android.app.ActivityOptions;
import android.app.PendingIntent;
+import android.app.PictureInPictureArgs;
import android.app.ResultInfo;
import android.content.ComponentName;
import android.content.Intent;
@@ -149,6 +150,7 @@
AppWindowContainerController mWindowContainerController;
final ActivityInfo info; // all about me
final ApplicationInfo appInfo; // information about activity's app
+ final int launchedFromPid; // always the pid who started the activity.
final int launchedFromUid; // always the uid who started the activity.
final String launchedFromPackage; // always the package who started the activity.
final int userId; // Which user is this running for?
@@ -229,13 +231,10 @@
boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
boolean immersive; // immersive mode (don't interrupt if possible)
boolean forceNewConfig; // force re-create with new config next time
- boolean supportsPipOnMoveToBackground; // Supports automatically entering picture-in-picture
- // when this activity is hidden. This flag is requested by the activity.
- private boolean enterPipOnMoveToBackground; // Flag to enter picture in picture when this
- // activity is made invisible. This flag is set specifically when another task is being
- // launched or moved to the front which may cause this activity to try and enter PiP
- // when it is next made invisible.
- PictureInPictureArguments pictureInPictureArgs = new PictureInPictureArguments(); // The PiP
+ boolean supportsPictureInPictureWhilePausing; // This flag is set by the system to indicate
+ // that the activity can enter picture in picture while pausing (ie. only when another
+ // task is brought to front or started)
+ PictureInPictureArgs pictureInPictureArgs = new PictureInPictureArgs(); // The PiP
// arguments used when deferring the entering of picture-in-picture.
int launchCount; // count of launches since last state
long lastLaunchTime; // time of last launch of this activity
@@ -452,13 +451,10 @@
}
if (info != null) {
pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
+ pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
}
- if (supportsPipOnMoveToBackground) {
- pw.println(prefix + "supportsPipOnMoveToBackground=1");
- pw.println(prefix + "enterPipOnMoveToBackground=" +
- (enterPipOnMoveToBackground ? 1 : 0));
- pictureInPictureArgs.dump(pw, prefix);
- }
+ pw.println(prefix + "supportsPictureInPictureWhilePausing: "
+ + supportsPictureInPictureWhilePausing);
}
private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
@@ -595,7 +591,7 @@
return ResolverActivity.class.getName().equals(realActivity.getClassName());
}
- ActivityRecord(ActivityManagerService _service, ProcessRecord _caller,
+ ActivityRecord(ActivityManagerService _service, ProcessRecord _caller, int _launchedFromPid,
int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
ActivityInfo aInfo, Configuration _configuration,
ActivityRecord _resultTo, String _resultWho, int _reqCode,
@@ -605,6 +601,7 @@
service = _service;
appToken = new Token(this);
info = aInfo;
+ launchedFromPid = _launchedFromPid;
launchedFromUid = _launchedFromUid;
launchedFromPackage = _launchedFromPackage;
userId = UserHandle.getUserId(aInfo.applicationInfo.uid);
@@ -819,23 +816,6 @@
}
/**
- * If this activity has requested that it auto-enter picture-in-picture and we can actually do
- * this, then mark it to enter picture in picture at that point.
- */
- void setEnterPipOnMoveToBackground(boolean enterPipOnInvisible) {
- if (supportsPipOnMoveToBackground) {
- enterPipOnMoveToBackground = enterPipOnInvisible;
- }
- }
-
- /**
- * @return whether to enter PiP when this activity is made invisible.
- */
- public boolean shouldEnterPictureInPictureOnInvisible() {
- return enterPipOnMoveToBackground;
- }
-
- /**
* @return Stack value from current task, null if there is no task.
*/
ActivityStack getStack() {
@@ -897,49 +877,96 @@
}
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 is currently allowed to enter PIP, if
- * {@param checkActivityVisibility} is set, then the current activity visibility is taken into
- * account.
+ * @return whether this activity supports split-screen multi-window and can be put in the docked
+ * stack.
*/
- boolean canEnterPictureInPicture(boolean checkActivityVisibility) {
- if (!checkActivityVisibility) {
- return supportsPictureInPicture();
- }
-
- if (supportsPictureInPicture()) {
- switch (state) {
- case RESUMED:
- case PAUSING:
- case PAUSED:
- return true;
- }
- }
- return false;
+ 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();
}
- boolean canGoInDockedStack() {
- return !isHomeActivity() && isResizeableOrForced();
+ /**
+ * @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);
+ }
+
+ /**
+ * @return whether this activity is currently allowed to enter PIP, throwing an exception if
+ * the activity is not currently visible.
+ */
+ boolean checkEnterPictureInPictureState(String caller) {
+ boolean isKeyguardLocked = service.isKeyguardLocked();
+ boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
+ switch (state) {
+ case RESUMED:
+ // When visible, allow entering PiP if not on the lockscreen. If there is another
+ // PiP activity, the logic to handle that comes later in enterPictureInPictureMode()
+ return !isKeyguardLocked;
+ case PAUSING:
+ 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
+ && 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
+ && checkEnterPictureInPictureOnHideAppOpsState();
+ }
+ default:
+ throw new IllegalStateException(caller
+ + ": Current activity is not visible (state=" + state.name() + ") "
+ + "r=" + this);
+ }
+ }
+
+ /**
+ * @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() {
@@ -1783,7 +1810,8 @@
if (_taskDescription.getIconFilename() == null &&
(icon = _taskDescription.getIcon()) != null) {
final String iconFilename = createImageFilename(createTime, task.taskId);
- final File iconFile = new File(TaskPersister.getUserImagesDir(userId), iconFilename);
+ final File iconFile = new File(TaskPersister.getUserImagesDir(task.userId),
+ iconFilename);
final String iconFilePath = iconFile.getAbsolutePath();
service.mRecentTasks.saveImage(icon, iconFilePath);
_taskDescription.setIconFilename(iconFilePath);
@@ -2206,9 +2234,11 @@
throw new XmlPullParserException("restoreActivity resolver error. Intent=" + intent +
" resolvedType=" + resolvedType);
}
- final ActivityRecord r = new ActivityRecord(service, /*caller*/null, launchedFromUid,
- launchedFromPackage, intent, resolvedType, aInfo, service.getConfiguration(),
- null, null, 0, componentSpecified, false, stackSupervisor, null, null, null);
+ final ActivityRecord r = new ActivityRecord(service, null /* caller */,
+ 0 /* launchedFromPid */, launchedFromUid, launchedFromPackage, intent, resolvedType,
+ aInfo, service.getConfiguration(), null /* resultTo */, null /* resultWho */,
+ 0 /* reqCode */, componentSpecified, false /* rootVoiceInteraction */,
+ stackSupervisor, null /* container */, null /* options */, null /* sourceRecord */);
r.persistentState = persistentState;
r.taskDescription = taskDescription;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 4df0cb1..6d5678c 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -26,6 +26,8 @@
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_SHOW_WITH_INSECURE_LOCKSCREEN;
+
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_APP;
@@ -62,7 +64,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 +74,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 +82,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;
@@ -105,6 +106,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.ArraySet;
import android.util.EventLog;
+import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -115,6 +117,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;
@@ -129,7 +133,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;
@@ -195,6 +199,12 @@
return mActivityContainer.mActivityDisplay;
}
+ @Override
+ void onParentChanged() {
+ super.onParentChanged();
+ mStackSupervisor.updateUIDsPresentOnDisplay();
+ }
+
enum ActivityState {
INITIALIZING,
RESUMED,
@@ -235,6 +245,7 @@
final ActivityManagerService mService;
private final WindowManagerService mWindowManager;
+ private StackWindowController mWindowContainerController;
private final RecentTasks mRecentTasks;
/**
@@ -323,7 +334,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;
@@ -436,7 +447,7 @@
}
ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
- RecentTasks recentTasks) {
+ RecentTasks recentTasks, boolean onTop) {
mActivityContainer = activityContainer;
mStackSupervisor = activityContainer.getOuter();
mService = mStackSupervisor.mService;
@@ -447,13 +458,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);
}
/**
@@ -462,10 +485,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);
@@ -473,6 +496,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.
@@ -482,16 +506,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.
*/
@@ -513,7 +527,8 @@
void remove() {
removeFromDisplay();
mStackSupervisor.deleteActivityContainerRecord(mStackId);
- mWindowManager.removeStack(mStackId);
+ mWindowContainerController.removeContainer();
+ mWindowContainerController = null;
onParentChanged();
}
@@ -521,6 +536,45 @@
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, Rect outTempBounds, Rect outTempInsetBounds,
+ boolean ignoreVisibility) {
+ mWindowContainerController.getStackDockedModeBounds(outBounds, outTempBounds,
+ outTempInsetBounds, ignoreVisibility);
+ }
+
+ void prepareFreezingTaskBounds() {
+ mWindowContainerController.prepareFreezingTaskBounds();
+ }
+
+ void setPictureInPictureActions(List<RemoteAction> actions) {
+ mWindowContainerController.setPictureInPictureActions(actions);
+ }
+
+ void getWindowContainerBounds(Rect outBounds) {
+ if (mWindowContainerController != null) {
+ mWindowContainerController.getBounds(outBounds);
+ return;
+ }
+ outBounds.setEmpty();
+ }
+
+ void getBoundsForNewConfiguration(Rect outBounds, Rect outTempBounds) {
+ mWindowContainerController.getBoundsForNewConfiguration(outBounds, outTempBounds);
+ }
+
+ 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()}.
@@ -541,8 +595,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);
}
@@ -680,6 +733,27 @@
return null;
}
+ boolean isInStackLocked(TaskRecord task) {
+ return mTaskHistory.contains(task);
+ }
+
+ /** Checks if there are tasks with specific UID in the stack. */
+ boolean isUidPresent(int uid) {
+ for (TaskRecord task : mTaskHistory) {
+ if (task.effectiveUid == uid) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Get all UIDs that are present in the stack. */
+ void getPresentUIDs(IntArray presentUIDs) {
+ for (TaskRecord task : mTaskHistory) {
+ presentUIDs.add(task.effectiveUid);
+ }
+ }
+
final boolean updateLRUListLocked(ActivityRecord r) {
final boolean hadit = mLRUActivities.remove(r);
mLRUActivities.add(r);
@@ -739,7 +813,8 @@
task = topTask();
if (task != null) {
- task.moveWindowContainerToTop(true /* includingParents */);
+ mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+ true /* includingParents */);
}
}
@@ -758,7 +833,7 @@
mTaskHistory.remove(task);
mTaskHistory.add(0, task);
updateTaskMovement(task, false);
- task.moveWindowContainerToBottom();
+ mWindowContainerController.positionChildAtBottom(task.getWindowContainerController());
}
}
@@ -1517,7 +1592,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;
}
@@ -1698,9 +1773,7 @@
+ stackInvisible + " behindFullscreenActivity="
+ behindFullscreenActivity + " mLaunchTaskBehind="
+ r.mLaunchTaskBehind);
- if (!enterPictureInPictureOnActivityInvisible(r)) {
- makeInvisible(r, visibleBehind);
- }
+ makeInvisible(r, visibleBehind);
}
}
if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
@@ -1769,6 +1842,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
*/
@@ -1807,6 +1886,12 @@
if (isTop) {
mTopActivityOccludesKeyguard |= showWhenLocked;
}
+
+ final boolean canShowWithKeyguard = isOnShowWhenLockedInsecureDisplay()
+ && mStackSupervisor.mKeyguardController.canDismissKeyguard();
+ if (canShowWithKeyguard) {
+ return true;
+ }
}
if (keyguardShowing) {
@@ -1822,6 +1907,22 @@
}
}
+ /**
+ * Check if the display to which this stack is attached has
+ * {@link Display#FLAG_SHOW_WITH_INSECURE_LOCKSCREEN} applied.
+ */
+ private boolean isOnShowWhenLockedInsecureDisplay() {
+ final ActivityStackSupervisor.ActivityDisplay activityDisplay
+ = mActivityContainer.mActivityDisplay;
+ if (activityDisplay == null) {
+ throw new IllegalStateException("Stack is not attached to any display, stackId="
+ + mStackId);
+ }
+
+ final int flags = activityDisplay.mDisplay.getFlags();
+ return (flags & FLAG_SHOW_WITH_INSECURE_LOCKSCREEN) != 0;
+ }
+
private void checkTranslucentActivityWaiting(ActivityRecord top) {
if (mTranslucentActivityWaiting != top) {
mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -1859,35 +1960,6 @@
return false;
}
- /**
- * Attempts to enter picture-in-picture if the activity that is being made invisible supports
- * it. If not, then
- *
- * @return whether or not picture-in-picture mode was entered.
- */
- private boolean enterPictureInPictureOnActivityInvisible(ActivityRecord r) {
- final boolean hasPinnedStack =
- mStackSupervisor.getStack(PINNED_STACK_ID) != null;
- final boolean isKeyguardLocked = mService.isKeyguardLocked();
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, " enterPictureInPictureOnInvisible="
- + r.shouldEnterPictureInPictureOnInvisible()
- + " hasPinnedStack=" + hasPinnedStack
- + " isKeyguardLocked=" + isKeyguardLocked);
- if (!hasPinnedStack && !isKeyguardLocked && r.visible &&
- r.shouldEnterPictureInPictureOnInvisible()) {
- r.setEnterPipOnMoveToBackground(false);
-
- // Enter picture in picture, but don't move the home stack to the front
- // since it will affect the focused stack's visibility and occlude
- // starting activities
- mService.enterPictureInPictureModeLocked(r, r.getDisplayId(),
- r.pictureInPictureArgs, false /* moveHomeStackToFront */,
- "ensureActivitiesVisibleLocked");
- return true;
- }
- return false;
- }
-
private void makeInvisible(ActivityRecord r, ActivityRecord visibleBehind) {
if (!r.visible) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
@@ -1906,6 +1978,10 @@
"Scheduling invisibility: " + r);
r.app.thread.scheduleWindowVisibility(r.appToken, false);
}
+
+ // Reset the flag indicating that an app can enter picture-in-picture once the
+ // activity is hidden
+ r.supportsPictureInPictureWhilePausing = false;
break;
case INITIALIZING:
@@ -2187,15 +2263,16 @@
mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid);
- // We need to start pausing the current activity so the top one can be resumed...
- final boolean dontWaitForPause = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
- boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, dontWaitForPause);
+ // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous activity
+ // to be paused, while at the same time resuming the new resume activity
+ final boolean resumeWhilePausing = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
+ boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, false);
if (mResumedActivity != null) {
if (DEBUG_STATES) Slog.d(TAG_STATES,
"resumeTopActivityLocked: Pausing " + mResumedActivity);
- pausing |= startPausingLocked(userLeaving, false, next, dontWaitForPause);
+ pausing |= startPausingLocked(userLeaving, false, next, false);
}
- if (pausing) {
+ if (pausing && !resumeWhilePausing) {
if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG_STATES,
"resumeTopActivityLocked: Skip resume: need to start pausing");
// At this point we want to put the upcoming activity's process
@@ -2572,7 +2649,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);
}
@@ -2584,7 +2662,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) {
@@ -2612,7 +2691,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
@@ -2696,10 +2775,10 @@
if (r.mLaunchTaskBehind) {
transit = TRANSIT_TASK_OPEN_BEHIND;
} else {
- // If a new task is being launched, then mark the existing top activity to
- // enter picture-in-picture if it supports auto-entering PiP
+ // If a new task is being launched, then mark the existing top activity as
+ // supporting picture-in-picture while pausing
if (focusedTopActivity != null) {
- focusedTopActivity.setEnterPipOnMoveToBackground(true);
+ focusedTopActivity.supportsPictureInPictureWhilePausing = true;
}
transit = TRANSIT_TASK_OPEN;
}
@@ -2862,7 +2941,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
@@ -2998,7 +3078,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
@@ -4245,10 +4326,10 @@
} else {
updateTransitLocked(TRANSIT_TASK_TO_FRONT, options);
}
- // If a new task is moved to the front, then mark the existing top activity to enter
- // picture-in-picture if it supports auto-entering PiP
+ // If a new task is moved to the front, then mark the existing top activity as supporting
+ // picture-in-picture while paused
if (focusedTopActivity != null) {
- focusedTopActivity.setEnterPipOnMoveToBackground(true);
+ focusedTopActivity.supportsPictureInPictureWhilePausing = true;
}
mStackSupervisor.resumeFocusedStackTopActivityLocked();
@@ -4367,7 +4448,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())) {
@@ -4430,11 +4511,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();
@@ -4448,9 +4542,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);
}
@@ -4463,11 +4557,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);
}
@@ -4657,7 +4749,7 @@
}
ci.numActivities = numActivities;
ci.numRunning = numRunning;
- ci.isDockable = task.canGoInDockedStack();
+ ci.supportsSplitScreenMultiWindow = task.supportsSplitScreen();
ci.resizeMode = task.mResizeMode;
list.add(ci);
}
@@ -4891,7 +4983,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 */);
}
}
@@ -4904,13 +4997,13 @@
final boolean toTop = position >= mTaskHistory.size();
final ActivityStack prevStack = preAddTask(task, reason, toTop);
+ mTaskHistory.add(position, task);
task.setStack(this);
if (toTop) {
updateTaskReturnToForTopInsertion(task);
}
- mTaskHistory.add(position, task);
updateTaskMovement(task, toTop);
postAddTask(task, prevStack);
@@ -4927,8 +5020,8 @@
final ActivityRecord topRunningActivity = task.topRunningActivityLocked();
final boolean wasResumed = topRunningActivity == task.getStack().mResumedActivity;
- task.setStack(this);
insertTaskAtPosition(task, index);
+ task.setStack(this);
postAddTask(task, null /* prevStack */);
if (wasResumed) {
@@ -4966,8 +5059,8 @@
}
}
- void moveToFrontAndResumeStateIfNeeded(
- ActivityRecord r, boolean moveToFront, boolean setResume, String reason) {
+ void moveToFrontAndResumeStateIfNeeded(ActivityRecord r, boolean moveToFront, boolean setResume,
+ boolean setPause, String reason) {
if (!moveToFront) {
return;
}
@@ -4978,6 +5071,10 @@
if (setResume) {
mResumedActivity = r;
}
+ // If the activity was previously pausing, then ensure we transfer that as well
+ if (setPause) {
+ mPausingActivity = r;
+ }
// Move the stack in which we are placing the activity to the front. The call will also
// make sure the activity focus is set.
moveToFront(reason);
@@ -4998,6 +5095,7 @@
final boolean wasFocused = mStackSupervisor.isFocusedStack(prevStack)
&& (mStackSupervisor.topRunningActivityLocked() == r);
final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
+ final boolean wasPaused = prevStack.mPausingActivity == r;
final TaskRecord task = createTaskRecord(
mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
@@ -5005,10 +5103,14 @@
r.setTask(task, null);
task.addActivityToTop(r);
mStackSupervisor.scheduleReportPictureInPictureModeChangedIfNeeded(task, prevStack);
- moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, "moveActivityToStack");
+ moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, wasPaused,
+ "moveActivityToStack");
if (wasResumed) {
prevStack.mResumedActivity = null;
}
+ if (wasPaused) {
+ prevStack.mPausingActivity = null;
+ }
}
public int getStackId() {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 4fe8939..b2b3e61 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;
@@ -125,6 +124,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
@@ -154,6 +154,7 @@
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -375,12 +376,15 @@
// 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<>();
- InputManagerInternal mInputManagerInternal;
+ private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+
+ private DisplayManagerInternal mDisplayManagerInternal;
+ private InputManagerInternal mInputManagerInternal;
/** The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks
* may be finished until there is only one entry left. If this is empty the system is not
@@ -414,8 +418,6 @@
final ActivityMetricsLogger mActivityMetricsLogger;
- private final ResizeDockedStackTimeout mResizeDockedStackTimeout;
-
@Override
protected int getChildCount() {
return mActivityDisplays.size();
@@ -525,7 +527,6 @@
mService = service;
mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper());
mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
- mResizeDockedStackTimeout = new ResizeDockedStackTimeout(service, this, mHandler);
mKeyguardController = new KeyguardController(service, this);
}
@@ -580,6 +581,7 @@
mDisplayManager =
(DisplayManager)mService.mContext.getSystemService(Context.DISPLAY_SERVICE);
mDisplayManager.registerDisplayListener(this, null);
+ mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
Display[] displays = mDisplayManager.getDisplays();
for (int displayNdx = displays.length - 1; displayNdx >= 0; --displayNdx) {
@@ -1317,7 +1319,8 @@
}
profilerInfo = new ProfilerInfo(profileFile, profileFd,
- mService.mSamplingInterval, mService.mAutoStopProfiler);
+ mService.mSamplingInterval, mService.mAutoStopProfiler,
+ mService.mStreamingOutput);
}
}
}
@@ -1452,7 +1455,7 @@
ActivityRecord resultRecord, ActivityStack resultStack, ActivityOptions options) {
final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
callingUid);
- if (startAnyPerm == PERMISSION_GRANTED) {
+ if (startAnyPerm == PERMISSION_GRANTED) {
return true;
}
final int componentRestriction = getComponentRestrictionForCallingPackage(
@@ -1519,25 +1522,76 @@
// Check if someone tries to launch an activity on a private display with a different
// owner.
final int launchDisplayId = options.getLaunchDisplayId();
- if (launchDisplayId != INVALID_DISPLAY) {
- final ActivityDisplay activityDisplay = mActivityDisplays.get(launchDisplayId);
- if (activityDisplay != null
- && (activityDisplay.mDisplay.getFlags() & FLAG_PRIVATE) != 0) {
- if (activityDisplay.mDisplay.getOwnerUid() != callingUid) {
- final String msg = "Permission Denial: starting " + intent.toString()
- + " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ") with launchDisplayId="
- + launchDisplayId;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- }
+ if (launchDisplayId != INVALID_DISPLAY
+ && !isCallerAllowedToLaunchOnDisplay(callingPid, callingUid, launchDisplayId)) {
+ final String msg = "Permission Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ") with launchDisplayId="
+ + launchDisplayId;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
}
}
return true;
}
+ /** Check if caller is allowed to launch activities on specified display. */
+ boolean isCallerAllowedToLaunchOnDisplay(int callingPid, int callingUid, int launchDisplayId) {
+ if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check: displayId=" + launchDisplayId
+ + " callingPid=" + callingPid + " callingUid=" + callingUid);
+
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(launchDisplayId);
+ if (activityDisplay == null) {
+ Slog.w(TAG, "Launch on display check: display not found");
+ return false;
+ }
+
+ if (!activityDisplay.isPrivate()) {
+ // Anyone can launch on a public display.
+ if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ + " allow launch on public display");
+ return true;
+ }
+
+ // Check if the caller is the owner of the display.
+ if (activityDisplay.mDisplay.getOwnerUid() == callingUid) {
+ if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ + " allow launch for owner of the display");
+ return true;
+ }
+
+ // Check if caller is present on display
+ if (activityDisplay.isUidPresent(callingUid)) {
+ if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ + " allow launch for caller present on the display");
+ return true;
+ }
+
+ // Check if the caller can launch anything.
+ final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
+ callingUid);
+ if (startAnyPerm == PERMISSION_GRANTED) {
+ if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ + " allow launch any on display");
+ return true;
+ }
+
+ Slog.w(TAG, "Launch on display check: denied");
+ return false;
+ }
+
+ /** Update lists of UIDs that are present on displays and have access to them. */
+ void updateUIDsPresentOnDisplay() {
+ mDisplayAccessUIDs.clear();
+ for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
+ mDisplayAccessUIDs.append(activityDisplay.mDisplayId, activityDisplay.getPresentUIDs());
+ }
+ // Store updated lists in DisplayManager. Callers from outside of AM should get them there.
+ mDisplayManagerInternal.setDisplayAccessUIDs(mDisplayAccessUIDs);
+ }
+
UserInfo getUserInfo(int userId) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -2142,7 +2196,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);
@@ -2180,17 +2234,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) {
@@ -2284,8 +2327,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.
@@ -2302,13 +2344,19 @@
// 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 */);
+ final Rect tempOtherTaskRect = new Rect();
+ final Rect tempOtherTaskInsetRect = new Rect();
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
- resizeStackLocked(i, tempRect, tempOtherTaskBounds,
- tempOtherTaskInsetBounds, preserveWindows,
- true /* allowResizeInDockedMode */, deferResume);
+ final ActivityStack current = getStack(i);
+ if (current != null && StackId.isResizeableByDockedStack(i)) {
+ current.getStackDockedModeBounds(tempRect, tempOtherTaskRect,
+ tempOtherTaskInsetRect, true /* ignoreVisibility */);
+ resizeStackLocked(i, tempRect,
+ !tempOtherTaskRect.isEmpty() ? tempOtherTaskRect :
+ tempOtherTaskBounds,
+ !tempOtherTaskInsetRect.isEmpty() ? tempOtherTaskInsetRect :
+ tempOtherTaskInsetBounds,
+ preserveWindows, true /* allowResizeInDockedMode */, deferResume);
}
}
}
@@ -2320,12 +2368,6 @@
mWindowManager.continueSurfaceLayout();
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
-
- mResizeDockedStackTimeout.notifyResizing(dockedBounds,
- tempDockedTaskBounds != null
- || tempDockedTaskInsetBounds != null
- || tempOtherTaskBounds != null
- || tempOtherTaskInsetBounds != null);
}
void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
@@ -2338,8 +2380,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();
@@ -2348,14 +2389,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;
}
@@ -2519,7 +2559,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;
@@ -2617,6 +2657,7 @@
final ActivityStack prevStack = task.getStack();
final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() == r);
final boolean wasResumed = prevStack.mResumedActivity == r;
+ final boolean wasPaused = prevStack.mPausingActivity == r;
// In some cases the focused stack isn't the front stack. E.g. pinned stack.
// Whenever we are moving the top activity from the front stack we want to make sure to move
// the stack to the front.
@@ -2641,10 +2682,19 @@
task.mTemporarilyUnresizable = false;
task.reparent(stack.mStackId, toTop ? MAX_VALUE : 0, reason);
+ // Reset the resumed activity on the previous stack
+ if (wasResumed) {
+ prevStack.mResumedActivity = null;
+ }
+ // Reset the paused activity on the previous stack
+ if (wasPaused) {
+ prevStack.mPausingActivity = null;
+ }
+
// If the task had focus before (or we're requested to move focus),
// move focus to the new stack by moving the stack to the front.
- stack.moveToFrontAndResumeStateIfNeeded(
- r, forceFocus || wasFocused || wasFront, wasResumed, reason);
+ stack.moveToFrontAndResumeStateIfNeeded(r, forceFocus || wasFocused || wasFront, wasResumed,
+ wasPaused, reason);
return stack;
}
@@ -2704,7 +2754,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) {
@@ -2773,7 +2824,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;
@@ -2783,9 +2838,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.
@@ -2795,8 +2847,12 @@
if (task.mActivities.size() == 1) {
// There is only one activity in the task. So, we can just move the task over to
- // the stack without re-parenting the activity in a different task.
- if (moveHomeStackToFront && task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE) {
+ // the stack without re-parenting the activity in a different task. We don't
+ // move the home stack forward if we are currently entering picture-in-picture
+ // while pausing because that changes the focused stack and may prevent the new
+ // starting activity from resuming.
+ if (moveHomeStackToFront && task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE
+ && !r.supportsPictureInPictureWhilePausing) {
// Move the home stack forward if the task we just moved to the pinned stack
// was launched from home so home should be visible behind it.
moveHomeStackToFront(reason);
@@ -2808,6 +2864,10 @@
// reveal/leave the other activities in their original task
stack.moveActivityToStack(r);
}
+
+ // Reset the state that indicates it can enter PiP while pausing after we've moved it
+ // to the pinned stack
+ r.supportsPictureInPictureWhilePausing = false;
} finally {
mWindowManager.continueSurfaceLayout();
}
@@ -2817,7 +2877,7 @@
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
- mWindowManager.animateResizePinnedStack(bounds, -1);
+ stack.animateResizePinnedStack(bounds, -1);
mService.mTaskChangeNotificationController.notifyActivityPinned();
}
@@ -3154,6 +3214,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;
}
@@ -3271,7 +3342,7 @@
stack.switchUserLocked(userId);
TaskRecord task = stack.topTask();
if (task != null) {
- task.moveWindowContainerToTop(true /* includingParents */);
+ stack.positionChildWindowContainerAtTop(task);
}
}
}
@@ -3709,7 +3780,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;
@@ -3809,14 +3880,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(
@@ -4235,7 +4306,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;
@@ -4249,10 +4320,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);
}
@@ -4260,21 +4332,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
@@ -4284,7 +4353,7 @@
if (activityDisplay == null) {
return;
}
- addToDisplayLocked(activityDisplay, true /* onTop */);
+ addToDisplayLocked(activityDisplay);
}
}
@@ -4361,7 +4430,7 @@
if (DEBUG_STACK) Slog.d(TAG_STACK, "removeFromDisplayLocked: " + this
+ " current displayId=" + mActivityDisplay.mDisplayId);
- mActivityDisplay.detachActivitiesLocked(mStack);
+ mActivityDisplay.detachStack(mStack);
mActivityDisplay = null;
}
@@ -4377,8 +4446,7 @@
removeFromDisplayLocked();
mActivityDisplay = activityDisplay;
- mStack.moveToDisplay(activityDisplay);
- activityDisplay.attachActivities(mStack, ON_TOP);
+ mStack.reparent(activityDisplay, ON_TOP);
}
@Override
@@ -4468,7 +4536,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;
@@ -4500,7 +4569,7 @@
new VirtualActivityDisplay(width, height, density);
mActivityDisplay = virtualActivityDisplay;
mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
- addToDisplayLocked(virtualActivityDisplay, true /* onTop */);
+ addToDisplayLocked(virtualActivityDisplay);
}
if (mSurface != null) {
@@ -4568,6 +4637,9 @@
ActivityRecord mVisibleBehindActivity;
+ /** Array of all UIDs that are present on the display. */
+ private IntArray mDisplayAccessUIDs = new IntArray();
+
ActivityDisplay() {
}
@@ -4586,10 +4658,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 {
@@ -4597,8 +4668,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);
}
@@ -4630,6 +4701,28 @@
protected ConfigurationContainer getParent() {
return ActivityStackSupervisor.this;
}
+
+ boolean isPrivate() {
+ return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
+ }
+
+ boolean isUidPresent(int uid) {
+ for (ActivityStack stack : mStacks) {
+ if (stack.isUidPresent(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Update and get all UIDs that are present on the display and have access to it. */
+ private IntArray getPresentUIDs() {
+ mDisplayAccessUIDs.clear();
+ for (ActivityStack stack : mStacks) {
+ stack.getPresentUIDs(mDisplayAccessUIDs);
+ }
+ return mDisplayAccessUIDs;
+ }
}
class VirtualActivityDisplay extends ActivityDisplay {
@@ -4654,8 +4747,8 @@
}
@Override
- void detachActivitiesLocked(ActivityStack stack) {
- super.detachActivitiesLocked(stack);
+ void detachStack(ActivityStack stack) {
+ super.detachStack(stack);
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
mVirtualDisplay = null;
@@ -4806,4 +4899,24 @@
}
return topActivityTokens;
}
+
+ public IBinder getTopVisibleActivity(int uid) {
+ // TODO(b/33197203): get rid of DEFAULT_DISPLAY here?. Used in
+ // VoiceInteractionManagerServiceImpl#showSessionLocked.
+ final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
+ if (display == null) {
+ return null;
+ }
+ final ArrayList<ActivityStack> stacks = display.mStacks;
+ for (int i = stacks.size() - 1; i >= 0; i--) {
+ ActivityStack stack = stacks.get(i);
+ if (stack.getStackVisibilityLocked(null) == ActivityStack.STACK_VISIBLE) {
+ ActivityRecord top = stack.topActivity();
+ if (top != null && stack == mFocusedStack && top.app.uid == uid) {
+ return top.appToken;
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 2634385..96f732e 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;
@@ -476,10 +477,10 @@
aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
}
- ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
- intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord,
- resultWho, requestCode, componentSpecified, voiceSession != null, mSupervisor,
- container, options, sourceRecord);
+ ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
+ callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
+ resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
+ mSupervisor, container, options, sourceRecord);
if (outActivity != null) {
outActivity[0] = r;
}
@@ -582,7 +583,10 @@
// The activity was already running in the pinned stack so it wasn't started, but either
// brought to the front or the new intent was delivered to it since it was already in
// front. Notify anyone interested in this piece of information.
- mService.mTaskChangeNotificationController.notifyPinnedActivityRestartAttempt();
+ final ComponentName sourceComponent = sourceRecord == null ? null :
+ sourceRecord.realActivity;
+ mService.mTaskChangeNotificationController.notifyPinnedActivityRestartAttempt(
+ sourceComponent);
return;
}
}
@@ -644,18 +648,18 @@
null);
credential.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
// Show confirm credentials activity.
- startConfirmCredentialIntent(credential);
+ startConfirmCredentialIntent(credential, null);
}
}
- void startConfirmCredentialIntent(Intent intent) {
+ void startConfirmCredentialIntent(Intent intent, Bundle optionsBundle) {
intent.addFlags(FLAG_ACTIVITY_NEW_TASK |
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
FLAG_ACTIVITY_TASK_ON_HOME);
- final ActivityOptions options = ActivityOptions.makeBasic();
+ ActivityOptions options = (optionsBundle != null ? new ActivityOptions(optionsBundle)
+ : ActivityOptions.makeBasic());
options.setLaunchTaskId(mSupervisor.getHomeActivity().task.taskId);
- mService.mContext.startActivityAsUser(intent, options.toBundle(),
- UserHandle.CURRENT);
+ mService.mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
}
final int startActivityMayWait(IApplicationThread caller, int callingUid,
@@ -1128,7 +1132,8 @@
mService.grantUriPermissionFromIntentLocked(mCallingUid, mStartActivity.packageName,
mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId);
-
+ mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent,
+ mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid));
if (mSourceRecord != null && mSourceRecord.isRecentsActivity()) {
mStartActivity.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);
}
@@ -1830,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);
}
@@ -1896,16 +1901,14 @@
final ActivityStack currentStack = task != null ? task.getStack() : null;
if (currentStack != null) {
- if (currentStack.isOnHomeDisplay()) {
- if (mSupervisor.mFocusedStack != currentStack) {
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
- "computeStackFocus: Setting " + "focused stack to r=" + r
- + " task=" + task);
- } else {
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
- "computeStackFocus: Focused stack already="
- + mSupervisor.mFocusedStack);
- }
+ if (mSupervisor.mFocusedStack != currentStack) {
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+ "computeStackFocus: Setting " + "focused stack to r=" + r
+ + " task=" + task);
+ } else {
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+ "computeStackFocus: Focused stack already="
+ + mSupervisor.mFocusedStack);
}
return currentStack;
}
@@ -1922,13 +1925,7 @@
// Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
// If the freeform or docked stack has focus, and the activity to be launched is resizeable,
// we can also put it in the focused stack.
- final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
- final boolean canUseFocusedStack = focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID
- || (focusedStackId == DOCKED_STACK_ID && r.canGoInDockedStack())
- || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.isResizeableOrForced())
- || isDynamicStack(focusedStackId);
- if (canUseFocusedStack && (!newTask
- || mSupervisor.mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
+ if (canLaunchIntoFocusedStack(r, newTask)) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
"computeStackFocus: Have a focused stack=" + mSupervisor.mFocusedStack);
return mSupervisor.mFocusedStack;
@@ -1955,6 +1952,36 @@
return stack;
}
+ /** Check if provided activity record can launch in currently focused stack. */
+ private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
+ // The fullscreen stack can contain any task regardless of if the task is resizeable
+ // or not. So, we let the task go in the fullscreen task if it is the focus stack.
+ // Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
+ // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
+ // we can also put it in the focused stack.
+ final ActivityStack focusedStack = mSupervisor.mFocusedStack;
+ final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
+ final boolean canUseFocusedStack;
+ switch (focusedStackId) {
+ case FULLSCREEN_WORKSPACE_STACK_ID:
+ canUseFocusedStack = true;
+ break;
+ case DOCKED_STACK_ID:
+ canUseFocusedStack = r.supportsSplitScreen();
+ break;
+ case FREEFORM_WORKSPACE_STACK_ID:
+ canUseFocusedStack = r.supportsFreeform();
+ break;
+ default:
+ canUseFocusedStack = isDynamicStack(focusedStackId)
+ && mSupervisor.isCallerAllowedToLaunchOnDisplay(r.launchedFromPid,
+ r.launchedFromUid, focusedStack.mDisplayId);
+ }
+
+ return canUseFocusedStack
+ && (!newTask || focusedStack.mActivityContainer.isEligibleForNewTasks());
+ }
+
private ActivityStack getLaunchStack(ActivityRecord r, int launchFlags, TaskRecord task,
ActivityOptions aOptions) {
@@ -2030,29 +2057,27 @@
}
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:
+ if (StackId.isDynamicStack(stackId)) {
+ return true;
+ }
+ 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 029b5dd..384f2f8 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -20,6 +20,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.os.ProcessCpuTracker;
+import com.android.server.RescueParty;
import com.android.server.Watchdog;
import android.app.ActivityManager;
@@ -258,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;
@@ -269,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;
}
}
@@ -285,7 +296,8 @@
if (proc == null) {
Slog.w(TAG, "crashApplication: nothing for uid=" + uid
+ " initialPid=" + initialPid
- + " packageName=" + packageName);
+ + " packageName=" + packageName
+ + " userId=" + userId);
return;
}
@@ -323,6 +335,12 @@
longMsg = shortMsg;
}
+ // If a persistent app is stuck in a crash loop, the device isn't very
+ // usable, so we want to consider sending out a rescue party.
+ if (r != null && r.persistent) {
+ RescueParty.notePersistentAppCrash(mContext, r.uid);
+ }
+
AppErrorResult result = new AppErrorResult();
TaskRecord task;
synchronized (mService) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 8104a43..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,14 +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..2bd119e 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();
@@ -278,7 +280,7 @@
/**
* @return true if Keyguard can be currently dismissed without entering credentials.
*/
- private boolean canDismissKeyguard() {
+ boolean canDismissKeyguard() {
return mWindowManager.isKeyguardTrusted() || !mWindowManager.isKeyguardSecure();
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 1322ecf..40effff 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -138,31 +138,9 @@
// without empty apps being able to push them out of memory.
static final int MIN_CACHED_APPS = 2;
- // The maximum number of cached processes we will keep around before killing them.
- // NOTE: this constant is *only* a control to not let us go too crazy with
- // keeping around processes on devices with large amounts of RAM. For devices that
- // are tighter on RAM, the out of memory killer is responsible for killing background
- // processes as RAM is needed, and we should *never* be relying on this limit to
- // kill them. Also note that this limit only applies to cached background processes;
- // we have no limit on the number of service, visible, foreground, or other such
- // processes and the number of those processes does not count against the cached
- // process limit.
- static final int MAX_CACHED_APPS = 32;
-
// We allow empty processes to stick around for at most 30 minutes.
static final long MAX_EMPTY_TIME = 30*60*1000;
- // The maximum number of empty app processes we will let sit around.
- private static final int MAX_EMPTY_APPS = computeEmptyProcessLimit(MAX_CACHED_APPS);
-
- // The number of empty apps at which we don't consider it necessary to do
- // memory trimming.
- static final int TRIM_EMPTY_APPS = MAX_EMPTY_APPS/2;
-
- // The number of cached at which we don't consider it necessary to do
- // memory trimming.
- static final int TRIM_CACHED_APPS = (MAX_CACHED_APPS-MAX_EMPTY_APPS)/3;
-
// Threshold of number of cached+empty where we consider memory critical.
static final int TRIM_CRITICAL_THRESHOLD = 3;
diff --git a/services/core/java/com/android/server/am/ResizeDockedStackTimeout.java b/services/core/java/com/android/server/am/ResizeDockedStackTimeout.java
deleted file mode 100644
index ff39589..0000000
--- a/services/core/java/com/android/server/am/ResizeDockedStackTimeout.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import android.graphics.Rect;
-import android.os.Handler;
-
-import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
-
-/**
- * When resizing the docked stack, a caller can temporarily supply task bounds that are different
- * from the stack bounds. In order to return to a sane state if the caller crashes or has a bug,
- * this class manages this cycle.
- */
-class ResizeDockedStackTimeout {
-
- private static final long TIMEOUT_MS = 10 * 1000;
- private final ActivityManagerService mService;
- private final ActivityStackSupervisor mSupervisor;
- private final Handler mHandler;
- private final Rect mCurrentDockedBounds = new Rect();
-
- private final Runnable mTimeoutRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (mService) {
- mSupervisor.resizeDockedStackLocked(mCurrentDockedBounds, null, null, null, null,
- PRESERVE_WINDOWS);
- }
- }
- };
-
- ResizeDockedStackTimeout(ActivityManagerService service, ActivityStackSupervisor supervisor,
- Handler handler) {
- mService = service;
- mSupervisor = supervisor;
- mHandler = handler;
- }
-
- void notifyResizing(Rect dockedBounds, boolean hasTempBounds) {
- mHandler.removeCallbacks(mTimeoutRunnable);
- if (!hasTempBounds) {
- return;
- }
- mCurrentDockedBounds.set(dockedBounds);
- mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
- }
-
-}
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/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index d035fa9..2990dff 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -97,7 +97,7 @@
};
private final TaskStackConsumer mNotifyPinnedActivityRestartAttempt = (l, m) -> {
- l.onPinnedActivityRestartAttempt();
+ l.onPinnedActivityRestartAttempt((ComponentName) m.obj);
};
private final TaskStackConsumer mNotifyPinnedStackAnimationEnded = (l, m) -> {
@@ -267,10 +267,11 @@
* running in the pinned stack and the activity was not actually started, but the task is
* either brought to the front or a new Intent is delivered to it.
*/
- void notifyPinnedActivityRestartAttempt() {
+ void notifyPinnedActivityRestartAttempt(ComponentName sourceComponent) {
mHandler.removeMessages(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG);
final Message msg =
- mHandler.obtainMessage(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG);
+ mHandler.obtainMessage(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG,
+ sourceComponent);
forAllLocalListeners(mNotifyPinnedActivityRestartAttempt, msg);
msg.sendToTarget();
}
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 7a62f2c..9dde39e 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -651,6 +651,8 @@
"omitting from persistentTaskIds task=" + task);
}
}
+ mService.mWindowManager.removeObsoleteTaskFiles(persistentTaskIds,
+ mRecentTasks.usersWithRecentsLoadedLocked());
}
removeObsoleteFiles(persistentTaskIds);
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a72a958..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);
@@ -583,11 +578,14 @@
mWindowContainerController.cancelThumbnailTransition();
}
- public TaskSnapshot getSnapshot() {
- if (mWindowContainerController == null) {
- return null;
- }
- return mWindowContainerController.getSnapshot();
+ /**
+ * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
+ */
+ TaskSnapshot getSnapshot() {
+
+ // TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more
+ // synchronized between AM and WM.
+ return mService.mWindowManager.getTaskSnapshot(taskId, userId);
}
void touchActiveTime() {
@@ -688,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;
@@ -741,8 +740,14 @@
return mStack;
}
- /** Must be used for setting parent stack because it performs configuration updates. */
+ /**
+ * Must be used for setting parent stack because it performs configuration updates.
+ * Must be called after adding task as a child to the stack.
+ */
void setStack(ActivityStack stack) {
+ if (stack != null && !stack.isInStackLocked(this)) {
+ throw new IllegalStateException("Task must be added as a Stack child first.");
+ }
mStack = stack;
onParentChanged();
}
@@ -769,6 +774,12 @@
return mStack;
}
+ @Override
+ void onParentChanged() {
+ super.onParentChanged();
+ mService.mStackSupervisor.updateUIDsPresentOnDisplay();
+ }
+
// Close up recents linked list.
void closeRecentsChain() {
if (mPrevAffiliate != null) {
@@ -788,6 +799,9 @@
inRecents = false;
mService.notifyTaskPersisterLocked(this, false);
}
+
+ // TODO: Use window container controller once tasks are better synced between AM and WM
+ mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
}
void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
@@ -1283,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);
}
/**
@@ -1311,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.
@@ -1384,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();
@@ -1461,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(
@@ -1531,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;
@@ -1597,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)) {
@@ -1669,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,
@@ -1676,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) {
@@ -1963,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);
@@ -2054,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/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index d1a15bd..64e3417 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -26,10 +26,13 @@
*/
public final class UidRecord {
final int uid;
+ final boolean persistent;
int curProcState;
int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
long lastBackgroundTime;
boolean ephemeral;
+ boolean curWhitelist;
+ boolean setWhitelist;
boolean idle;
int numProcs;
@@ -49,8 +52,9 @@
ChangeItem pendingChange;
- public UidRecord(int _uid) {
+ public UidRecord(int _uid, boolean _persist) {
uid = _uid;
+ persistent = _persist;
reset();
}
@@ -69,6 +73,9 @@
if (ephemeral) {
sb.append(" ephemeral");
}
+ if (curWhitelist) {
+ sb.append(" whitelist");
+ }
if (lastBackgroundTime > 0) {
sb.append(" bg:");
TimeUtils.formatDuration(SystemClock.elapsedRealtime()-lastBackgroundTime, sb);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index f516e99..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);
@@ -419,7 +423,8 @@
if (userId != UserHandle.USER_SYSTEM) {
Slog.d(TAG, "Initializing user #" + userId);
Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mInjector.broadcastIntentLocked(intent, null,
new IIntentReceiver.Stub() {
@Override
@@ -435,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);
@@ -1708,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..213041e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -144,7 +144,6 @@
*/
public class AudioService extends IAudioService.Stub
implements AccessibilityManager.TouchExplorationStateChangeListener,
- AccessibilityManager.AccessibilityStateChangeListener,
AccessibilityManager.AccessibilityServicesStateChangeListener {
private static final String TAG = "AudioService";
@@ -5926,25 +5925,13 @@
//==========================================================================================
// Accessibility
- /**
- * Compile-time constant to enable the use of an independent a11y volume:
- * - set to true to listen to a11y services state changes and read
- * 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 void initA11yMonitoring() {
final AccessibilityManager accessibilityManager =
(AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
updateDefaultStreamOverrideDelay(accessibilityManager.isTouchExplorationEnabled());
updateA11yVolumeAlias(accessibilityManager.isEnabled());
accessibilityManager.addTouchExplorationStateChangeListener(this);
- if (USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME) {
- accessibilityManager.addAccessibilityServicesStateChangeListener(this);
- } else {
- accessibilityManager.addAccessibilityStateChangeListener(this);
- }
+ accessibilityManager.addAccessibilityServicesStateChangeListener(this);
}
//---------------------------------------------------------------------------------
@@ -5982,12 +5969,6 @@
private static boolean sIndependentA11yVolume = false;
- // implementation of AccessibilityStateChangeListener
- @Override
- public void onAccessibilityStateChanged(boolean enabled) {
- updateA11yVolumeAlias(enabled);
- }
-
// implementation of AccessibilityServicesStateChangeListener
@Override
public void onAccessibilityServicesStateChanged() {
@@ -6502,6 +6483,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/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 8d4f0a9..5fed397 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -40,6 +40,7 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -50,18 +51,106 @@
import java.util.HashSet;
import java.util.List;
+import java.lang.Thread;
+import java.lang.Runnable;
+import java.lang.InterruptedException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+// The following class is Android Emulator specific. It is used to read and
+// write contents of the host system's clipboard.
+class HostClipboardMonitor implements Runnable {
+ public interface HostClipboardCallback {
+ void onHostClipboardUpdated(String contents);
+ }
+
+ private RandomAccessFile mPipe = null;
+ private HostClipboardCallback mHostClipboardCallback;
+ private static final String PIPE_NAME = "pipe:clipboard";
+ private static final String PIPE_DEVICE = "/dev/qemu_pipe";
+
+ private void openPipe() {
+ try {
+ // String.getBytes doesn't include the null terminator,
+ // but the QEMU pipe device requires the pipe service name
+ // to be null-terminated.
+ byte[] b = new byte[PIPE_NAME.length() + 1];
+ b[PIPE_NAME.length()] = 0;
+ System.arraycopy(
+ PIPE_NAME.getBytes(),
+ 0,
+ b,
+ 0,
+ PIPE_NAME.length());
+ mPipe = new RandomAccessFile(PIPE_DEVICE, "rw");
+ mPipe.write(b);
+ } catch (IOException e) {
+ try {
+ if (mPipe != null) mPipe.close();
+ } catch (IOException ee) {}
+ mPipe = null;
+ }
+ }
+
+ public HostClipboardMonitor(HostClipboardCallback cb) {
+ mHostClipboardCallback = cb;
+ }
+
+ @Override
+ public void run() {
+ while(!Thread.interrupted()) {
+ try {
+ // There's no guarantee that QEMU pipes will be ready at the moment
+ // this method is invoked. We simply try to get the pipe open and
+ // retry on failure indefinitely.
+ while (mPipe == null) {
+ openPipe();
+ Thread.sleep(100);
+ }
+ int size = mPipe.readInt();
+ size = Integer.reverseBytes(size);
+ byte[] receivedData = new byte[size];
+ mPipe.readFully(receivedData);
+ mHostClipboardCallback.onHostClipboardUpdated(
+ new String(receivedData));
+ } catch (IOException e) {
+ try {
+ mPipe.close();
+ } catch (IOException ee) {}
+ mPipe = null;
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ public void setHostClipboard(String content) {
+ try {
+ if (mPipe != null) {
+ mPipe.writeInt(Integer.reverseBytes(content.getBytes().length));
+ mPipe.write(content.getBytes());
+ }
+ } catch(IOException e) {
+ Slog.e("HostClipboardMonitor",
+ "Failed to set host clipboard " + e.getMessage());
+ }
+ }
+}
+
/**
* Implementation of the clipboard for copy and paste.
*/
public class ClipboardService extends SystemService {
private static final String TAG = "ClipboardService";
+ private static final boolean IS_EMULATOR =
+ SystemProperties.getBoolean("ro.kernel.qemu", false);
private final IActivityManager mAm;
private final IUserManager mUm;
private final PackageManager mPm;
private final AppOpsManager mAppOps;
private final IBinder mPermissionOwner;
+ private HostClipboardMonitor mHostClipboardMonitor = null;
+ private Thread mHostMonitorThread = null;
private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
@@ -82,6 +171,23 @@
Slog.w("clipboard", "AM dead", e);
}
mPermissionOwner = permOwner;
+ if (IS_EMULATOR) {
+ mHostClipboardMonitor = new HostClipboardMonitor(
+ new HostClipboardMonitor.HostClipboardCallback() {
+ @Override
+ public void onHostClipboardUpdated(String contents){
+ ClipData clip =
+ new ClipData("host clipboard",
+ new String[]{"text/plain"},
+ new ClipData.Item(contents));
+ synchronized(mClipboards) {
+ setPrimaryClipInternal(getClipboard(0), clip);
+ }
+ }
+ });
+ mHostMonitorThread = new Thread(mHostClipboardMonitor);
+ mHostMonitorThread.start();
+ }
}
@Override
@@ -142,6 +248,11 @@
if (clip != null && clip.getItemCount() <= 0) {
throw new IllegalArgumentException("No items");
}
+ if (clip.getItemAt(0).getText() != null &&
+ mHostClipboardMonitor != null) {
+ mHostClipboardMonitor.setHostClipboard(
+ clip.getItemAt(0).getText().toString());
+ }
final int callingUid = Binder.getCallingUid();
if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
callingPackage) != AppOpsManager.MODE_ALLOWED) {
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index 5f9efe7..85d1d1e 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -23,6 +23,7 @@
import android.net.Network;
import android.net.NetworkUtils;
import android.net.RouteInfo;
+import android.net.TrafficStats;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
@@ -381,7 +382,12 @@
protected void setupSocket(
int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
throws ErrnoException, IOException {
- mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
+ try {
+ mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
+ }
// Setting SNDTIMEO is purely for defensive purposes.
Os.setsockoptTimeval(mFileDescriptor,
SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(writeTimeout));
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index c40780e..fbda901 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -779,6 +779,7 @@
int httpResponseCode = 599;
String redirectUrl = null;
final Stopwatch probeTimer = new Stopwatch().start();
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
try {
urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC);
@@ -839,6 +840,7 @@
if (urlConnection != null) {
urlConnection.disconnect();
}
+ TrafficStats.setThreadStatsTag(oldTag);
}
logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString());
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index 58c76ec..34826b6 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -25,6 +25,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.net.ProxyInfo;
+import android.net.TrafficStats;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
@@ -103,11 +104,15 @@
String file;
synchronized (mProxyLock) {
if (Uri.EMPTY.equals(mPacUrl)) return;
+ final int oldTag = TrafficStats
+ .getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PAC);
try {
file = get(mPacUrl);
} catch (IOException ioe) {
file = null;
Log.w(TAG, "Failed to load PAC file: " + ioe);
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
}
}
if (file != null) {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 0c80166..b0e4509 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -16,6 +16,11 @@
package com.android.server.connectivity;
+import static android.hardware.usb.UsbManager.USB_CONNECTED;
+import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -71,6 +76,7 @@
import com.android.server.connectivity.tethering.IControlsTethering;
import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
+import com.android.server.connectivity.tethering.TetheringConfiguration;
import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
import com.android.server.net.BaseNetworkObserver;
@@ -108,23 +114,11 @@
private static final SparseArray<String> sMagicDecoderRing =
MessageUtils.findMessageNames(messageClasses);
- // TODO - remove both of these - should be part of interface inspection/selection stuff
- private String[] mTetherableUsbRegexs;
- private String[] mTetherableWifiRegexs;
- private String[] mTetherableBluetoothRegexs;
- private Collection<Integer> mUpstreamIfaceTypes;
+ private volatile TetheringConfiguration mConfig;
// used to synchronize public access to members
private final Object mPublicSync;
- private static final Integer MOBILE_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE);
- private static final Integer HIPRI_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_HIPRI);
- private static final Integer DUN_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_DUN);
-
- // if we have to connect to mobile, what APN type should we use? Calculated by examining the
- // upstream type list and the DUN_REQUIRED secure-setting
- private int mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_NONE;
-
private final INetworkManagementService mNMService;
private final INetworkStatsService mStatsService;
private final INetworkPolicyManager mPolicyManager;
@@ -150,24 +144,6 @@
private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
.getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
- // USB is 192.168.42.1 and 255.255.255.0
- // Wifi is 192.168.43.1 and 255.255.255.0
- // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
- // with 255.255.255.0
- // P2P is 192.168.49.1 and 255.255.255.0
-
- private String[] mDhcpRange;
- private static final String[] DHCP_DEFAULT_RANGE = {
- "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
- "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
- "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
- "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
- };
-
- private String[] mDefaultDnsServers;
- private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
- private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4";
-
private final StateMachine mTetherMasterSM;
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private String mCurrentUpstreamIface;
@@ -208,27 +184,16 @@
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- mContext.registerReceiver(mStateReceiver, filter);
+ mContext.registerReceiver(mStateReceiver, filter, null, mTetherMasterSM.getHandler());
filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_SHARED);
filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
filter.addDataScheme("file");
- mContext.registerReceiver(mStateReceiver, filter);
-
- mDhcpRange = context.getResources().getStringArray(
- com.android.internal.R.array.config_tether_dhcp_range);
- if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
- mDhcpRange = DHCP_DEFAULT_RANGE;
- }
+ mContext.registerReceiver(mStateReceiver, filter, null, mTetherMasterSM.getHandler());
// load device config info
updateConfiguration();
-
- // TODO - remove and rely on real notifications of the current iface
- mDefaultDnsServers = new String[2];
- mDefaultDnsServers[0] = DNS_DEFAULT_SERVER1;
- mDefaultDnsServers[1] = DNS_DEFAULT_SERVER2;
}
// We can't do this once in the Tethering() constructor and cache the value, because the
@@ -237,30 +202,8 @@
return (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
}
- void updateConfiguration() {
- String[] tetherableUsbRegexs = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_tether_usb_regexs);
- String[] tetherableWifiRegexs = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_tether_wifi_regexs);
- String[] tetherableBluetoothRegexs = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_tether_bluetooth_regexs);
-
- int ifaceTypes[] = mContext.getResources().getIntArray(
- com.android.internal.R.array.config_tether_upstream_types);
- Collection<Integer> upstreamIfaceTypes = new ArrayList<>();
- for (int i : ifaceTypes) {
- upstreamIfaceTypes.add(new Integer(i));
- }
-
- synchronized (mPublicSync) {
- mTetherableUsbRegexs = tetherableUsbRegexs;
- mTetherableWifiRegexs = tetherableWifiRegexs;
- mTetherableBluetoothRegexs = tetherableBluetoothRegexs;
- mUpstreamIfaceTypes = upstreamIfaceTypes;
- }
-
- // check if the upstream type list needs to be modified due to secure-settings
- checkDunRequired();
+ private void updateConfiguration() {
+ mConfig = new TetheringConfiguration(mContext);
}
@Override
@@ -300,39 +243,14 @@
interfaceStatusChanged(iface, up);
}
- private boolean isUsb(String iface) {
- synchronized (mPublicSync) {
- for (String regex : mTetherableUsbRegexs) {
- if (iface.matches(regex)) return true;
- }
- return false;
- }
- }
-
- private boolean isWifi(String iface) {
- synchronized (mPublicSync) {
- for (String regex : mTetherableWifiRegexs) {
- if (iface.matches(regex)) return true;
- }
- return false;
- }
- }
-
- private boolean isBluetooth(String iface) {
- synchronized (mPublicSync) {
- for (String regex : mTetherableBluetoothRegexs) {
- if (iface.matches(regex)) return true;
- }
- return false;
- }
- }
-
private int ifaceNameToType(String iface) {
- if (isWifi(iface)) {
+ final TetheringConfiguration cfg = mConfig;
+
+ if (cfg.isWifi(iface)) {
return ConnectivityManager.TETHERING_WIFI;
- } else if (isUsb(iface)) {
+ } else if (cfg.isUsb(iface)) {
return ConnectivityManager.TETHERING_USB;
- } else if (isBluetooth(iface)) {
+ } else if (cfg.isBluetooth(iface)) {
return ConnectivityManager.TETHERING_BLUETOOTH;
}
return ConnectivityManager.TETHERING_INVALID;
@@ -662,6 +580,8 @@
boolean usbTethered = false;
boolean bluetoothTethered = false;
+ final TetheringConfiguration cfg = mConfig;
+
synchronized (mPublicSync) {
for (int i = 0; i < mTetherStates.size(); i++) {
TetherState tetherState = mTetherStates.valueAt(i);
@@ -671,11 +591,11 @@
} else if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
availableList.add(iface);
} else if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
- if (isUsb(iface)) {
+ if (cfg.isUsb(iface)) {
usbTethered = true;
- } else if (isWifi(iface)) {
+ } else if (cfg.isWifi(iface)) {
wifiTethered = true;
- } else if (isBluetooth(iface)) {
+ } else if (cfg.isBluetooth(iface)) {
bluetoothTethered = true;
}
activeList.add(iface);
@@ -779,69 +699,84 @@
private class StateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context content, Intent intent) {
- String action = intent.getAction();
- if (action == null) { return; }
+ final String action = intent.getAction();
+ if (action == null) return;
+
if (action.equals(UsbManager.ACTION_USB_STATE)) {
- synchronized (Tethering.this.mPublicSync) {
- boolean usbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
- mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false);
- // start tethering if we have a request pending
- if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
- tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
- }
- mUsbTetherRequested = false;
- }
+ handleUsbAction(intent);
} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
- NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
- ConnectivityManager.EXTRA_NETWORK_INFO);
- if (networkInfo != null &&
- networkInfo.getDetailedState() != NetworkInfo.DetailedState.FAILED) {
- if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
- mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
- }
+ handleConnectivityAction(intent);
} else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
- synchronized (Tethering.this.mPublicSync) {
- int curState = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
- WifiManager.WIFI_AP_STATE_DISABLED);
- switch (curState) {
- case WifiManager.WIFI_AP_STATE_ENABLING:
- // We can see this state on the way to both enabled and failure states.
- break;
- case WifiManager.WIFI_AP_STATE_ENABLED:
- // When the AP comes up and we've been requested to tether it, do so.
- if (mWifiTetherRequested) {
- tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
- }
- break;
- case WifiManager.WIFI_AP_STATE_DISABLED:
- case WifiManager.WIFI_AP_STATE_DISABLING:
- case WifiManager.WIFI_AP_STATE_FAILED:
- default:
- if (DBG) {
- Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
- curState);
- }
- // Tell appropriate interface state machines that they should tear
- // themselves down.
- for (int i = 0; i < mTetherStates.size(); i++) {
- TetherInterfaceStateMachine tism =
- mTetherStates.valueAt(i).mStateMachine;
- if (tism.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
- tism.sendMessage(
- TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
- break; // There should be at most one of these.
- }
- }
- // Regardless of whether we requested this transition, the AP has gone
- // down. Don't try to tether again unless we're requested to do so.
- mWifiTetherRequested = false;
- break;
- }
- }
+ handleWifiApAction(intent);
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
updateConfiguration();
}
}
+
+ private void handleConnectivityAction(Intent intent) {
+ final NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
+ ConnectivityManager.EXTRA_NETWORK_INFO);
+ if (networkInfo == null ||
+ networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED) {
+ return;
+ }
+
+ if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION: " + networkInfo.toString());
+ mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
+ }
+
+ private void handleUsbAction(Intent intent) {
+ final boolean usbConnected = intent.getBooleanExtra(USB_CONNECTED, false);
+ final boolean rndisEnabled = intent.getBooleanExtra(USB_FUNCTION_RNDIS, false);
+ synchronized (Tethering.this.mPublicSync) {
+ mRndisEnabled = rndisEnabled;
+ // start tethering if we have a request pending
+ if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
+ tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
+ }
+ mUsbTetherRequested = false;
+ }
+ }
+
+ private void handleWifiApAction(Intent intent) {
+ final int curState = intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED);
+ synchronized (Tethering.this.mPublicSync) {
+ switch (curState) {
+ case WifiManager.WIFI_AP_STATE_ENABLING:
+ // We can see this state on the way to both enabled and failure states.
+ break;
+ case WifiManager.WIFI_AP_STATE_ENABLED:
+ // When the AP comes up and we've been requested to tether it, do so.
+ if (mWifiTetherRequested) {
+ tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
+ }
+ break;
+ case WifiManager.WIFI_AP_STATE_DISABLED:
+ case WifiManager.WIFI_AP_STATE_DISABLING:
+ case WifiManager.WIFI_AP_STATE_FAILED:
+ default:
+ if (DBG) {
+ Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
+ curState);
+ }
+ // Tell appropriate interface state machines that they should tear
+ // themselves down.
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherInterfaceStateMachine tism =
+ mTetherStates.valueAt(i).mStateMachine;
+ if (tism.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
+ tism.sendMessage(
+ TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+ break; // There should be at most one of these.
+ }
+ }
+ // Regardless of whether we requested this transition, the AP has gone
+ // down. Don't try to tether again unless we're requested to do so.
+ mWifiTetherRequested = false;
+ break;
+ }
+ }
+ }
}
private void tetherMatchingInterfaces(boolean enable, int interfaceType) {
@@ -875,26 +810,38 @@
}
}
- // TODO - return copies so people can't tamper
+ public TetheringConfiguration getTetheringConfiguration() {
+ return mConfig;
+ }
+
+ public boolean hasTetherableConfiguration() {
+ final TetheringConfiguration cfg = mConfig;
+ final boolean hasDownstreamConfiguration =
+ (cfg.tetherableUsbRegexs.length != 0) ||
+ (cfg.tetherableWifiRegexs.length != 0) ||
+ (cfg.tetherableBluetoothRegexs.length != 0);
+ final boolean hasUpstreamConfiguration = !cfg.preferredUpstreamIfaceTypes.isEmpty();
+
+ return hasDownstreamConfiguration && hasUpstreamConfiguration;
+ }
+
+ // TODO - update callers to use getTetheringConfiguration(),
+ // which has only final members.
public String[] getTetherableUsbRegexs() {
- return mTetherableUsbRegexs;
+ return copy(mConfig.tetherableUsbRegexs);
}
public String[] getTetherableWifiRegexs() {
- return mTetherableWifiRegexs;
+ return copy(mConfig.tetherableWifiRegexs);
}
public String[] getTetherableBluetoothRegexs() {
- return mTetherableBluetoothRegexs;
+ return copy(mConfig.tetherableBluetoothRegexs);
}
public int setUsbTethering(boolean enable) {
if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
UsbManager usbManager = mContext.getSystemService(UsbManager.class);
- if (usbManager == null) {
- return enable ? ConnectivityManager.TETHER_ERROR_MASTER_ERROR
- : ConnectivityManager.TETHER_ERROR_NO_ERROR;
- }
synchronized (mPublicSync) {
if (enable) {
@@ -925,61 +872,6 @@
return ConnectivityManager.TETHER_ERROR_NO_ERROR;
}
- public int[] getUpstreamIfaceTypes() {
- int values[];
- synchronized (mPublicSync) {
- updateConfiguration(); // TODO - remove?
- values = new int[mUpstreamIfaceTypes.size()];
- Iterator<Integer> iterator = mUpstreamIfaceTypes.iterator();
- for (int i=0; i < mUpstreamIfaceTypes.size(); i++) {
- values[i] = iterator.next();
- }
- }
- return values;
- }
-
- private void checkDunRequired() {
- int secureSetting = 2;
- TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
- if (tm != null) {
- secureSetting = tm.getTetherApnRequired();
- }
- synchronized (mPublicSync) {
- // 2 = not set, 0 = DUN not required, 1 = DUN required
- if (secureSetting != 2) {
- int requiredApn = (secureSetting == 1 ?
- ConnectivityManager.TYPE_MOBILE_DUN :
- ConnectivityManager.TYPE_MOBILE_HIPRI);
- if (requiredApn == ConnectivityManager.TYPE_MOBILE_DUN) {
- while (mUpstreamIfaceTypes.contains(MOBILE_TYPE)) {
- mUpstreamIfaceTypes.remove(MOBILE_TYPE);
- }
- while (mUpstreamIfaceTypes.contains(HIPRI_TYPE)) {
- mUpstreamIfaceTypes.remove(HIPRI_TYPE);
- }
- if (mUpstreamIfaceTypes.contains(DUN_TYPE) == false) {
- mUpstreamIfaceTypes.add(DUN_TYPE);
- }
- } else {
- while (mUpstreamIfaceTypes.contains(DUN_TYPE)) {
- mUpstreamIfaceTypes.remove(DUN_TYPE);
- }
- if (mUpstreamIfaceTypes.contains(MOBILE_TYPE) == false) {
- mUpstreamIfaceTypes.add(MOBILE_TYPE);
- }
- if (mUpstreamIfaceTypes.contains(HIPRI_TYPE) == false) {
- mUpstreamIfaceTypes.add(HIPRI_TYPE);
- }
- }
- }
- if (mUpstreamIfaceTypes.contains(DUN_TYPE)) {
- mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_MOBILE_DUN;
- } else {
- mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_MOBILE_HIPRI;
- }
- }
- }
-
// TODO review API - maybe return ArrayList<String> here and below?
public String[] getTetheredIfaces() {
ArrayList<String> list = new ArrayList<String>();
@@ -1008,7 +900,7 @@
}
public String[] getTetheredDhcpRanges() {
- return mDhcpRange;
+ return mConfig.dhcpRanges;
}
public String[] getErroredIfaces() {
@@ -1083,8 +975,6 @@
private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
- private int mPreviousMobileApn = ConnectivityManager.TYPE_NONE;
-
private static final int UPSTREAM_SETTLE_TIME_MS = 10000;
TetherMasterSM(String name, Looper looper) {
@@ -1118,46 +1008,18 @@
return false;
}
- protected boolean turnOnUpstreamMobileConnection(int apnType) {
- if (apnType == ConnectivityManager.TYPE_NONE) { return false; }
-
- if (apnType != mPreviousMobileApn) {
- // Unregister any previous mobile upstream callback because
- // this request, if any, will be different.
- turnOffUpstreamMobileConnection();
- }
-
- 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:
- mPreviousMobileApn = apnType;
- break;
- default:
- return false;
- }
-
- // TODO: This should be called by the code that observes
- // configuration changes, once the above code in this function
- // is simplified (i.e. eradicated).
- mUpstreamNetworkMonitor.mobileUpstreamRequiresDun(
- apnType == ConnectivityManager.TYPE_MOBILE_DUN);
-
+ protected boolean requestUpstreamMobileConnection() {
+ mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
mUpstreamNetworkMonitor.registerMobileNetworkRequest();
return true;
}
- protected void turnOffUpstreamMobileConnection() {
+ protected void unrequestUpstreamMobileConnection() {
mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
- mPreviousMobileApn = ConnectivityManager.TYPE_NONE;
}
protected boolean turnOnMasterTetherSettings() {
+ final TetheringConfiguration cfg = mConfig;
try {
mNMService.setIpForwardingEnabled(true);
} catch (Exception e) {
@@ -1165,11 +1027,11 @@
return false;
}
try {
- mNMService.startTethering(mDhcpRange);
+ mNMService.startTethering(cfg.dhcpRanges);
} catch (Exception e) {
try {
mNMService.stopTethering();
- mNMService.startTethering(mDhcpRange);
+ mNMService.startTethering(cfg.dhcpRanges);
} catch (Exception ee) {
transitionTo(mStartTetheringErrorState);
return false;
@@ -1202,29 +1064,31 @@
updateConfiguration(); // TODO - remove?
- synchronized (mPublicSync) {
- if (VDBG) {
- Log.d(TAG, "chooseUpstreamType has upstream iface types:");
- for (Integer netType : mUpstreamIfaceTypes) {
- Log.d(TAG, " " + netType);
- }
- }
-
- for (Integer netType : mUpstreamIfaceTypes) {
- NetworkInfo info = cm.getNetworkInfo(netType.intValue());
- // TODO: if the network is suspended we should consider
- // that to be the same as connected here.
- if ((info != null) && info.isConnected()) {
- upType = netType.intValue();
- break;
- }
+ final TetheringConfiguration cfg = mConfig;
+ if (VDBG) {
+ Log.d(TAG, "chooseUpstreamType has upstream iface types:");
+ for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
+ Log.d(TAG, " " + netType);
}
}
+ for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
+ NetworkInfo info = cm.getNetworkInfo(netType.intValue());
+ // TODO: if the network is suspended we should consider
+ // that to be the same as connected here.
+ if ((info != null) && info.isConnected()) {
+ upType = netType.intValue();
+ break;
+ }
+ }
+
+ final int preferredUpstreamMobileApn = cfg.isDunRequired
+ ? ConnectivityManager.TYPE_MOBILE_DUN
+ : ConnectivityManager.TYPE_MOBILE_HIPRI;
if (DBG) {
Log.d(TAG, "chooseUpstreamType(" + tryCell + "),"
+ " preferredApn="
- + ConnectivityManager.getNetworkTypeName(mPreferredUpstreamMobileApn)
+ + ConnectivityManager.getNetworkTypeName(preferredUpstreamMobileApn)
+ ", got type="
+ ConnectivityManager.getNetworkTypeName(upType));
}
@@ -1233,11 +1097,10 @@
case ConnectivityManager.TYPE_MOBILE_DUN:
case ConnectivityManager.TYPE_MOBILE_HIPRI:
// If we're on DUN, put our own grab on it.
- turnOnUpstreamMobileConnection(upType);
+ requestUpstreamMobileConnection();
break;
case ConnectivityManager.TYPE_NONE:
- if (tryCell &&
- turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn)) {
+ if (tryCell && requestUpstreamMobileConnection()) {
// We think mobile should be coming up; don't set a retry.
} else {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
@@ -1250,7 +1113,7 @@
* If we found NONE we don't want to do this as we want any previous
* requests to keep trying to bring up something we can use.
*/
- turnOffUpstreamMobileConnection();
+ unrequestUpstreamMobileConnection();
break;
}
@@ -1295,7 +1158,8 @@
}
protected void setDnsForwarders(final Network network, final LinkProperties lp) {
- String[] dnsServers = mDefaultDnsServers;
+ // TODO: Set v4 and/or v6 DNS per available connectivity.
+ String[] dnsServers = mConfig.defaultIPv4DNS;
final Collection<InetAddress> dnses = lp.getDnsServers();
// TODO: Properly support the absence of DNS servers.
if (dnses != null && !dnses.isEmpty()) {
@@ -1330,96 +1194,127 @@
}
}
- private final AtomicInteger mSimBcastGenerationNumber = new AtomicInteger(0);
- private SimChangeBroadcastReceiver mBroadcastReceiver = null;
+ private class SimChangeListener {
+ private final Context mContext;
+ private final AtomicInteger mSimBcastGenerationNumber;
+ private BroadcastReceiver mBroadcastReceiver;
- private void startListeningForSimChanges() {
- if (DBG) Log.d(TAG, "startListeningForSimChanges");
- if (mBroadcastReceiver == null) {
+ SimChangeListener(Context ctx) {
+ mContext = ctx;
+ mSimBcastGenerationNumber = new AtomicInteger(0);
+ }
+
+ public int generationNumber() {
+ return mSimBcastGenerationNumber.get();
+ }
+
+ public void startListening() {
+ if (DBG) Log.d(TAG, "startListening for SIM changes");
+
+ if (mBroadcastReceiver != null) return;
+
mBroadcastReceiver = new SimChangeBroadcastReceiver(
mSimBcastGenerationNumber.incrementAndGet());
final IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mContext.registerReceiver(mBroadcastReceiver, filter, null,
+ mTetherMasterSM.getHandler());
}
- }
- private void stopListeningForSimChanges() {
- if (DBG) Log.d(TAG, "stopListeningForSimChanges");
- if (mBroadcastReceiver != null) {
+ public void stopListening() {
+ if (DBG) Log.d(TAG, "stopListening for SIM changes");
+
+ if (mBroadcastReceiver == null) return;
+
mSimBcastGenerationNumber.incrementAndGet();
mContext.unregisterReceiver(mBroadcastReceiver);
mBroadcastReceiver = null;
}
- }
- class SimChangeBroadcastReceiver extends BroadcastReceiver {
- // used to verify this receiver is still current
- final private int mGenerationNumber;
-
- // we're interested in edge-triggered LOADED notifications, so
- // ignore LOADED unless we saw an ABSENT state first
- private boolean mSimAbsentSeen = false;
-
- public SimChangeBroadcastReceiver(int generationNumber) {
- super();
- mGenerationNumber = generationNumber;
+ public boolean hasMobileHotspotProvisionApp() {
+ try {
+ if (!mContext.getResources().getString(com.android.internal.R.string.
+ config_mobile_hotspot_provision_app_no_ui).isEmpty()) {
+ Log.d(TAG, "re-evaluate provisioning");
+ return true;
+ }
+ } catch (Resources.NotFoundException e) {}
+ Log.d(TAG, "no prov-check needed for new SIM");
+ return false;
}
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DBG) {
- Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
- ", current generationNumber=" + mSimBcastGenerationNumber.get());
- }
- if (mGenerationNumber != mSimBcastGenerationNumber.get()) return;
+ private boolean isSimCardAbsent(String state) {
+ return IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(state);
+ }
- final String state =
- intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+ private boolean isSimCardLoaded(String state) {
+ return IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state);
+ }
- Log.d(TAG, "got Sim changed to state " + state + ", mSimAbsentSeen=" +
- mSimAbsentSeen);
- if (!mSimAbsentSeen && IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(state)) {
- mSimAbsentSeen = true;
+ private void startProvisionIntent(int tetherType) {
+ final Intent startProvIntent = new Intent();
+ startProvIntent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
+ startProvIntent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
+ startProvIntent.setComponent(TETHER_SERVICE);
+ mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
+ }
+
+ private class SimChangeBroadcastReceiver extends BroadcastReceiver {
+ // used to verify this receiver is still current
+ final private int mGenerationNumber;
+
+ // we're interested in edge-triggered LOADED notifications, so
+ // ignore LOADED unless we saw an ABSENT state first
+ private boolean mSimAbsentSeen = false;
+
+ public SimChangeBroadcastReceiver(int generationNumber) {
+ mGenerationNumber = generationNumber;
}
- if (mSimAbsentSeen && IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state)) {
- mSimAbsentSeen = false;
- try {
- if (mContext.getResources().getString(com.android.internal.R.string.
- config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) {
- ArrayList<Integer> tethered = new ArrayList<Integer>();
- synchronized (mPublicSync) {
- for (int i = 0; i < mTetherStates.size(); i++) {
- TetherState tetherState = mTetherStates.valueAt(i);
- if (tetherState.mLastState !=
- IControlsTethering.STATE_TETHERED) {
- continue; // Skip interfaces that aren't tethered.
- }
- String iface = mTetherStates.keyAt(i);
- int interfaceType = ifaceNameToType(iface);
- if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
- tethered.add(new Integer(interfaceType));
- }
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int currentGenerationNumber = mSimBcastGenerationNumber.get();
+
+ if (DBG) {
+ Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
+ ", current generationNumber=" + currentGenerationNumber);
+ }
+ if (mGenerationNumber != currentGenerationNumber) return;
+
+ final String state = intent.getStringExtra(
+ IccCardConstants.INTENT_KEY_ICC_STATE);
+ Log.d(TAG, "got Sim changed to state " + state + ", mSimAbsentSeen=" +
+ mSimAbsentSeen);
+
+ if (isSimCardAbsent(state)) {
+ if (!mSimAbsentSeen) mSimAbsentSeen = true;
+ return;
+ }
+
+ if (isSimCardLoaded(state) && mSimAbsentSeen) {
+ mSimAbsentSeen = false;
+
+ if (!hasMobileHotspotProvisionApp()) return;
+
+ ArrayList<Integer> tethered = new ArrayList<Integer>();
+ synchronized (mPublicSync) {
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ if (tetherState.mLastState != IControlsTethering.STATE_TETHERED) {
+ continue; // Skip interfaces that aren't tethered.
+ }
+ String iface = mTetherStates.keyAt(i);
+ int interfaceType = ifaceNameToType(iface);
+ if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
+ tethered.add(new Integer(interfaceType));
}
}
- for (int tetherType : tethered) {
- Intent startProvIntent = new Intent();
- startProvIntent.putExtra(
- ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
- startProvIntent.putExtra(
- ConnectivityManager.EXTRA_RUN_PROVISION, true);
- startProvIntent.setComponent(TETHER_SERVICE);
- mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
- }
- Log.d(TAG, "re-evaluate provisioning");
- } else {
- Log.d(TAG, "no prov-check needed for new SIM");
}
- } catch (Resources.NotFoundException e) {
- Log.d(TAG, "no prov-check needed for new SIM");
- // not defined, do nothing
+
+ for (int tetherType : tethered) {
+ startProvisionIntent(tetherType);
+ }
}
}
}
@@ -1455,12 +1350,13 @@
}
class TetherModeAliveState extends TetherMasterUtilState {
+ final SimChangeListener simChange = new SimChangeListener(mContext);
boolean mTryCell = true;
@Override
public void enter() {
// TODO: examine if we should check the return value.
turnOnMasterTetherSettings(); // may transition us out
- startListeningForSimChanges();
+ simChange.startListening();
mUpstreamNetworkMonitor.start();
mTryCell = true; // better try something first pass or crazy tests cases will fail
@@ -1470,9 +1366,9 @@
@Override
public void exit() {
- turnOffUpstreamMobileConnection();
+ unrequestUpstreamMobileConnection();
mUpstreamNetworkMonitor.stop();
- stopListeningForSimChanges();
+ simChange.stopListening();
notifyTetheredOfNewUpstreamIface(null);
handleNewUpstreamNetworkState(null);
}
@@ -1672,9 +1568,10 @@
pw.println("Tethering:");
pw.increaseIndent();
- pw.print("mUpstreamIfaceTypes:");
+ final TetheringConfiguration cfg = mConfig;
+ pw.print("preferredUpstreamIfaceTypes:");
synchronized (mPublicSync) {
- for (Integer netType : mUpstreamIfaceTypes) {
+ for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
pw.print(" " + ConnectivityManager.getNetworkTypeName(netType));
}
pw.println();
@@ -1754,4 +1651,8 @@
mTetherStates.put(iface, tetherState);
tetherState.mStateMachine.start();
}
+
+ private static String[] copy(String[] strarray) {
+ return Arrays.copyOf(strarray, strarray.length);
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
new file mode 100644
index 0000000..14d06cc
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -0,0 +1,163 @@
+/*
+ * 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.connectivity.tethering;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+
+/**
+ * A utility class to encapsulate the various tethering configuration elements.
+ *
+ * This configuration data includes elements describing upstream properties
+ * (preferred and required types of upstream connectivity as well as default
+ * DNS servers to use if none are available) and downstream properties (such
+ * as regular expressions use to match suitable downstream interfaces and the
+ * DHCPv4 ranges to use).
+ *
+ * @hide
+ */
+public class TetheringConfiguration {
+ private static final String TAG = TetheringConfiguration.class.getSimpleName();
+
+ private static final int DUN_NOT_REQUIRED = 0;
+ private static final int DUN_REQUIRED = 1;
+ private static final int DUN_UNSPECIFIED = 2;
+
+ // USB is 192.168.42.1 and 255.255.255.0
+ // Wifi is 192.168.43.1 and 255.255.255.0
+ // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
+ // with 255.255.255.0
+ // P2P is 192.168.49.1 and 255.255.255.0
+ private static final String[] DHCP_DEFAULT_RANGE = {
+ "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
+ "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
+ "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
+ "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
+ };
+
+ private final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
+
+ public final String[] tetherableUsbRegexs;
+ public final String[] tetherableWifiRegexs;
+ public final String[] tetherableBluetoothRegexs;
+ public final boolean isDunRequired;
+ public final Collection<Integer> preferredUpstreamIfaceTypes;
+ public final String[] dhcpRanges;
+ public final String[] defaultIPv4DNS;
+
+ public TetheringConfiguration(Context ctx) {
+ tetherableUsbRegexs = ctx.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_usb_regexs);
+ tetherableWifiRegexs = ctx.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_wifi_regexs);
+ tetherableBluetoothRegexs = ctx.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_bluetooth_regexs);
+
+ isDunRequired = checkDunRequired(ctx);
+ preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, isDunRequired);
+
+ dhcpRanges = getDhcpRanges(ctx);
+ defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+ }
+
+ public boolean isUsb(String iface) {
+ return matchesDownstreamRegexs(iface, tetherableUsbRegexs);
+ }
+
+ public boolean isWifi(String iface) {
+ return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
+ }
+
+ public boolean isBluetooth(String iface) {
+ return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
+ }
+
+ private static boolean checkDunRequired(Context ctx) {
+ final TelephonyManager tm = ctx.getSystemService(TelephonyManager.class);
+ final int secureSetting =
+ (tm != null) ? tm.getTetherApnRequired() : DUN_UNSPECIFIED;
+ return (secureSetting == DUN_REQUIRED);
+ }
+
+ private static Collection<Integer> getUpstreamIfaceTypes(Context ctx, boolean requiresDun) {
+ final int ifaceTypes[] = ctx.getResources().getIntArray(
+ com.android.internal.R.array.config_tether_upstream_types);
+ final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
+ for (int i : ifaceTypes) {
+ switch (i) {
+ case TYPE_MOBILE:
+ case TYPE_MOBILE_HIPRI:
+ if (requiresDun) continue;
+ break;
+ case TYPE_MOBILE_DUN:
+ if (!requiresDun) continue;
+ break;
+ }
+ upstreamIfaceTypes.add(i);
+ }
+
+ // Fix up upstream interface types for DUN or mobile. NOTE: independent
+ // of the value of |requiresDun|, cell data of one form or another is
+ // *always* an upstream, regardless of the upstream interface types
+ // specified by configuration resources.
+ if (requiresDun) {
+ if (!upstreamIfaceTypes.contains(TYPE_MOBILE_DUN)) {
+ upstreamIfaceTypes.add(TYPE_MOBILE_DUN);
+ }
+ } else {
+ if (!upstreamIfaceTypes.contains(TYPE_MOBILE)) {
+ upstreamIfaceTypes.add(TYPE_MOBILE);
+ }
+ if (!upstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI)) {
+ upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI);
+ }
+ }
+
+ return upstreamIfaceTypes;
+ }
+
+ private static boolean matchesDownstreamRegexs(String iface, String[] regexs) {
+ for (String regex : regexs) {
+ if (iface.matches(regex)) return true;
+ }
+ return false;
+ }
+
+ private static String[] getDhcpRanges(Context ctx) {
+ final String[] fromResource = ctx.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_dhcp_range);
+ if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
+ return fromResource;
+ }
+ return copy(DHCP_DEFAULT_RANGE);
+ }
+
+ private static String[] copy(String[] strarray) {
+ return Arrays.copyOf(strarray, strarray.length);
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 4c950de..23481dc 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -109,7 +109,7 @@
mNetworkMap.clear();
}
- public void mobileUpstreamRequiresDun(boolean dunRequired) {
+ public void updateMobileRequiresDun(boolean dunRequired) {
final boolean valueChanged = (mDunRequired != dunRequired);
mDunRequired = dunRequired;
if (valueChanged && mobileNetworkRequested()) {
@@ -123,7 +123,10 @@
}
public void registerMobileNetworkRequest() {
- if (mMobileNetworkCallback != null) return;
+ if (mMobileNetworkCallback != null) {
+ Log.e(TAG, "registerMobileNetworkRequest() already registered");
+ return;
+ }
final NetworkRequest.Builder builder = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
@@ -139,11 +142,10 @@
// Therefore, to avoid duplicate notifications, we only register a no-op.
mMobileNetworkCallback = new NetworkCallback();
- // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
- // moderate callback time (once timeout callbacks are implemented). This might
- // be useful for updating some UI. Additionally, we should definitely log a
- // message to aid in any subsequent debugging
- if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
+ // TODO: Change the timeout from 0 (no onUnavailable callback) to some
+ // moderate callback timeout. This might be useful for updating some UI.
+ // Additionally, we log a message to aid in any subsequent debugging.
+ Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
// The following use of the legacy type system cannot be removed until
// after upstream selection no longer finds networks by legacy type.
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/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 6719182..4798880 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -93,6 +93,11 @@
public static final int FLAG_ROUND = 1 << 8;
/**
+ * Flag: This display can show its content when non-secure keyguard is shown.
+ */
+ public static final int FLAG_SHOW_WITH_INSECURE_LOCKSCREEN = 1 << 9;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
@@ -420,6 +425,9 @@
if ((flags & FLAG_ROUND) != 0) {
msg.append(", FLAG_ROUND");
}
+ if ((flags & FLAG_SHOW_WITH_INSECURE_LOCKSCREEN) != 0) {
+ msg.append(", FLAG_SHOW_WITH_INSECURE_LOCKSCREEN");
+ }
return msg.toString();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9c762cc..7d18d36 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -16,6 +16,13 @@
package com.android.server.display;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+import static android.hardware.display.DisplayManager
+ .VIRTUAL_DISPLAY_FLAG_SHOW_WITH_INSECURE_LOCKSCREEN;
+
import com.android.internal.util.IndentingPrintWriter;
import android.Manifest;
@@ -48,6 +55,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.text.TextUtils;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -230,6 +238,9 @@
// intended for use inside of the requestGlobalDisplayStateInternal function.
private final ArrayList<Runnable> mTempDisplayStateWorkQueue = new ArrayList<Runnable>();
+ // Lists of UIDs that are present on the displays. Maps displayId -> array of UIDs.
+ private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+
public DisplayManagerService(Context context) {
super(context);
mContext = context;
@@ -394,7 +405,8 @@
LogicalDisplay display = mLogicalDisplays.get(displayId);
if (display != null) {
DisplayInfo info = display.getDisplayInfoLocked();
- if (info.hasAccess(callingUid)) {
+ if (info.hasAccess(callingUid)
+ || isUidPresentOnDisplayInternal(callingUid, displayId)) {
return info;
}
}
@@ -937,6 +949,25 @@
}
}
+ // Updates the lists of UIDs that are present on displays.
+ private void setDisplayAccessUIDsInternal(SparseArray<IntArray> newDisplayAccessUIDs) {
+ synchronized (mSyncRoot) {
+ mDisplayAccessUIDs.clear();
+ for (int i = newDisplayAccessUIDs.size() - 1; i >= 0; i--) {
+ mDisplayAccessUIDs.append(newDisplayAccessUIDs.keyAt(i),
+ newDisplayAccessUIDs.valueAt(i));
+ }
+ }
+ }
+
+ // Checks if provided UID's content is present on the display and UID has access to it.
+ private boolean isUidPresentOnDisplayInternal(int uid, int displayId) {
+ synchronized (mSyncRoot) {
+ final IntArray displayUIDs = mDisplayAccessUIDs.get(displayId);
+ return displayUIDs != null && displayUIDs.indexOf(uid) != -1;
+ }
+ }
+
private void clearViewportsLocked() {
mDefaultViewport.valid = false;
mExternalTouchViewport.valid = false;
@@ -1422,11 +1453,17 @@
throw new IllegalArgumentException("Surface can't be single-buffered");
}
- if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
- flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+ if ((flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
+ flags |= VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+
+ // Public displays can't be allowed to show content when locked.
+ if ((flags & VIRTUAL_DISPLAY_FLAG_SHOW_WITH_INSECURE_LOCKSCREEN) != 0) {
+ throw new IllegalArgumentException(
+ "Public display must not be marked as SHOW_WHEN_LOCKED_INSECURE");
+ }
}
- if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
- flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+ if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
+ flags &= ~VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
}
if (projection != null) {
@@ -1441,7 +1478,7 @@
}
if (callingUid != Process.SYSTEM_UID &&
- (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
+ (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
if (!canProjectVideo(projection)) {
throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+ "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
@@ -1449,7 +1486,7 @@
+ "display.");
}
}
- if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
+ if ((flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
if (!canProjectSecureVideo(projection)) {
throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
+ "or an appropriate MediaProjection token to create a "
@@ -1647,5 +1684,15 @@
public void setDisplayOffsets(int displayId, int x, int y) {
setDisplayOffsetsInternal(displayId, x, y);
}
+
+ @Override
+ public void setDisplayAccessUIDs(SparseArray<IntArray> newDisplayAccessUIDs) {
+ setDisplayAccessUIDsInternal(newDisplayAccessUIDs);
+ }
+
+ @Override
+ public boolean isUidPresentOnDisplay(int uid, int displayId) {
+ return isUidPresentOnDisplayInternal(uid, displayId);
+ }
}
}
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/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 287a25a..d9422ca 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -223,6 +223,9 @@
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ROUND) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_ROUND;
}
+ if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SHOW_WITH_INSECURE_LOCKSCREEN) != 0) {
+ mBaseDisplayInfo.flags |= Display.FLAG_SHOW_WITH_INSECURE_LOCKSCREEN;
+ }
mBaseDisplayInfo.type = deviceInfo.type;
mBaseDisplayInfo.address = deviceInfo.address;
mBaseDisplayInfo.name = deviceInfo.name;
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/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 9d0fde5..6a1ef85 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -333,6 +333,9 @@
}
}
}
+ if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOW_WITH_INSECURE_LOCKSCREEN) != 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_SHOW_WITH_INSECURE_LOCKSCREEN;
+ }
mInfo.type = Display.TYPE_VIRTUAL;
mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF;
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 997222c..e2e0d6b 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -41,6 +41,7 @@
import android.os.IRemoteCallback;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
+import android.security.KeyStore;
import android.os.RemoteException;
import android.os.SELinux;
import android.os.SystemClock;
@@ -63,7 +64,6 @@
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
-
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintService;
@@ -276,8 +276,18 @@
}
}
- protected void handleAuthenticated(long deviceId, int fingerId, int groupId) {
+ protected void handleAuthenticated(long deviceId, int fingerId, int groupId,
+ ArrayList<Byte> token) {
ClientMonitor client = mCurrentClient;
+ if (fingerId != 0) {
+ // Ugh...
+ final byte[] byteToken = new byte[token.size()];
+ for (int i = 0; i < token.size(); i++) {
+ byteToken[i] = token.get(i);
+ }
+ // Send to Keystore
+ KeyStore.getInstance().addAuthToken(byteToken);
+ }
if (client != null && client.onAuthenticated(fingerId, groupId)) {
removeClient(client);
}
@@ -482,7 +492,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;
}
@@ -721,11 +731,12 @@
}
@Override
- public void onAuthenticated(final long deviceId, final int fingerId, final int groupId) {
+ public void onAuthenticated(final long deviceId, final int fingerId, final int groupId,
+ ArrayList<Byte> token) {
mHandler.post(new Runnable() {
@Override
public void run() {
- handleAuthenticated(deviceId, fingerId, groupId);
+ handleAuthenticated(deviceId, fingerId, groupId, token);
}
});
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index fbb39384..3793b91 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -84,6 +84,7 @@
import android.util.Xml;
import android.view.IInputFilter;
import android.view.IInputFilterHost;
+import android.view.IWindow;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -180,6 +181,9 @@
IInputFilter mInputFilter; // guarded by mInputFilterLock
InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
+ private IWindow mFocusedWindow;
+ private boolean mFocusedWindowHasCapture;
+
private static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
private static native void nativeStart(long ptr);
@@ -226,6 +230,7 @@
private static native void nativeSetPointerIconType(long ptr, int iconId);
private static native void nativeReloadPointerIcons(long ptr);
private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
+ private static native void nativeSetPointerCapture(long ptr, boolean detached);
// Input event injection constants defined in InputDispatcher.h.
private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
@@ -1503,7 +1508,16 @@
}
}
- public void setInputWindows(InputWindowHandle[] windowHandles) {
+ public void setInputWindows(InputWindowHandle[] windowHandles,
+ InputWindowHandle focusedWindowHandle) {
+ final IWindow newFocusedWindow =
+ focusedWindowHandle != null ? focusedWindowHandle.clientWindow : null;
+ if (mFocusedWindow != newFocusedWindow) {
+ mFocusedWindow = newFocusedWindow;
+ if (mFocusedWindowHasCapture) {
+ setPointerCapture(false);
+ }
+ }
nativeSetInputWindows(mPtr, windowHandles);
}
@@ -1511,6 +1525,30 @@
nativeSetFocusedApplication(mPtr, application);
}
+ @Override
+ public void requestPointerCapture(IBinder windowToken, boolean enabled) {
+ if (mFocusedWindow == null || mFocusedWindow.asBinder() != windowToken) {
+ Slog.e(TAG, "requestPointerCapture called for a window that has no focus: "
+ + windowToken);
+ return;
+ }
+ if (mFocusedWindowHasCapture == enabled) {
+ Slog.i(TAG, "requestPointerCapture: already " + (enabled ? "enabled" : "disabled"));
+ return;
+ }
+ setPointerCapture(enabled);
+ try {
+ mFocusedWindow.dispatchPointerCaptureChanged(enabled);
+ } catch (RemoteException ex) {
+ /* ignore */
+ }
+ }
+
+ private void setPointerCapture(boolean enabled) {
+ mFocusedWindowHasCapture = enabled;
+ nativeSetPointerCapture(mPtr, enabled);
+ }
+
public void setInputDispatchMode(boolean enabled, boolean frozen) {
nativeSetInputDispatchMode(mPtr, enabled, frozen);
}
diff --git a/services/core/java/com/android/server/input/InputWindowHandle.java b/services/core/java/com/android/server/input/InputWindowHandle.java
index eb3581a..3d6f7ad 100644
--- a/services/core/java/com/android/server/input/InputWindowHandle.java
+++ b/services/core/java/com/android/server/input/InputWindowHandle.java
@@ -18,6 +18,7 @@
import android.graphics.Region;
import android.view.InputChannel;
+import android.view.IWindow;
/**
* Functions as a handle for a window that can receive input.
@@ -36,6 +37,9 @@
// The window manager's window state.
public final Object windowState;
+ // The client window.
+ public final IWindow clientWindow;
+
// The input channel associated with the window.
public InputChannel inputChannel;
@@ -93,9 +97,10 @@
private native void nativeDispose();
public InputWindowHandle(InputApplicationHandle inputApplicationHandle,
- Object windowState, int displayId) {
+ Object windowState, IWindow clientWindow, int displayId) {
this.inputApplicationHandle = inputApplicationHandle;
this.windowState = windowState;
+ this.clientWindow = clientWindow;
this.displayId = displayId;
}
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/location/GpsXtraDownloader.java b/services/core/java/com/android/server/location/GpsXtraDownloader.java
index bf779859..62332c9 100644
--- a/services/core/java/com/android/server/location/GpsXtraDownloader.java
+++ b/services/core/java/com/android/server/location/GpsXtraDownloader.java
@@ -16,22 +16,19 @@
package com.android.server.location;
+import android.net.TrafficStats;
import android.text.TextUtils;
import android.util.Log;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
-
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.IOException;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.TimeUnit;
-import libcore.io.Streams;
-
/**
* A class for downloading GPS XTRA data.
*
@@ -94,7 +91,12 @@
// load balance our requests among the available servers
while (result == null) {
- result = doDownload(mXtraServers[mNextServerIndex]);
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_GPS);
+ try {
+ result = doDownload(mXtraServers[mNextServerIndex]);
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
+ }
// increment mNextServerIndex and wrap around if necessary
mNextServerIndex++;
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/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 1e0035d..6f49df4 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -30,7 +30,6 @@
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.Slog;
import java.util.Comparator;
import java.util.Objects;
@@ -57,7 +56,15 @@
@Override
public int compare(NotificationRecord left, NotificationRecord right) {
- // First up: sufficiently important ongoing notifications of certain categories
+ // first all colorized notifications
+ boolean leftImportantColorized = isImportantColorized(left);
+ boolean rightImportantColorized = isImportantColorized(right);
+
+ if (leftImportantColorized != rightImportantColorized) {
+ return -1 * Boolean.compare(leftImportantColorized, rightImportantColorized);
+ }
+
+ // sufficiently important ongoing notifications of certain categories
boolean leftImportantOngoing = isImportantOngoing(left);
boolean rightImportantOngoing = isImportantOngoing(right);
@@ -106,6 +113,13 @@
return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
}
+ private boolean isImportantColorized(NotificationRecord record) {
+ if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) {
+ return false;
+ }
+ return record.getNotification().isColorized();
+ }
+
private boolean isImportantOngoing(NotificationRecord record) {
if (!isOngoing(record)) {
return false;
@@ -148,7 +162,6 @@
return (record.getNotification().flags & ongoingFlags) != 0;
}
-
private Class<? extends Notification.Style> getNotificationStyle(NotificationRecord record) {
String templateClass =
record.getNotification().extras.getString(Notification.EXTRA_TEMPLATE);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9018302..218b571 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,9 +51,11 @@
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;
+import android.app.NotificationChannelGroup;
import android.app.backup.BackupManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
@@ -165,6 +168,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 +202,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 +233,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 +282,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 +387,7 @@
private void loadPolicyFile() {
if (DBG) Slog.d(TAG, "loadPolicyFile");
- synchronized(mPolicyFile) {
+ synchronized (mPolicyFile) {
FileInputStream infile = null;
try {
@@ -491,7 +506,7 @@
@Override
public void onSetDisabled(int status) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mDisableNotificationEffects =
(status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
if (disableNotificationEffects(null) != null) {
@@ -519,7 +534,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 +542,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 +563,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 +599,7 @@
@Override
public void clearEffects() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
if (DBG) Slog.d(TAG, "clearEffects");
clearSoundLocked();
clearVibrateLocked();
@@ -601,7 +616,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 +627,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 +650,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 +693,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) {
@@ -702,16 +740,19 @@
int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_ALL);
String pkgList[] = null;
+ int uidList[] = null;
boolean removingPackage = queryRemove &&
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
if (DBG) Slog.i(TAG, "action=" + action + " removing=" + removingPackage);
if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
} else if (action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
reason = REASON_PACKAGE_SUSPENDED;
} else if (queryRestart) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+ uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
} else {
Uri uri = intent.getData();
if (uri == null) {
@@ -743,8 +784,8 @@
}
}
pkgList = new String[]{pkgName};
+ uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
}
-
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
if (cancelNotifications) {
@@ -756,7 +797,8 @@
mListeners.onPackagesChanged(removingPackage, pkgList);
mNotificationAssistants.onPackagesChanged(removingPackage, pkgList);
mConditionProviders.onPackagesChanged(removingPackage, pkgList);
- mRankingHelper.onPackagesChanged(removingPackage, changeUserId, pkgList);
+ mRankingHelper.onPackagesChanged(removingPackage, changeUserId, pkgList, uidList);
+ savePolicyFile();
}
}
};
@@ -945,7 +987,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,
@@ -953,11 +996,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 {
@@ -987,7 +1032,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();
}
}
@@ -1015,7 +1060,7 @@
mGroupHelper = new GroupHelper(new GroupHelper.Callback() {
@Override
public void addAutoGroup(String key) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
addAutogroupKeyLocked(key);
}
mRankingHandler.requestSort(false);
@@ -1023,7 +1068,7 @@
@Override
public void removeAutoGroup(String key) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
removeAutogroupKeyLocked(key);
}
mRankingHandler.requestSort(false);
@@ -1036,7 +1081,7 @@
@Override
public void removeAutoGroupSummary(int userId, String pkg) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
clearAutogroupSummaryLocked(userId, pkg);
}
}
@@ -1047,8 +1092,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();
@@ -1122,6 +1167,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(
@@ -1130,7 +1179,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);
}
@@ -1232,6 +1282,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) {
@@ -1510,6 +1589,34 @@
}
@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 createNotificationChannelGroups(String pkg,
+ ParceledListSlice channelGroupList) throws RemoteException {
+ checkCallerIsSystemOrSameApp(pkg);
+ List<NotificationChannelGroup> groups = channelGroupList.getList();
+ final int groupSize = groups.size();
+ for (int i = 0; i < groupSize; i++) {
+ final NotificationChannelGroup group = groups.get(i);
+ Preconditions.checkNotNull(group, "group in list is null");
+ mRankingHelper.createNotificationChannelGroup(pkg, Binder.getCallingUid(), group,
+ true /* fromTargetApp */);
+ }
+ savePolicyFile();
+ }
+
+ @Override
public void createNotificationChannels(String pkg,
ParceledListSlice channelsList) throws RemoteException {
checkCallerIsSystemOrSameApp(pkg);
@@ -1527,14 +1634,16 @@
@Override
public NotificationChannel getNotificationChannel(String pkg, String channelId) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannel(pkg, Binder.getCallingUid(), channelId);
+ return mRankingHelper.getNotificationChannel(
+ pkg, Binder.getCallingUid(), channelId, false /* includeDeleted */);
}
@Override
public NotificationChannel getNotificationChannelForPackage(String pkg, int uid,
- String channelId) {
+ String channelId, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannel(pkg, uid, channelId);
+ return mRankingHelper.getNotificationChannel
+ (pkg, uid, channelId, includeDeleted);
}
@Override
@@ -1553,30 +1662,55 @@
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
public ParceledListSlice<NotificationChannel> getNotificationChannelsForPackage(String pkg,
- int uid) {
+ int uid, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannels(pkg, uid);
+ return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted);
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
+ String pkg, int uid, boolean includeDeleted) {
+ checkCallerIsSystem();
+ return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted);
}
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannels(pkg, Binder.getCallingUid());
+ return mRankingHelper.getNotificationChannels(
+ pkg, Binder.getCallingUid(), false /* includeDeleted */);
}
+
+ @Override
+ public void clearData(String packageName, int uid) throws RemoteException {
+ checkCallerIsSystem();
+
+ // Cancel posted notifications
+ cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
+
+ // Listener & assistant
+ mListeners.onPackagesChanged(true, new String[] {packageName});
+ mNotificationAssistants.onPackagesChanged(true, new String[] {packageName});
+
+ // Zen
+ mConditionProviders.onPackagesChanged(true, new String[] {packageName});
+
+ // Reset notification preferences
+ mRankingHelper.onPackagesChanged(true, UserHandle.getCallingUserId(),
+ new String[] {packageName}, new int[] {uid});
+
+ savePolicyFile();
+ }
+
+
/**
* System-only API for getting a list of current (i.e. not cleared) notifications.
*
@@ -1596,7 +1730,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++) {
@@ -1610,6 +1744,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
@@ -1622,7 +1759,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,
@@ -1637,10 +1774,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) {
@@ -1665,7 +1800,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());
@@ -1732,7 +1866,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;
@@ -1795,7 +1929,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;
@@ -1850,7 +1984,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);
}
@@ -1867,38 +2001,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);
@@ -1919,7 +2038,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) "
@@ -1948,7 +2067,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();
@@ -1969,11 +2088,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
@@ -1994,7 +2143,7 @@
@Override
public int getHintsFromListener(INotificationListener token) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
return mListenerHints;
}
}
@@ -2004,7 +2153,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();
@@ -2025,7 +2174,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);
@@ -2344,7 +2493,7 @@
enforceSystemOrSystemUI("grant notification policy access");
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mPolicyAccess.put(pkg, granted);
}
} finally {
@@ -2379,7 +2528,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++) {
@@ -2401,7 +2550,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);
@@ -2418,7 +2567,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mNotificationAssistants.checkServiceTokenLocked(token);
for (Adjustment adjustment : adjustments) {
NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
@@ -2459,15 +2608,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
@@ -2475,7 +2618,7 @@
INotificationListener token, String pkg) throws RemoteException {
ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
- return mRankingHelper.getNotificationChannels(pkg, uid);
+ return mRankingHelper.getNotificationChannels(pkg, uid, false /* includeDeleted */);
}
};
@@ -2492,8 +2635,9 @@
final ArrayList<SnoozeCriterion> snoozeCriterionList =
adjustment.getSignals().getParcelableArrayList(Adjustment.KEY_SNOOZE_CRITERIA);
if (!TextUtils.isEmpty(overrideChannelId)) {
- n.setNotificationChannelOverride(mRankingHelper.getNotificationChannel(
- n.sbn.getPackageName(), n.sbn.getUid(), overrideChannelId));
+ n.updateNotificationChannel(mRankingHelper.getNotificationChannel(
+ n.sbn.getPackageName(), n.sbn.getUid(), overrideChannelId,
+ false /* includeDeleted */));
}
n.setPeopleOverride(people);
n.setSnoozeCriteria(snoozeCriterionList);
@@ -2523,9 +2667,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);
}
}
@@ -2534,7 +2677,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
@@ -2574,18 +2717,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));
@@ -2640,7 +2783,7 @@
}
}
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
if (!zenOnly) {
N = mNotificationList.size();
if (N > 0) {
@@ -2678,19 +2821,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) {
@@ -2785,14 +2926,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,
@@ -2829,7 +2970,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);
@@ -2846,16 +2987,16 @@
+ " id=" + id + " notification=" + notification);
}
final NotificationChannel channel = mRankingHelper.getNotificationChannelWithFallback(pkg,
- callingUid, notification.getChannel());
+ 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) {
@@ -2912,10 +3053,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;
@@ -2932,7 +3070,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) {
@@ -3027,9 +3168,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);
@@ -3038,12 +3179,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();
@@ -3102,9 +3241,7 @@
}
buzzBeepBlinkLocked(r);
- }
- } finally {
- synchronized (mEnqueuedNotifications) {
+ } finally {
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
@@ -3161,8 +3298,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);
}
}
@@ -3431,7 +3583,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;
@@ -3459,16 +3611,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);
@@ -3477,7 +3633,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;
}
@@ -3516,7 +3673,7 @@
}
private void handleSendRankingUpdate() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mListeners.notifyRankingUpdateLocked();
}
}
@@ -3535,19 +3692,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)
{
@@ -3633,6 +3794,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);
@@ -3650,50 +3822,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) {
@@ -3709,10 +3882,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());
@@ -3747,10 +3919,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.
@@ -3765,13 +3938,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();
@@ -3810,120 +3983,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) {
@@ -3938,47 +4120,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;
@@ -3992,16 +4167,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);
}
}
@@ -4050,19 +4235,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
@@ -4077,7 +4292,7 @@
}
private void updateNotificationPulse() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
updateLightsLocked();
}
}
@@ -4157,9 +4372,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)) {
@@ -4181,9 +4397,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]);
@@ -4194,7 +4411,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) {
@@ -4381,7 +4598,7 @@
public void onServiceAdded(ManagedServiceInfo info) {
final INotificationListener listener = (INotificationListener) info.service;
final NotificationRankingUpdate update;
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
update = makeRankingUpdateLocked(info);
}
try {
@@ -4576,12 +4793,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 5c1e99c..2e35e3d 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -16,19 +16,28 @@
package com.android.server.notification;
import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
import android.content.pm.ParceledListSlice;
public interface RankingConfig {
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 createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
+ boolean fromTargetApp);
+ ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+ int uid, boolean includeDeleted);
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel);
- NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
- NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, String channelId);
+ NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
+ NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
- ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid);
+ void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
+ void permanentlyDeleteNotificationChannels(String pkg, int uid);
+ ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted);
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 3fcce3c..01cca2d0 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -18,10 +18,12 @@
import static android.app.NotificationManager.IMPORTANCE_NONE;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -29,7 +31,6 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.os.Build;
-import android.os.Process;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService.Ranking;
import android.text.TextUtils;
@@ -59,6 +60,7 @@
private static final String TAG_RANKING = "ranking";
private static final String TAG_PACKAGE = "package";
private static final String TAG_CHANNEL = "channel";
+ private static final String TAG_GROUP = "channelGroup";
private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
@@ -67,10 +69,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,9 +173,9 @@
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();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
@@ -181,6 +185,17 @@
}
String tagName = parser.getName();
+ // Channel groups
+ if (TAG_GROUP.equals(tagName)) {
+ String id = parser.getAttributeValue(null, ATT_ID);
+ CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
+ if (!TextUtils.isEmpty(id)) {
+ final NotificationChannelGroup group =
+ new NotificationChannelGroup(id, groupName);
+ r.groups.put(id, group);
+ }
+ }
+ // Channels
if (TAG_CHANNEL.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
@@ -215,11 +230,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 +244,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 +314,8 @@
}
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
+ || r.groups.size() > 0;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -311,11 +328,16 @@
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));
}
+ for (NotificationChannelGroup group : r.groups.values()) {
+ group.writeXml(out);
+ }
+
for (NotificationChannel channel : r.channels.values()) {
channel.writeXml(out);
}
@@ -396,6 +418,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 +447,32 @@
}
@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 createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
+ boolean fromTargetApp) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(group);
+ Preconditions.checkNotNull(group.getId());
+ Preconditions.checkNotNull(group.getName());
+ Record r = getOrCreateRecord(pkg, uid);
+ if (r == null) {
+ throw new IllegalArgumentException("Invalid package");
+ }
+ r.groups.put(group.getId(), group);
+ updateConfig();
+ }
+
+ @Override
public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp) {
Preconditions.checkNotNull(pkg);
@@ -432,9 +486,17 @@
if (IMPORTANCE_NONE == r.importance) {
throw new IllegalArgumentException("Package blocked");
}
- if (r.channels.containsKey(channel.getId()) || channel.getName().equals(
- mContext.getString(R.string.default_notification_channel_label))) {
- // Channel already exists, no-op.
+ if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
+ throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
+ }
+
+ NotificationChannel existing = r.channels.get(channel.getId());
+ // Keep existing settings
+ if (existing != null) {
+ if (existing.isDeleted()) {
+ existing.setDeleted(false);
+ updateConfig();
+ }
return;
}
if (channel.getImportance() < NotificationManager.IMPORTANCE_NONE
@@ -450,6 +512,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();
}
@@ -471,7 +536,7 @@
throw new IllegalArgumentException("Invalid package");
}
NotificationChannel channel = r.channels.get(updatedChannel.getId());
- if (channel == null) {
+ if (channel == null || channel.isDeleted()) {
throw new IllegalArgumentException("Channel does not exist");
}
if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
@@ -489,7 +554,7 @@
throw new IllegalArgumentException("Invalid package");
}
NotificationChannel channel = r.channels.get(updatedChannel.getId());
- if (channel == null) {
+ if (channel == null || channel.isDeleted()) {
throw new IllegalArgumentException("Channel does not exist");
}
@@ -519,6 +584,10 @@
if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SHOW_BADGE) == 0) {
channel.setShowBadge(updatedChannel.canShowBadge());
}
+ if (updatedChannel.isDeleted()) {
+ channel.setDeleted(true);
+ }
+ // Assistant cannot change the group
r.channels.put(channel.getId(), channel);
updateConfig();
@@ -526,13 +595,13 @@
@Override
public NotificationChannel getNotificationChannelWithFallback(String pkg, int uid,
- String channelId) {
+ String channelId, boolean includeDeleted) {
Record r = getOrCreateRecord(pkg, uid);
if (channelId == null) {
channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
}
NotificationChannel channel = r.channels.get(channelId);
- if (channel != null) {
+ if (channel != null && (includeDeleted || !channel.isDeleted())) {
return channel;
} else {
return r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
@@ -540,16 +609,21 @@
}
@Override
- public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId) {
+ public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+ boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
Record r = getOrCreateRecord(pkg, uid);
if (r == null) {
- throw new IllegalArgumentException("Invalid package");
+ return null;
}
if (channelId == null) {
channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
}
- return r.channels.get(channelId);
+ final NotificationChannel nc = r.channels.get(channelId);
+ if (nc != null && (includeDeleted || !nc.isDeleted())) {
+ return nc;
+ }
+ return null;
}
@Override
@@ -558,24 +632,87 @@
Preconditions.checkNotNull(channelId);
Record r = getRecord(pkg, uid);
if (r == null) {
- throw new IllegalArgumentException("Invalid package");
+ return;
}
- if (r != null) {
- r.channels.remove(channelId);
+ NotificationChannel channel = r.channels.get(channelId);
+ if (channel != null) {
+ channel.setDeleted(true);
}
}
@Override
- public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid) {
+ @VisibleForTesting
+ public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(channelId);
+ Record r = getRecord(pkg, uid);
+ if (r == null) {
+ return;
+ }
+ r.channels.remove(channelId);
+ }
+
+ @Override
+ public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
+ Preconditions.checkNotNull(pkg);
+ Record r = getRecord(pkg, uid);
+ if (r == null) {
+ return;
+ }
+ int N = r.channels.size() - 1;
+ for (int i = N; i >= 0; i--) {
+ String key = r.channels.keyAt(i);
+ if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
+ r.channels.remove(key);
+ }
+ }
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+ int uid, boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
+ List<NotificationChannelGroup> groups = new ArrayList<>();
+ Record r = getRecord(pkg, uid);
+ if (r == null) {
+ return ParceledListSlice.emptyList();
+ }
+ NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (includeDeleted || !nc.isDeleted()) {
+ if (nc.getGroup() != null) {
+ // lazily populate channel list
+ NotificationChannelGroup ncg = r.groups.get(nc.getGroup());
+ ncg.addChannel(nc);
+ } else {
+ nonGrouped.addChannel(nc);
+ }
+ }
+ }
+ groups.addAll(r.groups.values());
+ if (nonGrouped.getChannels().size() > 0) {
+ groups.add(nonGrouped);
+ }
+ return new ParceledListSlice<>(groups);
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+ boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
List<NotificationChannel> channels = new ArrayList<>();
Record r = getRecord(pkg, uid);
if (r == null) {
- throw new IllegalArgumentException("Invalid package");
+ return ParceledListSlice.emptyList();
}
int N = r.channels.size();
for (int i = 0; i < N; i++) {
- channels.add(r.channels.valueAt(i));
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (includeDeleted || !nc.isDeleted()) {
+ channels.add(nc);
+ }
}
return new ParceledListSlice<>(channels);
}
@@ -627,7 +764,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));
@@ -644,6 +781,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);
@@ -651,6 +790,12 @@
pw.print(" ");
pw.println(channel);
}
+ for (NotificationChannelGroup group : r.groups.values()) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(" ");
+ pw.println(group);
+ }
}
}
}
@@ -680,9 +825,15 @@
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());
}
+ for (NotificationChannelGroup group : r.groups.values()) {
+ record.put("group", group.toJson());
+ }
} catch (JSONException e) {
// pass
}
@@ -738,30 +889,46 @@
return packageBans;
}
- public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList) {
- if (removingPackage || pkgList == null || pkgList.length == 0) {
+ public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
+ int[] uidList) {
+ if (pkgList == null || pkgList.length == 0) {
return; // nothing to do
}
boolean updated = false;
- for (String pkg : pkgList) {
- final Record r = mRestoredWithoutUids.get(pkg);
- if (r != null) {
+ if (removingPackage) {
+ // Remove notification settings for uninstalled package
+ int size = Math.min(pkgList.length, uidList.length);
+ for (int i = 0; i < size; i++) {
+ final String pkg = pkgList[i];
+ final int uid = uidList[i];
+ mRecords.remove(recordKey(pkg, uid));
+ mRestoredWithoutUids.remove(pkg);
+ updated = true;
+ }
+ } else {
+ for (String pkg : pkgList) {
+ // Package install
+ final Record r = mRestoredWithoutUids.get(pkg);
+ if (r != null) {
+ try {
+ r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
+ mRestoredWithoutUids.remove(pkg);
+ mRecords.put(recordKey(r.pkg, r.uid), r);
+ updated = true;
+ } catch (NameNotFoundException e) {
+ // noop
+ }
+ }
+ // Package upgrade
try {
- r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
- mRestoredWithoutUids.remove(pkg);
- mRecords.put(recordKey(r.pkg, r.uid), r);
- updated = true;
+ Record fullRecord = getRecord(pkg,
+ mPm.getPackageUidAsUser(pkg, changeUserId));
+ if (fullRecord != null) {
+ clampDefaultChannel(fullRecord);
+ }
} catch (NameNotFoundException e) {
- // noop
}
}
- try {
- Record fullRecord = getRecord(pkg,
- mPm.getPackageUidAsUser(pkg, changeUserId));
- if (fullRecord != null) {
- clampDefaultChannel(fullRecord);
- }
- } catch (NameNotFoundException e) {}
}
if (updated) {
@@ -777,7 +944,9 @@
int importance = DEFAULT_IMPORTANCE;
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
+ boolean showBadge = DEFAULT_SHOW_BADGE;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
+ ArrayMap<String, NotificationChannelGroup> groups = 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/EphemeralApplicationRegistry.java b/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
index 1e3e0ca..3d946e0 100644
--- a/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
+++ b/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
@@ -17,6 +17,7 @@
package com.android.server.pm;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.EphemeralApplicationInfo;
import android.content.pm.PackageParser;
import android.content.pm.PackageUserState;
@@ -27,10 +28,13 @@
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Environment;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
@@ -51,6 +55,7 @@
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -62,7 +67,7 @@
class EphemeralApplicationRegistry {
private static final boolean DEBUG = false;
- private static final boolean ENABLED = false;
+ private static final boolean ENABLED = true;
private static final String LOG_TAG = "EphemeralAppRegistry";
@@ -90,6 +95,16 @@
@GuardedBy("mService.mPackages")
private SparseArray<List<UninstalledEphemeralAppState>> mUninstalledEphemeralApps;
+ /**
+ * Automatic grants for access to instant app metadata.
+ * The key is the target application UID.
+ * The value is a set of instant app UIDs.
+ * UserID -> TargetAppId -> InstantAppId
+ */
+ private SparseArray<SparseArray<SparseBooleanArray>> mEphemeralGrants;
+ /** The set of all installed instant apps. UserID -> AppID */
+ private SparseArray<SparseBooleanArray> mInstalledEphemeralAppUids;
+
public EphemeralApplicationRegistry(PackageManagerService service) {
mService = service;
}
@@ -188,7 +203,11 @@
}
// Propagate permissions before removing any state
- propagateEphemeralAppPermissionsIfNeeded(pkg, userId);
+ // TODO: Fix this later
+ // propagateEphemeralAppPermissionsIfNeeded(pkg, userId);
+ if (pkg.applicationInfo.isEphemeralApp()) {
+ addEphemeralAppLPw(userId, ps.appId);
+ }
// Remove the in-memory state
if (mUninstalledEphemeralApps != null) {
@@ -248,9 +267,11 @@
if (pkg.applicationInfo.isEphemeralApp()) {
// Add a record for an uninstalled ephemeral app
addUninstalledEphemeralAppLPw(pkg, userId);
+ removeEphemeralAppLPw(userId, ps.appId);
} else {
// Deleting an app prunes all ephemeral state such as cookie
deleteDir(getEphemeralApplicationDir(pkg.packageName, userId));
+ removeAppLPw(userId, ps.appId);
}
}
}
@@ -262,9 +283,114 @@
if (mUninstalledEphemeralApps != null) {
mUninstalledEphemeralApps.remove(userId);
}
+ if (mInstalledEphemeralAppUids != null) {
+ mInstalledEphemeralAppUids.remove(userId);
+ }
+ if (mEphemeralGrants != null) {
+ mEphemeralGrants.remove(userId);
+ }
deleteDir(getEphemeralApplicationsDir(userId));
}
+ public boolean isEphemeralAccessGranted(int userId, int targetAppId, int ephemeralAppId) {
+ if (mEphemeralGrants == null) {
+ return false;
+ }
+ final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
+ if (targetAppList == null) {
+ return false;
+ }
+ final SparseBooleanArray ephemeralGrantList = targetAppList.get(targetAppId);
+ if (ephemeralGrantList == null) {
+ return false;
+ }
+ return ephemeralGrantList.get(ephemeralAppId);
+ }
+
+ public void grantEphemeralAccessLPw(int userId, Intent intent,
+ int targetAppId, int ephemeralAppId) {
+ if (mInstalledEphemeralAppUids == null) {
+ return; // no ephemeral apps installed; no need to grant
+ }
+ SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
+ if (ephemeralAppList == null || !ephemeralAppList.get(ephemeralAppId)) {
+ return; // ephemeral app id isn't installed; no need to grant
+ }
+ if (ephemeralAppList.get(targetAppId)) {
+ return; // target app id is an ephemeral app; no need to grant
+ }
+ if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
+ final Set<String> categories = intent.getCategories();
+ if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
+ return; // launched via VIEW/BROWSABLE intent; no need to grant
+ }
+ }
+ if (mEphemeralGrants == null) {
+ mEphemeralGrants = new SparseArray<>();
+ }
+ SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
+ if (targetAppList == null) {
+ targetAppList = new SparseArray<>();
+ mEphemeralGrants.put(userId, targetAppList);
+ }
+ SparseBooleanArray ephemeralGrantList = targetAppList.get(targetAppId);
+ if (ephemeralGrantList == null) {
+ ephemeralGrantList = new SparseBooleanArray();
+ targetAppList.put(targetAppId, ephemeralGrantList);
+ }
+ ephemeralGrantList.put(ephemeralAppId, true /*granted*/);
+ }
+
+ public void addEphemeralAppLPw(int userId, int ephemeralAppId) {
+ if (mInstalledEphemeralAppUids == null) {
+ mInstalledEphemeralAppUids = new SparseArray<>();
+ }
+ SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
+ if (ephemeralAppList == null) {
+ ephemeralAppList = new SparseBooleanArray();
+ mInstalledEphemeralAppUids.put(userId, ephemeralAppList);
+ }
+ ephemeralAppList.put(ephemeralAppId, true /*installed*/);
+ }
+
+ private void removeEphemeralAppLPw(int userId, int ephemeralAppId) {
+ // remove from the installed list
+ if (mInstalledEphemeralAppUids == null) {
+ return; // no ephemeral apps on the system
+ }
+ final SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
+ if (ephemeralAppList == null) {
+ Slog.w(LOG_TAG, "Remove ephemeral not in install list");
+ return;
+ } else {
+ ephemeralAppList.delete(ephemeralAppId);
+ }
+ // remove any grants
+ if (mEphemeralGrants == null) {
+ return; // no grants on the system
+ }
+ final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
+ if (targetAppList == null) {
+ return; // no grants for this user
+ }
+ final int numApps = targetAppList.size();
+ for (int i = targetAppList.size() - 1; i >= 0; --i) {
+ targetAppList.valueAt(i).delete(ephemeralAppId);
+ }
+ }
+
+ private void removeAppLPw(int userId, int targetAppId) {
+ // remove from the installed list
+ if (mEphemeralGrants == null) {
+ return; // no grants on the system
+ }
+ final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
+ if (targetAppList == null) {
+ return; // no grants for this user
+ }
+ targetAppList.delete(targetAppId);
+ }
+
private void addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId) {
EphemeralApplicationInfo uninstalledApp = createEphemeralAppInfoForPackage(pkg, userId);
if (uninstalledApp == null) {
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 2ddf6db..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;
@@ -231,11 +232,20 @@
long ident = injectClearCallingIdentity();
try {
UserInfo callingUserInfo = mUm.getUserInfo(callingUserId);
+ if (callingUserInfo.isManagedProfile()) {
+ // 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);
if (targetUserInfo == null
|| targetUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID
|| targetUserInfo.profileGroupId != callingUserInfo.profileGroupId) {
- throw new SecurityException(message);
+ throw new SecurityException(message + " for unrelated profile " + targetUserId);
}
} finally {
injectRestoreCallingIdentity(ident);
@@ -277,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),
@@ -287,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 for unrelated profile " + user);
+ ensureInUserProfiles(callingPackage, user, "Cannot resolve activity");
if (!isUserEnabled(user)) {
return null;
}
@@ -307,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 for unrelated profile " + user);
+ private ParceledListSlice<ResolveInfo> queryActivitiesForUser(String callingPackage,
+ Intent intent, UserHandle user) {
+ ensureInUserProfiles(callingPackage, user, "Cannot retrieve activities");
if (!isUserEnabled(user)) {
return null;
}
@@ -354,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 for unrelated profile " + user);
+ ensureInUserProfiles(callingPackage, user, "Cannot check package");
if (!isUserEnabled(user)) {
return false;
}
@@ -375,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 for unrelated profile " + user);
+ ensureInUserProfiles(callingPackage, user, "Cannot check package");
if (!isUserEnabled(user)) {
return null;
}
@@ -399,7 +413,7 @@
private void ensureShortcutPermission(@NonNull String callingPackage, int userId) {
verifyCallingPackage(callingPackage);
- ensureInUserProfiles(userId, "Cannot access shortcuts for unrelated profile " + userId);
+ ensureInUserProfiles(callingPackage, userId, "Cannot access shortcuts");
if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
callingPackage)) {
@@ -475,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 for unrelated profile " + userId);
+ ensureInUserProfiles(callingPackage, userId, "Cannot start activity");
if (!isUserEnabled(userId)) {
throw new IllegalStateException("Cannot start a shortcut for disabled profile "
@@ -526,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 for unrelated profile " + user);
+ ensureInUserProfiles(callingPackage , user, "Cannot check component");
if (!isUserEnabled(user)) {
return false;
}
@@ -547,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 for unrelated profile " + user);
+ ensureInUserProfiles(callingPackage, user, "Cannot start activity");
if (!isUserEnabled(user)) {
throw new IllegalStateException("Cannot start activity for disabled profile " + user);
}
@@ -600,9 +616,9 @@
}
@Override
- public void showAppDetailsAsUser(ComponentName component, Rect sourceBounds,
- Bundle opts, UserHandle user) throws RemoteException {
- ensureInUserProfiles(user, "Cannot show app details for unrelated profile " + user);
+ 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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index d516acf..da6a67e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -52,6 +52,7 @@
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
@@ -869,8 +870,8 @@
}
@Override
- public void uninstall(String packageName, String callerPackageName, int flags,
- IntentSender statusReceiver, int userId) {
+ public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
+ IntentSender statusReceiver, int userId) throws RemoteException {
final int callingUid = Binder.getCallingUid();
mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
@@ -884,24 +885,24 @@
callerPackageName);
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
- statusReceiver, packageName, isDeviceOwner, userId);
+ statusReceiver, versionedPackage.getPackageName(), isDeviceOwner, userId);
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
- mPm.deletePackage(packageName, adapter.getBinder(), userId, flags);
+ mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
} else if (isDeviceOwner) {
// Allow the DeviceOwner to silently delete packages
// Need to clear the calling identity to get DELETE_PACKAGES permission
long ident = Binder.clearCallingIdentity();
try {
- mPm.deletePackage(packageName, adapter.getBinder(), userId, flags);
+ mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
} finally {
Binder.restoreCallingIdentity(ident);
}
} else {
// Take a short detour to confirm with user
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
- intent.setData(Uri.fromParts("package", packageName, null));
+ intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());
adapter.onUserActionRequired(intent);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d1aed3e..067a136 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -894,7 +894,7 @@
// This is kind of hacky; we're creating a half-parsed package that is
// straddled between the inherited and staged APKs.
- final PackageLite pkg = new PackageLite(null, baseApk, null,
+ final PackageLite pkg = new PackageLite(null, baseApk, null, null,
splitPaths.toArray(new String[splitPaths.size()]), null);
final boolean isForwardLocked =
(params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6669889..abf3526 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -16,7 +16,11 @@
package com.android.server.pm;
+import static android.Manifest.permission.DELETE_PACKAGES;
+import static android.Manifest.permission.INSTALL_PACKAGES;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
+import static android.Manifest.permission.REQUEST_INSTALL_PACKAGES;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -68,6 +72,7 @@
import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL;
import static android.content.pm.PackageManager.MOVE_FAILED_DEVICE_ADMIN;
import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR;
@@ -80,7 +85,6 @@
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
-
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
@@ -160,10 +164,12 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VerifierInfo;
+import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.hardware.display.DisplayManager;
@@ -218,6 +224,7 @@
import android.util.Log;
import android.util.LogPrinter;
import android.util.MathUtils;
+import android.util.PackageUtils;
import android.util.Pair;
import android.util.PrintStreamPrinter;
import android.util.Slog;
@@ -415,6 +422,8 @@
static final int REMOVE_CHATTY = 1<<16;
static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<17;
+ private static final String STATIC_SHARED_LIB_DELIMITER = "_";
+
private static final int[] EMPTY_INT_ARRAY = new int[0];
/**
@@ -697,6 +706,8 @@
boolean mFirstBoot;
+ PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
+
// System configuration read by SystemConfig.
final int[] mGlobalGids;
final SparseArray<ArraySet<String>> mSystemPermissions;
@@ -710,16 +721,21 @@
public static final class SharedLibraryEntry {
public final String path;
public final String apk;
+ public final SharedLibraryInfo info;
- SharedLibraryEntry(String _path, String _apk) {
+ SharedLibraryEntry(String _path, String _apk, String name, int version, int type,
+ String declaringPackageName, int declaringPackageVersionCode) {
path = _path;
apk = _apk;
+ info = new SharedLibraryInfo(name, version, type, new VersionedPackage(
+ declaringPackageName, declaringPackageVersionCode), null);
}
}
// Currently known shared libraries.
- final ArrayMap<String, SharedLibraryEntry> mSharedLibraries =
- new ArrayMap<String, SharedLibraryEntry>();
+ final ArrayMap<String, SparseArray<SharedLibraryEntry>> mSharedLibraries = new ArrayMap<>();
+ final ArrayMap<String, SparseArray<SharedLibraryEntry>> mStaticLibsByDeclaringPackage =
+ new ArrayMap<>();
// All available activities, for your resolving pleasure.
final ActivityIntentResolver mActivities =
@@ -1763,7 +1779,8 @@
}
// Send installed broadcasts if the install/update is not ephemeral
- if (!isEphemeral(res.pkg)) {
+ // and the package is not a static shared lib.
+ if (!isEphemeral(res.pkg) && res.pkg.staticSharedLibName == null) {
mProcessLoggingHandler.invalidateProcessLoggingBaseApkHash(res.pkg.baseCodePath);
// Send added for users that see the package for the first time
@@ -1849,6 +1866,18 @@
res.removedInfo.args.doPostDeleteLI(true);
}
}
+
+ if (!isEphemeral(res.pkg)) {
+ // Notify DexManager that the package was installed for new users.
+ // The updated users should already be indexed and the package code paths
+ // should not change.
+ // Don't notify the manager for ephemeral apps as they are not expected to
+ // survive long enough to benefit of background optimizations.
+ for (int userId : firstUsers) {
+ PackageInfo info = getPackageInfo(packageName, /*flags*/ 0, userId);
+ mDexManager.notifyPackageInstalled(info, userId);
+ }
+ }
}
// If someone is watching installs - notify them
@@ -1940,9 +1969,10 @@
final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(fsUuid);
for (PackageSetting ps : packages) {
Slog.d(TAG, "Destroying " + ps.name + " because volume was forgotten");
- deletePackage(ps.name, new LegacyPackageDeleteObserver(null).getBinder(),
+ deletePackageVersioned(new VersionedPackage(ps.name,
+ PackageManager.VERSION_CODE_HIGHEST),
+ new LegacyPackageDeleteObserver(null).getBinder(),
UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS);
-
// Try very hard to release any references to this package
// so we don't risk the system server being killed due to
// open FDs
@@ -2224,6 +2254,7 @@
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);
+ mEphemeralApplicationRegistry = new EphemeralApplicationRegistry(this);
File dataDir = Environment.getDataDirectory();
mAppInstallDir = new File(dataDir, "app");
@@ -2250,9 +2281,12 @@
}
ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
- for (int i=0; i<libConfig.size(); i++) {
- mSharedLibraries.put(libConfig.keyAt(i),
- new SharedLibraryEntry(libConfig.valueAt(i), null));
+ final int builtInLibCount = libConfig.size();
+ for (int i = 0; i < builtInLibCount; i++) {
+ String name = libConfig.keyAt(i);
+ String path = libConfig.valueAt(i);
+ addSharedLibraryLPw(path, null, name, SharedLibraryInfo.VERSION_UNDEFINED,
+ SharedLibraryInfo.TYPE_BUILTIN, PLATFORM_PACKAGE_NAME, 0);
}
mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
@@ -2317,32 +2351,38 @@
// to compile them only when we come across an app that uses them (there's
// already logic for that in scanPackageLI) but that adds some complexity.
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
- final String lib = libEntry.path;
- if (lib == null) {
- continue;
- }
-
- try {
- // Shared libraries do not have profiles so we perform a full
- // AOT compilation (if needed).
- int dexoptNeeded = DexFile.getDexOptNeeded(
- lib, dexCodeInstructionSet,
- getCompilerFilterForReason(REASON_SHARED_APK),
- false /* newProfile */);
- if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
- mInstaller.dexopt(lib, Process.SYSTEM_UID, "*",
- dexCodeInstructionSet, dexoptNeeded, null,
- DEXOPT_PUBLIC,
- getCompilerFilterForReason(REASON_SHARED_APK),
- StorageManager.UUID_PRIVATE_INTERNAL,
- SKIP_SHARED_LIBRARY_CHECK);
+ final int libCount = mSharedLibraries.size();
+ for (int i = 0; i < libCount; i++) {
+ SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.valueAt(i);
+ final int versionCount = versionedLib.size();
+ for (int j = 0; j < versionCount; j++) {
+ SharedLibraryEntry libEntry = versionedLib.valueAt(j);
+ final String libPath = libEntry.path != null
+ ? libEntry.path : libEntry.apk;
+ if (libPath == null) {
+ continue;
}
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "Library not found: " + lib);
- } catch (IOException | InstallerException e) {
- Slog.w(TAG, "Cannot dexopt " + lib + "; is it an APK or JAR? "
- + e.getMessage());
+ try {
+ // Shared libraries do not have profiles so we perform a full
+ // AOT compilation (if needed).
+ int dexoptNeeded = DexFile.getDexOptNeeded(
+ libPath, dexCodeInstructionSet,
+ getCompilerFilterForReason(REASON_SHARED_APK),
+ false /* newProfile */);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ mInstaller.dexopt(libPath, Process.SYSTEM_UID, "*",
+ dexCodeInstructionSet, dexoptNeeded, null,
+ DEXOPT_PUBLIC,
+ getCompilerFilterForReason(REASON_SHARED_APK),
+ StorageManager.UUID_PRIVATE_INTERNAL,
+ SKIP_SHARED_LIBRARY_CHECK);
+ }
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "Library not found: " + libPath);
+ } catch (IOException | InstallerException e) {
+ Slog.w(TAG, "Cannot dexopt " + libPath + "; is it an APK or JAR? "
+ + e.getMessage());
+ }
}
}
}
@@ -2636,7 +2676,7 @@
// Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
- updateAllSharedLibrariesLPw();
+ updateAllSharedLibrariesLPw(null);
for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
// NOTE: We ignore potential failures here during a system scan (like
@@ -2773,9 +2813,11 @@
mIntentFilterVerifier = new IntentVerifierProxy(mContext,
mIntentFilterVerifierComponent);
mServicesSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
- PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES);
+ PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES,
+ SharedLibraryInfo.VERSION_UNDEFINED);
mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
- PackageManager.SYSTEM_SHARED_LIBRARY_SHARED);
+ PackageManager.SYSTEM_SHARED_LIBRARY_SHARED,
+ SharedLibraryInfo.VERSION_UNDEFINED);
} else {
mRequiredVerifierPackage = null;
mRequiredInstallerPackage = null;
@@ -2806,8 +2848,6 @@
setUpEphemeralInstallerActivityLP(mEphemeralInstallerComponent);
}
- mEphemeralApplicationRegistry = new EphemeralApplicationRegistry(this);
-
// Read and update the usage of dex files.
// Do this at the end of PM init so that all the packages have their
// data directory reconciled.
@@ -2934,11 +2974,11 @@
throw new RuntimeException("There must be exactly one verifier; found " + matches);
}
- private @NonNull String getRequiredSharedLibraryLPr(String libraryName) {
+ private @NonNull String getRequiredSharedLibraryLPr(String name, int version) {
synchronized (mPackages) {
- SharedLibraryEntry libraryEntry = mSharedLibraries.get(libraryName);
+ SharedLibraryEntry libraryEntry = getSharedLibraryEntryLPr(name, version);
if (libraryEntry == null) {
- throw new IllegalStateException("Missing required shared library:" + libraryName);
+ throw new IllegalStateException("Missing required shared library:" + name);
}
return libraryEntry.apk;
}
@@ -3257,6 +3297,31 @@
if (p == null) {
return null;
}
+ // Filter out ephemeral app metadata:
+ // * The system/shell/root can see metadata for any app
+ // * An installed app can see metadata for 1) other installed apps
+ // and 2) ephemeral apps that have explicitly interacted with it
+ // * Ephemeral apps can only see their own metadata
+ final int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+ if (callingAppId != Process.SYSTEM_UID
+ && callingAppId != Process.SHELL_UID
+ && callingAppId != Process.ROOT_UID) {
+ final String ephemeralPackageName = getEphemeralPackageName(Binder.getCallingUid());
+ if (ephemeralPackageName != null) {
+ // ephemeral apps can only get information on themselves
+ if (!ephemeralPackageName.equals(p.packageName)) {
+ return null;
+ }
+ } else {
+ if (p.applicationInfo.isEphemeralApp()) {
+ // only get access to the ephemeral app if we've been granted access
+ if (!mEphemeralApplicationRegistry.isEphemeralAccessGranted(
+ userId, callingAppId, ps.appId)) {
+ return null;
+ }
+ }
+ }
+ }
final PermissionsState permissionsState = ps.getPermissionsState();
@@ -3273,8 +3338,17 @@
flags |= MATCH_ANY_USER;
}
- return PackageParser.generatePackageInfo(p, gids, flags,
+ PackageInfo packageInfo = PackageParser.generatePackageInfo(p, gids, flags,
ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId);
+
+ if (packageInfo == null) {
+ return null;
+ }
+
+ packageInfo.packageName = packageInfo.applicationInfo.packageName =
+ resolveExternalPackageNameLPr(p);
+
+ return packageInfo;
}
@Override
@@ -3329,6 +3403,20 @@
@Override
public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
+ return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST,
+ flags, userId);
+ }
+
+ @Override
+ public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage,
+ int flags, int userId) {
+ return getPackageInfoInternal(versionedPackage.getPackageName(),
+ // TODO: We will change version code to long, so in the new API it is long
+ (int) versionedPackage.getVersionCode(), flags, userId);
+ }
+
+ private PackageInfo getPackageInfoInternal(String packageName, int versionCode,
+ int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForPackage(flags, userId, packageName);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
@@ -3336,36 +3424,89 @@
// reader
synchronized (mPackages) {
- // Normalize package name to hanlde renamed packages
- packageName = normalizePackageNameLPr(packageName);
+ // Normalize package name to handle renamed packages and static libs
+ packageName = resolveInternalPackageNameLPr(packageName, versionCode);
final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0;
- PackageParser.Package p = null;
if (matchFactoryOnly) {
final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
if (ps != null) {
+ if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId)) {
+ return null;
+ }
return generatePackageInfo(ps, flags, userId);
}
}
- if (p == null) {
- p = mPackages.get(packageName);
- if (matchFactoryOnly && p != null && !isSystemApp(p)) {
- return null;
- }
+
+ PackageParser.Package p = mPackages.get(packageName);
+ if (matchFactoryOnly && p != null && !isSystemApp(p)) {
+ return null;
}
if (DEBUG_PACKAGE_INFO)
Log.v(TAG, "getPackageInfo " + packageName + ": " + p);
if (p != null) {
+ if (filterSharedLibPackageLPr((PackageSetting) p.mExtras,
+ Binder.getCallingUid(), userId)) {
+ return null;
+ }
return generatePackageInfo((PackageSetting)p.mExtras, flags, userId);
}
if (!matchFactoryOnly && (flags & MATCH_KNOWN_PACKAGES) != 0) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId)) {
+ return null;
+ }
return generatePackageInfo(ps, flags, userId);
}
}
return null;
}
+
+ private boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId) {
+ // System/shell/root get to see all static libs
+ final int appId = UserHandle.getAppId(uid);
+ if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
+ || appId == Process.ROOT_UID) {
+ return false;
+ }
+
+ // No package means no static lib as it is always on internal storage
+ if (ps == null || ps.pkg == null || !ps.pkg.applicationInfo.isStaticSharedLibrary()) {
+ return false;
+ }
+
+ final SharedLibraryEntry libEntry = getSharedLibraryEntryLPr(ps.pkg.staticSharedLibName,
+ ps.pkg.staticSharedLibVersion);
+ if (libEntry == null) {
+ return false;
+ }
+
+ final int resolvedUid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+ final String[] uidPackageNames = getPackagesForUid(resolvedUid);
+ if (uidPackageNames == null) {
+ return true;
+ }
+
+ for (String uidPackageName : uidPackageNames) {
+ if (ps.name.equals(uidPackageName)) {
+ return false;
+ }
+ PackageSetting uidPs = mSettings.getPackageLPr(uidPackageName);
+ if (uidPs != null) {
+ final int index = ArrayUtils.indexOf(uidPs.usesStaticLibraries,
+ libEntry.info.getName());
+ if (index < 0) {
+ continue;
+ }
+ if (uidPs.pkg.usesStaticLibrariesVersions[index] == libEntry.info.getVersion()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
@Override
public String[] currentToCanonicalPackageNames(String[] names) {
String[] out = new String[names.length];
@@ -3518,10 +3659,13 @@
}
private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
- int userId) {
+ int uid, int userId) {
if (!sUserManager.exists(userId)) return null;
PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
+ if (filterSharedLibPackageLPr(ps, uid, userId)) {
+ return null;
+ }
if (ps.pkg == null) {
final PackageInfo pInfo = generatePackageInfo(ps, flags, userId);
if (pInfo != null) {
@@ -3529,8 +3673,12 @@
}
return null;
}
- return PackageParser.generateApplicationInfo(ps.pkg, flags,
+ ApplicationInfo ai = PackageParser.generateApplicationInfo(ps.pkg, flags,
ps.readUserState(userId), userId);
+ if (ai != null) {
+ ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
+ }
+ return ai;
}
return null;
}
@@ -3544,8 +3692,9 @@
// writer
synchronized (mPackages) {
- // Normalize package name to hanlde renamed packages
- packageName = normalizePackageNameLPr(packageName);
+ // Normalize package name to handle renamed packages and static libs
+ packageName = resolveInternalPackageNameLPr(packageName,
+ PackageManager.VERSION_CODE_HIGHEST);
PackageParser.Package p = mPackages.get(packageName);
if (DEBUG_PACKAGE_INFO) Log.v(
@@ -3554,15 +3703,24 @@
if (p != null) {
PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps == null) return null;
+ if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId)) {
+ return null;
+ }
// Note: isEnabledLP() does not apply here - always return info
- return PackageParser.generateApplicationInfo(
+ ApplicationInfo ai = PackageParser.generateApplicationInfo(
p, flags, ps.readUserState(userId), userId);
+ if (ai != null) {
+ ai.packageName = resolveExternalPackageNameLPr(p);
+ }
+ return ai;
}
if ("android".equals(packageName)||"system".equals(packageName)) {
return mAndroidApplication;
}
if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
- return generateApplicationInfoFromSettingsLPw(packageName, flags, userId);
+ // Already generates the external package name
+ return generateApplicationInfoFromSettingsLPw(packageName,
+ Binder.getCallingUid(), flags, userId);
}
}
return null;
@@ -3745,6 +3903,19 @@
}
/**
+ * Update given intent when being used to request {@link ResolveInfo}.
+ */
+ private Intent updateIntentForResolve(Intent intent) {
+ if (intent.getSelector() != null) {
+ intent = intent.getSelector();
+ }
+ if (DEBUG_PREFERRED) {
+ intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+ }
+ return intent;
+ }
+
+ /**
* Update given flags when being used to request {@link ResolveInfo}.
*/
int updateFlagsForResolve(int flags, int userId, Object cookie) {
@@ -3835,6 +4006,113 @@
}
@Override
+ public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(int flags, int userId) {
+ if (!sUserManager.exists(userId)) return null;
+ Preconditions.checkArgumentNonnegative(userId, "userId must be >= 0");
+
+ flags = updateFlagsForPackage(flags, userId, null);
+
+ final boolean canSeeStaticLibraries =
+ mContext.checkCallingOrSelfPermission(INSTALL_PACKAGES)
+ == PERMISSION_GRANTED
+ || mContext.checkCallingOrSelfPermission(DELETE_PACKAGES)
+ == PERMISSION_GRANTED
+ || mContext.checkCallingOrSelfPermission(REQUEST_INSTALL_PACKAGES)
+ == PERMISSION_GRANTED
+ || mContext.checkCallingOrSelfPermission(REQUEST_DELETE_PACKAGES)
+ == PERMISSION_GRANTED;
+
+ synchronized (mPackages) {
+ List<SharedLibraryInfo> result = null;
+
+ final int libCount = mSharedLibraries.size();
+ for (int i = 0; i < libCount; i++) {
+ SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.valueAt(i);
+ if (versionedLib == null) {
+ continue;
+ }
+
+ final int versionCount = versionedLib.size();
+ for (int j = 0; j < versionCount; j++) {
+ SharedLibraryInfo libInfo = versionedLib.valueAt(j).info;
+ if (!canSeeStaticLibraries && libInfo.isStatic()) {
+ break;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ // TODO: We will change version code to long, so in the new API it is long
+ PackageInfo packageInfo = getPackageInfoVersioned(
+ libInfo.getDeclaringPackage(), flags, userId);
+ if (packageInfo == null) {
+ continue;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ SharedLibraryInfo resLibInfo = new SharedLibraryInfo(libInfo.getName(),
+ libInfo.getVersion(), libInfo.getType(), libInfo.getDeclaringPackage(),
+ getPackagesUsingSharedLibraryLPr(libInfo, flags, userId));
+
+ if (result == null) {
+ result = new ArrayList<>();
+ }
+ result.add(resLibInfo);
+ }
+ }
+
+ return result != null ? new ParceledListSlice<>(result) : null;
+ }
+ }
+
+ private List<VersionedPackage> getPackagesUsingSharedLibraryLPr(
+ SharedLibraryInfo libInfo, int flags, int userId) {
+ List<VersionedPackage> versionedPackages = null;
+ final int packageCount = mSettings.mPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ PackageSetting ps = mSettings.mPackages.valueAt(i);
+
+ if (ps == null) {
+ continue;
+ }
+
+ if (!ps.getUserState().get(userId).isAvailable(flags)) {
+ continue;
+ }
+
+ final String libName = libInfo.getName();
+ if (libInfo.isStatic()) {
+ final int libIdx = ArrayUtils.indexOf(ps.usesStaticLibraries, libName);
+ if (libIdx < 0) {
+ continue;
+ }
+ if (ps.usesStaticLibrariesVersions[libIdx] != libInfo.getVersion()) {
+ continue;
+ }
+ if (versionedPackages == null) {
+ versionedPackages = new ArrayList<>();
+ }
+ // If the dependent is a static shared lib, use the public package name
+ String dependentPackageName = ps.name;
+ if (ps.pkg != null && ps.pkg.applicationInfo.isStaticSharedLibrary()) {
+ dependentPackageName = ps.pkg.manifestPackageName;
+ }
+ versionedPackages.add(new VersionedPackage(dependentPackageName, ps.versionCode));
+ } else if (ps.pkg != null) {
+ if (ArrayUtils.contains(ps.pkg.usesLibraries, libName)
+ || ArrayUtils.contains(ps.pkg.usesOptionalLibraries, libName)) {
+ if (versionedPackages == null) {
+ versionedPackages = new ArrayList<>();
+ }
+ versionedPackages.add(new VersionedPackage(ps.name, ps.versionCode));
+ }
+ }
+ }
+
+ return versionedPackages;
+ }
+
+ @Override
public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId, component);
@@ -3876,17 +4154,44 @@
@Override
public String[] getSystemSharedLibraryNames() {
- Set<String> libSet;
synchronized (mPackages) {
- libSet = mSharedLibraries.keySet();
- int size = libSet.size();
- if (size > 0) {
- String[] libs = new String[size];
- libSet.toArray(libs);
- return libs;
+ Set<String> libs = null;
+ final int libCount = mSharedLibraries.size();
+ for (int i = 0; i < libCount; i++) {
+ SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.valueAt(i);
+ if (versionedLib == null) {
+ continue;
+ }
+ final int versionCount = versionedLib.size();
+ for (int j = 0; j < versionCount; j++) {
+ SharedLibraryEntry libEntry = versionedLib.valueAt(j);
+ if (!libEntry.info.isStatic()) {
+ if (libs == null) {
+ libs = new ArraySet<>();
+ }
+ libs.add(libEntry.info.getName());
+ break;
+ }
+ PackageSetting ps = mSettings.getPackageLPr(libEntry.apk);
+ if (ps != null && !filterSharedLibPackageLPr(ps, Binder.getCallingUid(),
+ UserHandle.getUserId(Binder.getCallingUid()))) {
+ if (libs == null) {
+ libs = new ArraySet<>();
+ }
+ libs.add(libEntry.info.getName());
+ break;
+ }
+ }
}
+
+ if (libs != null) {
+ String[] libsArray = new String[libs.size()];
+ libs.toArray(libsArray);
+ return libsArray;
+ }
+
+ return null;
}
- return null;
}
@Override
@@ -4998,7 +5303,9 @@
return res;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
- return new String[] { ps.name };
+ if (ps.getInstalled(userId)) {
+ return new String[]{ps.name};
+ }
}
}
return null;
@@ -5128,6 +5435,26 @@
}
@Override
+ public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) {
+ if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID)) {
+ throw new SecurityException(
+ "findPersistentPreferredActivity can only be run by the system");
+ }
+ if (!sUserManager.exists(userId)) {
+ return null;
+ }
+ intent = updateIntentForResolve(intent);
+ final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
+ final int flags = updateFlagsForResolve(0, userId, intent);
+ final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
+ userId);
+ synchronized (mPackages) {
+ return findPersistentPreferredActivityLP(intent, resolvedType, flags, query, false,
+ userId);
+ }
+ }
+
+ @Override
public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
IntentFilter filter, int match, ComponentName activity) {
final int userId = UserHandle.getCallingUserId();
@@ -5393,13 +5720,9 @@
boolean removeMatches, boolean debug, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForResolve(flags, userId, intent);
+ intent = updateIntentForResolve(intent);
// writer
synchronized (mPackages) {
- if (intent.getSelector() != null) {
- intent = intent.getSelector();
- }
- if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
-
// Try to find a matching persistent preferred activity.
ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query,
debug, userId);
@@ -5965,7 +6288,7 @@
int status = (int)(packedStatus >> 32);
int linkGeneration = (int)(packedStatus & 0xFFFFFFFF);
if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
- if (DEBUG_DOMAIN_VERIFICATION) {
+ if (DEBUG_DOMAIN_VERIFICATION || debug) {
Slog.i(TAG, " + always: " + info.activityInfo.packageName
+ " : linkgen=" + linkGeneration);
}
@@ -5974,18 +6297,18 @@
info.preferredOrder = linkGeneration;
alwaysList.add(info);
} else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
- if (DEBUG_DOMAIN_VERIFICATION) {
+ if (DEBUG_DOMAIN_VERIFICATION || debug) {
Slog.i(TAG, " + never: " + info.activityInfo.packageName);
}
neverList.add(info);
} else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
- if (DEBUG_DOMAIN_VERIFICATION) {
+ if (DEBUG_DOMAIN_VERIFICATION || debug) {
Slog.i(TAG, " + always-ask: " + info.activityInfo.packageName);
}
alwaysAskList.add(info);
} else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED ||
status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) {
- if (DEBUG_DOMAIN_VERIFICATION) {
+ if (DEBUG_DOMAIN_VERIFICATION || debug) {
Slog.i(TAG, " + ask: " + info.activityInfo.packageName);
}
undefinedList.add(info);
@@ -6550,30 +6873,32 @@
synchronized (mPackages) {
ArrayList<PackageInfo> list;
if (listUninstalled) {
- list = new ArrayList<PackageInfo>(mSettings.mPackages.size());
+ list = new ArrayList<>(mSettings.mPackages.size());
for (PackageSetting ps : mSettings.mPackages.values()) {
- final PackageInfo pi;
- if (ps.pkg != null) {
- pi = generatePackageInfo(ps, flags, userId);
- } else {
- pi = generatePackageInfo(ps, flags, userId);
+ if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId)) {
+ continue;
}
+ final PackageInfo pi = generatePackageInfo(ps, flags, userId);
if (pi != null) {
list.add(pi);
}
}
} else {
- list = new ArrayList<PackageInfo>(mPackages.size());
+ list = new ArrayList<>(mPackages.size());
for (PackageParser.Package p : mPackages.values()) {
- final PackageInfo pi =
- generatePackageInfo((PackageSetting)p.mExtras, flags, userId);
+ if (filterSharedLibPackageLPr((PackageSetting) p.mExtras,
+ Binder.getCallingUid(), userId)) {
+ continue;
+ }
+ final PackageInfo pi = generatePackageInfo((PackageSetting)
+ p.mExtras, flags, userId);
if (pi != null) {
list.add(pi);
}
}
}
- return new ParceledListSlice<PackageInfo>(list);
+ return new ParceledListSlice<>(list);
}
}
@@ -6593,12 +6918,8 @@
if (numMatch == 0) {
return;
}
- final PackageInfo pi;
- if (ps.pkg != null) {
- pi = generatePackageInfo(ps, flags, userId);
- } else {
- pi = generatePackageInfo(ps, flags, userId);
- }
+ final PackageInfo pi = generatePackageInfo(ps, flags, userId);
+
// The above might return null in cases of uninstalled apps or install-state
// skew across users/profiles.
if (pi != null) {
@@ -6663,7 +6984,7 @@
synchronized (mPackages) {
ArrayList<ApplicationInfo> list;
if (listUninstalled) {
- list = new ArrayList<ApplicationInfo>(mSettings.mPackages.size());
+ list = new ArrayList<>(mSettings.mPackages.size());
for (PackageSetting ps : mSettings.mPackages.values()) {
ApplicationInfo ai;
int effectiveFlags = flags;
@@ -6671,30 +6992,43 @@
effectiveFlags |= PackageManager.MATCH_ANY_USER;
}
if (ps.pkg != null) {
+ if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId)) {
+ continue;
+ }
ai = PackageParser.generateApplicationInfo(ps.pkg, effectiveFlags,
ps.readUserState(userId), userId);
+ if (ai != null) {
+ ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
+ }
} else {
- ai = generateApplicationInfoFromSettingsLPw(ps.name, effectiveFlags,
- userId);
+ // Shared lib filtering done in generateApplicationInfoFromSettingsLPw
+ // and already converts to externally visible package name
+ ai = generateApplicationInfoFromSettingsLPw(ps.name,
+ Binder.getCallingUid(), effectiveFlags, userId);
}
if (ai != null) {
list.add(ai);
}
}
} else {
- list = new ArrayList<ApplicationInfo>(mPackages.size());
+ list = new ArrayList<>(mPackages.size());
for (PackageParser.Package p : mPackages.values()) {
if (p.mExtras != null) {
+ PackageSetting ps = (PackageSetting) p.mExtras;
+ if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId)) {
+ continue;
+ }
ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
- ((PackageSetting)p.mExtras).readUserState(userId), userId);
+ ps.readUserState(userId), userId);
if (ai != null) {
+ ai.packageName = resolveExternalPackageNameLPr(p);
list.add(ai);
}
}
}
}
- return new ParceledListSlice<ApplicationInfo>(list);
+ return new ParceledListSlice<>(list);
}
}
@@ -6784,6 +7118,7 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS,
"getEphemeralApplicationIcon");
+
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"getEphemeralApplicationIcon");
@@ -7060,9 +7395,15 @@
int errorCode = PackageManager.INSTALL_SUCCEEDED;
if (throwable == null) {
+ // Static shared libraries have synthetic package names
+ if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
+ renameStaticSharedLibraryPackage(parseResult.pkg);
+ }
try {
- scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
- currentTime, null);
+ if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
+ scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
+ currentTime, null);
+ }
} catch (PackageManagerException e) {
errorCode = e.error;
Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
@@ -7216,6 +7557,11 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ // Static shared libraries have synthetic package names
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
+ renameStaticSharedLibraryPackage(pkg);
+ }
+
return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
}
@@ -7518,6 +7864,12 @@
return scannedPkg;
}
+ private void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
+ // Derive the new package synthetic package name
+ pkg.setPackageName(pkg.packageName + STATIC_SHARED_LIB_DELIMITER
+ + pkg.staticSharedLibVersion);
+ }
+
private static String fixProcessName(String defProcessName,
String processName) {
if (processName == null) {
@@ -7873,8 +8225,9 @@
targetCompilerFilter, getOrCreateCompilerPackageStats(p));
}
- Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
- if (p.usesLibraries != null || p.usesOptionalLibraries != null) {
+ List<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
+ if (p.usesLibraries != null || p.usesOptionalLibraries != null
+ || p.usesStaticLibraries != null) {
ArrayList<PackageParser.Package> retValue = new ArrayList<>();
Set<String> collectedNames = new HashSet<>();
findSharedNonSystemLibrariesRecursive(p, retValue, collectedNames);
@@ -7888,37 +8241,74 @@
}
private void findSharedNonSystemLibrariesRecursive(PackageParser.Package p,
- Collection<PackageParser.Package> collected, Set<String> collectedNames) {
+ ArrayList<PackageParser.Package> collected, Set<String> collectedNames) {
if (!collectedNames.contains(p.packageName)) {
collectedNames.add(p.packageName);
collected.add(p);
if (p.usesLibraries != null) {
- findSharedNonSystemLibrariesRecursive(p.usesLibraries, collected, collectedNames);
+ findSharedNonSystemLibrariesRecursive(p.usesLibraries,
+ null, collected, collectedNames);
}
if (p.usesOptionalLibraries != null) {
- findSharedNonSystemLibrariesRecursive(p.usesOptionalLibraries, collected,
- collectedNames);
+ findSharedNonSystemLibrariesRecursive(p.usesOptionalLibraries,
+ null, collected, collectedNames);
+ }
+ if (p.usesStaticLibraries != null) {
+ findSharedNonSystemLibrariesRecursive(p.usesStaticLibraries,
+ p.usesStaticLibrariesVersions, collected, collectedNames);
}
}
}
- private void findSharedNonSystemLibrariesRecursive(Collection<String> libs,
- Collection<PackageParser.Package> collected, Set<String> collectedNames) {
- for (String libName : libs) {
- PackageParser.Package libPkg = findSharedNonSystemLibrary(libName);
+ private void findSharedNonSystemLibrariesRecursive(ArrayList<String> libs, int[] versions,
+ ArrayList<PackageParser.Package> collected, Set<String> collectedNames) {
+ final int libNameCount = libs.size();
+ for (int i = 0; i < libNameCount; i++) {
+ String libName = libs.get(i);
+ int version = (versions != null && versions.length == libNameCount)
+ ? versions[i] : PackageManager.VERSION_CODE_HIGHEST;
+ PackageParser.Package libPkg = findSharedNonSystemLibrary(libName, version);
if (libPkg != null) {
findSharedNonSystemLibrariesRecursive(libPkg, collected, collectedNames);
}
}
}
- private PackageParser.Package findSharedNonSystemLibrary(String libName) {
+ private PackageParser.Package findSharedNonSystemLibrary(String name, int version) {
synchronized (mPackages) {
- PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
- if (lib != null && lib.apk != null) {
- return mPackages.get(lib.apk);
+ SharedLibraryEntry libEntry = getSharedLibraryEntryLPr(name, version);
+ if (libEntry != null) {
+ return mPackages.get(libEntry.apk);
}
+ return null;
+ }
+ }
+
+ private SharedLibraryEntry getSharedLibraryEntryLPr(String name, int version) {
+ SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.get(name);
+ if (versionedLib == null) {
+ return null;
+ }
+ return versionedLib.get(version);
+ }
+
+ private SharedLibraryEntry getLatestSharedLibraVersionLPr(PackageParser.Package pkg) {
+ SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.get(
+ pkg.staticSharedLibName);
+ if (versionedLib == null) {
+ return null;
+ }
+ int previousLibVersion = -1;
+ final int versionCount = versionedLib.size();
+ for (int i = 0; i < versionCount; i++) {
+ final int libVersion = versionedLib.keyAt(i);
+ if (libVersion < pkg.staticSharedLibVersion) {
+ previousLibVersion = Math.max(previousLibVersion, libVersion);
+ }
+ }
+ if (previousLibVersion >= 0) {
+ return versionedLib.get(previousLibVersion);
}
return null;
}
@@ -8212,36 +8602,84 @@
private void updateSharedLibrariesLPr(PackageParser.Package pkg,
PackageParser.Package changingLib) throws PackageManagerException {
- if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
- final ArraySet<String> usesLibraryFiles = new ArraySet<>();
- int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
- for (int i=0; i<N; i++) {
- final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
- if (file == null) {
+ if (pkg == null) {
+ return;
+ }
+ ArraySet<String> usesLibraryFiles = null;
+ if (pkg.usesLibraries != null) {
+ usesLibraryFiles = addSharedLibrariesLPw(pkg.usesLibraries,
+ null, null, pkg.packageName, changingLib, true, null);
+ }
+ if (pkg.usesStaticLibraries != null) {
+ usesLibraryFiles = addSharedLibrariesLPw(pkg.usesStaticLibraries,
+ pkg.usesStaticLibrariesVersions, pkg.usesStaticLibrariesCertDigests,
+ pkg.packageName, changingLib, true, usesLibraryFiles);
+ }
+ if (pkg.usesOptionalLibraries != null) {
+ usesLibraryFiles = addSharedLibrariesLPw(pkg.usesOptionalLibraries,
+ null, null, pkg.packageName, changingLib, false, usesLibraryFiles);
+ }
+ if (!ArrayUtils.isEmpty(usesLibraryFiles)) {
+ pkg.usesLibraryFiles = usesLibraryFiles.toArray(new String[usesLibraryFiles.size()]);
+ } else {
+ pkg.usesLibraryFiles = null;
+ }
+ }
+
+ private ArraySet<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
+ @Nullable int[] requiredVersions, @Nullable String[] requiredCertDigests,
+ @NonNull String packageName, @Nullable PackageParser.Package changingLib,
+ boolean required, @Nullable ArraySet<String> outUsedLibraries)
+ throws PackageManagerException {
+ final int libCount = requestedLibraries.size();
+ for (int i = 0; i < libCount; i++) {
+ final String libName = requestedLibraries.get(i);
+ final int libVersion = requiredVersions != null ? requiredVersions[i]
+ : SharedLibraryInfo.VERSION_UNDEFINED;
+ final SharedLibraryEntry libEntry = getSharedLibraryEntryLPr(libName, libVersion);
+ if (libEntry == null) {
+ if (required) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + pkg.packageName + " requires unavailable shared library "
- + pkg.usesLibraries.get(i) + "; failing!");
- }
- addSharedLibraryLPr(usesLibraryFiles, file, changingLib);
- }
- N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
- for (int i=0; i<N; i++) {
- final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
- if (file == null) {
- Slog.w(TAG, "Package " + pkg.packageName
- + " desires unavailable shared library "
- + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
+ "Package " + packageName + " requires unavailable shared library "
+ + libName + "; failing!");
} else {
- addSharedLibraryLPr(usesLibraryFiles, file, changingLib);
+ Slog.w(TAG, "Package " + packageName
+ + " desires unavailable shared library "
+ + libName + "; ignoring!");
}
- }
- N = usesLibraryFiles.size();
- if (N > 0) {
- pkg.usesLibraryFiles = usesLibraryFiles.toArray(new String[N]);
} else {
- pkg.usesLibraryFiles = null;
+ if (requiredVersions != null && requiredCertDigests != null) {
+ if (libEntry.info.getVersion() != requiredVersions[i]) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable static shared"
+ + " library " + libName + " version "
+ + libEntry.info.getVersion() + "; failing!");
+ }
+
+ PackageParser.Package libPkg = mPackages.get(libEntry.apk);
+ if (libPkg == null) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable static shared"
+ + " library; failing!");
+ }
+
+ String expectedCertDigest = requiredCertDigests[i];
+ String libCertDigest = PackageUtils.computeCertSha256Digest(
+ libPkg.mSignatures[0]);
+ if (!libCertDigest.equalsIgnoreCase(expectedCertDigest)) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires differently signed" +
+ " static shared library; failing!");
+ }
+ }
+
+ if (outUsedLibraries == null) {
+ outUsedLibraries = new ArraySet<>();
+ }
+ addSharedLibraryLPr(outUsedLibraries, libEntry, changingLib);
}
}
+ return outUsedLibraries;
}
private static boolean hasString(List<String> list, List<String> which) {
@@ -8258,31 +8696,36 @@
return false;
}
- private void updateAllSharedLibrariesLPw() {
- for (PackageParser.Package pkg : mPackages.values()) {
- try {
- updateSharedLibrariesLPr(pkg, null);
- } catch (PackageManagerException e) {
- Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
- }
- }
- }
-
private ArrayList<PackageParser.Package> updateAllSharedLibrariesLPw(
PackageParser.Package changingPkg) {
ArrayList<PackageParser.Package> res = null;
for (PackageParser.Package pkg : mPackages.values()) {
- if (hasString(pkg.usesLibraries, changingPkg.libraryNames)
- || hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames)) {
- if (res == null) {
- res = new ArrayList<PackageParser.Package>();
+ if (changingPkg != null
+ && !hasString(pkg.usesLibraries, changingPkg.libraryNames)
+ && !hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames)
+ && !ArrayUtils.contains(pkg.usesStaticLibraries,
+ changingPkg.staticSharedLibName)) {
+ return null;
+ }
+ if (res == null) {
+ res = new ArrayList<>();
+ }
+ res.add(pkg);
+ try {
+ updateSharedLibrariesLPr(pkg, changingPkg);
+ } catch (PackageManagerException e) {
+ // If a system app update or an app and a required lib missing we
+ // delete the package and for updated system apps keep the data as
+ // it is better for the user to reinstall than to be in an limbo
+ // state. Also libs disappearing under an app should never happen
+ // - just in case.
+ if (!pkg.isSystemApp() || pkg.isUpdatedSystemApp()) {
+ final int flags = pkg.isUpdatedSystemApp()
+ ? PackageManager.DELETE_KEEP_DATA : 0;
+ deletePackageLIF(pkg.packageName, null, true, sUserManager.getUserIds(),
+ flags , null, true, null);
}
- res.add(pkg);
- try {
- updateSharedLibrariesLPr(pkg, changingPkg);
- } catch (PackageManagerException e) {
- Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
- }
+ Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
}
}
return res;
@@ -8540,9 +8983,17 @@
pkgSetting == null ? null : new PackageSetting(pkgSetting);
final PackageSetting disabledPkgSetting =
mSettings.getDisabledSystemPkgLPr(pkg.packageName);
+
+ String[] usesStaticLibraries = null;
+ if (pkg.usesStaticLibraries != null) {
+ usesStaticLibraries = new String[pkg.usesStaticLibraries.size()];
+ pkg.usesStaticLibraries.toArray(usesStaticLibraries);
+ }
+
if (pkgSetting == null) {
final String parentPackageName = (pkg.parentPackage != null)
? pkg.parentPackage.packageName : null;
+
// REMOVE SharedUserSetting from method; update in a separate call
pkgSetting = Settings.createNewSetting(pkg.packageName, origPackage,
disabledPkgSetting, realName, suid, destCodeFile, destResourceFile,
@@ -8550,7 +9001,8 @@
pkg.applicationInfo.secondaryCpuAbi, pkg.mVersionCode,
pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags, user,
true /*allowInstall*/, parentPackageName, pkg.getChildPackageNames(),
- UserManagerService.getInstance());
+ UserManagerService.getInstance(), usesStaticLibraries,
+ pkg.usesStaticLibrariesVersions);
// SIDE EFFECTS; updates system state; move elsewhere
if (origPackage != null) {
mSettings.addRenamedPackageLPw(pkg.packageName, origPackage.name);
@@ -8566,7 +9018,8 @@
pkg.applicationInfo.nativeLibraryDir, pkg.applicationInfo.primaryCpuAbi,
pkg.applicationInfo.secondaryCpuAbi, pkg.applicationInfo.flags,
pkg.applicationInfo.privateFlags, pkg.getChildPackageNames(),
- UserManagerService.getInstance());
+ UserManagerService.getInstance(), usesStaticLibraries,
+ pkg.usesStaticLibrariesVersions);
}
// SIDE EFFECTS; persists system state to files on disk; move elsewhere
mSettings.writeUserRestrictionsLPw(pkgSetting, oldPkgSetting);
@@ -8603,12 +9056,15 @@
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
}
- if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ if ((scanFlags & SCAN_BOOTING) == 0
+ && (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
// will take care of the system apps by updating all of their
- // library paths after the scan is done.
+ // library paths after the scan is done. Also during the initial
+ // scan don't update any libs as we do this wholesale after all
+ // apps are scanned to avoid dependency based scanning.
updateSharedLibrariesLPr(pkg, null);
}
@@ -8618,8 +9074,22 @@
pkg.applicationInfo.uid = pkgSetting.appId;
pkg.mExtras = pkgSetting;
- if (shouldCheckUpgradeKeySetLP(pkgSetting, scanFlags)) {
- if (checkUpgradeKeySetLP(pkgSetting, pkg)) {
+
+
+ // Static shared libs have same package with different versions where
+ // we internally use a synthetic package name to allow multiple versions
+ // of the same package, therefore we need to compare signatures against
+ // the package setting for the latest library version.
+ PackageSetting signatureCheckPs = pkgSetting;
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
+ SharedLibraryEntry libraryEntry = getLatestSharedLibraVersionLPr(pkg);
+ if (libraryEntry != null) {
+ signatureCheckPs = mSettings.getPackageLPr(libraryEntry.apk);
+ }
+ }
+
+ if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
+ if (checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -8638,7 +9108,7 @@
} else {
try {
// SIDE EFFECTS; compareSignaturesCompat() changes KeysetManagerService
- verifySignaturesLP(pkgSetting, pkg);
+ verifySignaturesLP(signatureCheckPs, pkg);
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -8654,8 +9124,8 @@
// What this means is that you can't change the signatures
// associated with an overall shared user, which doesn't seem all
// that unreasonable.
- if (pkgSetting.sharedUser != null) {
- if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
+ if (signatureCheckPs.sharedUser != null) {
+ if (compareSignatures(signatureCheckPs.sharedUser.signatures.mSignatures,
pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
throw new PackageManagerException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
@@ -8839,6 +9309,10 @@
// Modify state for the given package setting
commitPackageSettings(pkg, pkgSetting, user, scanFlags,
(policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
+ if (isEphemeral(pkg)) {
+ final int userId = user == null ? 0 : user.getIdentifier();
+ mEphemeralApplicationRegistry.addEphemeralAppLPw(userId, pkgSetting.appId);
+ }
}
return pkg;
}
@@ -8892,7 +9366,7 @@
}
/**
- * Asserts the parsed package is valid according to teh given policy. If the
+ * Asserts the parsed package is valid according to the given policy. If the
* package is invalid, for whatever reason, throws {@link PackgeManagerException}.
* <p>
* Implementation detail: This method must NOT have any side effects. It would
@@ -8931,13 +9405,134 @@
}
// 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.");
}
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
+ // Static libs have a synthetic package name containing the version
+ // but we still want the base name to be unique.
+ if (mPackages.containsKey(pkg.manifestPackageName)) {
+ throw new PackageManagerException(
+ "Duplicate static shared lib provider package");
+ }
+
+ // Static shared libraries should have at least O target SDK
+ if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+ throw new PackageManagerException(
+ "Packages declaring static-shared libs must target O SDK or higher");
+ }
+
+ // Package declaring static a shared lib cannot be ephemeral
+ if (pkg.applicationInfo.isEphemeralApp()) {
+ throw new PackageManagerException(
+ "Packages declaring static-shared libs cannot be ephemeral");
+ }
+
+ // Package declaring static a shared lib cannot be renamed since the package
+ // name is synthetic and apps can't code around package manager internals.
+ if (!ArrayUtils.isEmpty(pkg.mOriginalPackages)) {
+ throw new PackageManagerException(
+ "Packages declaring static-shared libs cannot be renamed");
+ }
+
+ // Package declaring static a shared lib cannot declare child packages
+ if (!ArrayUtils.isEmpty(pkg.childPackages)) {
+ throw new PackageManagerException(
+ "Packages declaring static-shared libs cannot have child packages");
+ }
+
+ // Package declaring static a shared lib cannot declare dynamic libs
+ if (!ArrayUtils.isEmpty(pkg.libraryNames)) {
+ throw new PackageManagerException(
+ "Packages declaring static-shared libs cannot declare dynamic libs");
+ }
+
+ // Package declaring static a shared lib cannot declare shared users
+ if (pkg.mSharedUserId != null) {
+ throw new PackageManagerException(
+ "Packages declaring static-shared libs cannot declare shared users");
+ }
+
+ // Static shared libs cannot declare activities
+ if (!pkg.activities.isEmpty()) {
+ throw new PackageManagerException(
+ "Static shared libs cannot declare activities");
+ }
+
+ // Static shared libs cannot declare services
+ if (!pkg.services.isEmpty()) {
+ throw new PackageManagerException(
+ "Static shared libs cannot declare services");
+ }
+
+ // Static shared libs cannot declare providers
+ if (!pkg.providers.isEmpty()) {
+ throw new PackageManagerException(
+ "Static shared libs cannot declare content providers");
+ }
+
+ // Static shared libs cannot declare receivers
+ if (!pkg.receivers.isEmpty()) {
+ throw new PackageManagerException(
+ "Static shared libs cannot declare broadcast receivers");
+ }
+
+ // Static shared libs cannot declare permission groups
+ if (!pkg.permissionGroups.isEmpty()) {
+ throw new PackageManagerException(
+ "Static shared libs cannot declare permission groups");
+ }
+
+ // Static shared libs cannot declare permissions
+ if (!pkg.permissions.isEmpty()) {
+ throw new PackageManagerException(
+ "Static shared libs cannot declare permissions");
+ }
+
+ // Static shared libs cannot declare protected broadcasts
+ if (pkg.protectedBroadcasts != null) {
+ throw new PackageManagerException(
+ "Static shared libs cannot declare protected broadcasts");
+ }
+
+ // Static shared libs cannot be overlay targets
+ if (pkg.mOverlayTarget != null) {
+ throw new PackageManagerException(
+ "Static shared libs cannot be overlay targets");
+ }
+
+ // The version codes must be ordered as lib versions
+ int minVersionCode = Integer.MIN_VALUE;
+ int maxVersionCode = Integer.MAX_VALUE;
+
+ SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.get(
+ pkg.staticSharedLibName);
+ if (versionedLib != null) {
+ final int versionCount = versionedLib.size();
+ for (int i = 0; i < versionCount; i++) {
+ SharedLibraryInfo libInfo = versionedLib.valueAt(i).info;
+ // TODO: We will change version code to long, so in the new API it is long
+ final int libVersionCode = (int) libInfo.getDeclaringPackage()
+ .getVersionCode();
+ if (libInfo.getVersion() < pkg.staticSharedLibVersion) {
+ minVersionCode = Math.max(minVersionCode, libVersionCode + 1);
+ } else if (libInfo.getVersion() > pkg.staticSharedLibVersion) {
+ maxVersionCode = Math.min(maxVersionCode, libVersionCode - 1);
+ } else {
+ minVersionCode = maxVersionCode = libVersionCode;
+ break;
+ }
+ }
+ }
+ if (pkg.mVersionCode < minVersionCode || pkg.mVersionCode > maxVersionCode) {
+ throw new PackageManagerException("Static shared"
+ + " lib version codes must be ordered as lib versions");
+ }
+ }
+
// Only privileged apps and updated privileged apps can add child packages.
if (pkg.childPackages != null && !pkg.childPackages.isEmpty()) {
if ((policyFlags & PARSE_IS_PRIVILEGED) == 0) {
@@ -9018,6 +9613,45 @@
}
}
+ private boolean addSharedLibraryLPw(String path, String apk, String name, int version,
+ int type, String declaringPackageName, int declaringVersionCode) {
+ SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.get(name);
+ if (versionedLib == null) {
+ versionedLib = new SparseArray<>();
+ mSharedLibraries.put(name, versionedLib);
+ if (type == SharedLibraryInfo.TYPE_STATIC) {
+ mStaticLibsByDeclaringPackage.put(declaringPackageName, versionedLib);
+ }
+ } else if (versionedLib.indexOfKey(version) >= 0) {
+ return false;
+ }
+ SharedLibraryEntry libEntry = new SharedLibraryEntry(path, apk, name,
+ version, type, declaringPackageName, declaringVersionCode);
+ versionedLib.put(version, libEntry);
+ return true;
+ }
+
+ private boolean removeSharedLibraryLPw(String name, int version) {
+ SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.get(name);
+ if (versionedLib == null) {
+ return false;
+ }
+ final int libIdx = versionedLib.indexOfKey(version);
+ if (libIdx < 0) {
+ return false;
+ }
+ SharedLibraryEntry libEntry = versionedLib.valueAt(libIdx);
+ versionedLib.remove(version);
+ if (versionedLib.size() <= 0) {
+ mSharedLibraries.remove(name);
+ if (libEntry.info.getType() == SharedLibraryInfo.TYPE_STATIC) {
+ mStaticLibsByDeclaringPackage.remove(libEntry.info.getDeclaringPackage()
+ .getPackageName());
+ }
+ }
+ return true;
+ }
+
/**
* Adds a scanned package to the system. When this method is finished, the package will
* be available for query, resolution, etc...
@@ -9070,10 +9704,30 @@
ArrayList<PackageParser.Package> clientLibPkgs = null;
// writer
synchronized (mPackages) {
- if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
- // Only system apps can add new shared libraries.
+ boolean hasStaticSharedLibs = false;
+
+ // Any app can add new static shared libraries
+ if (pkg.staticSharedLibName != null) {
+ // Static shared libs don't allow renaming as they have synthetic package
+ // names to allow install of multiple versions, so use name from manifest.
+ if (addSharedLibraryLPw(null, pkg.packageName, pkg.staticSharedLibName,
+ pkg.staticSharedLibVersion, SharedLibraryInfo.TYPE_STATIC,
+ pkg.manifestPackageName, pkg.mVersionCode)) {
+ hasStaticSharedLibs = true;
+ } else {
+ Slog.w(TAG, "Package " + pkg.packageName + " library "
+ + pkg.staticSharedLibName + " already exists; skipping");
+ }
+ // Static shared libs cannot be updated once installed since they
+ // use synthetic package name which includes the version code, so
+ // not need to update other packages's shared lib dependencies.
+ }
+
+ if (!hasStaticSharedLibs
+ && (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Only system apps can add new dynamic shared libraries.
if (pkg.libraryNames != null) {
- for (int i=0; i<pkg.libraryNames.size(); i++) {
+ for (int i = 0; i < pkg.libraryNames.size(); i++) {
String name = pkg.libraryNames.get(i);
boolean allowed = false;
if (pkg.isUpdatedSystemApp()) {
@@ -9090,7 +9744,7 @@
final PackageSetting sysPs = mSettings
.getDisabledSystemPkgLPr(pkg.packageName);
if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) {
- for (int j=0; j<sysPs.pkg.libraryNames.size(); j++) {
+ for (int j = 0; j < sysPs.pkg.libraryNames.size(); j++) {
if (name.equals(sysPs.pkg.libraryNames.get(j))) {
allowed = true;
break;
@@ -9101,9 +9755,10 @@
allowed = true;
}
if (allowed) {
- if (!mSharedLibraries.containsKey(name)) {
- mSharedLibraries.put(name, new SharedLibraryEntry(null, pkg.packageName));
- } else if (!name.equals(pkg.packageName)) {
+ if (!addSharedLibraryLPw(null, pkg.packageName, name,
+ SharedLibraryInfo.VERSION_UNDEFINED,
+ SharedLibraryInfo.TYPE_DYNAMIC,
+ pkg.packageName, pkg.mVersionCode)) {
Slog.w(TAG, "Package " + pkg.packageName + " library "
+ name + " already exists; skipping");
}
@@ -9112,6 +9767,7 @@
+ name + " that is not declared on system image; skipping");
}
}
+
if ((scanFlags & SCAN_BOOTING) == 0) {
// If we are not booting, we need to update any applications
// that are clients of our shared library. If we are booting,
@@ -9459,8 +10115,10 @@
a.info.packageName = pkg.applicationInfo.packageName;
a.info.sourceDir = pkg.applicationInfo.sourceDir;
a.info.publicSourceDir = pkg.applicationInfo.publicSourceDir;
+ a.info.splitNames = pkg.splitNames;
a.info.splitSourceDirs = pkg.applicationInfo.splitSourceDirs;
a.info.splitPublicSourceDirs = pkg.applicationInfo.splitPublicSourceDirs;
+ a.info.splitDependencies = pkg.applicationInfo.splitDependencies;
a.info.dataDir = pkg.applicationInfo.dataDir;
a.info.deviceProtectedDataDir = pkg.applicationInfo.deviceProtectedDataDir;
a.info.credentialProtectedDataDir = pkg.applicationInfo.credentialProtectedDataDir;
@@ -10325,11 +10983,9 @@
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
// Only system apps can hold shared libraries.
if (pkg.libraryNames != null) {
- for (i=0; i<pkg.libraryNames.size(); i++) {
+ for (i = 0; i < pkg.libraryNames.size(); i++) {
String name = pkg.libraryNames.get(i);
- SharedLibraryEntry cur = mSharedLibraries.get(name);
- if (cur != null && cur.apk != null && cur.apk.equals(pkg.packageName)) {
- mSharedLibraries.remove(name);
+ if (removeSharedLibraryLPw(name, 0)) {
if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
@@ -10342,6 +10998,23 @@
}
}
}
+
+ r = null;
+
+ // Any package can hold static shared libraries.
+ if (pkg.staticSharedLibName != null) {
+ if (removeSharedLibraryLPw(pkg.staticSharedLibName, pkg.staticSharedLibVersion)) {
+ if (DEBUG_REMOVE && chatty) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append(pkg.staticSharedLibName);
+ }
+ }
+ }
+
if (r != null) {
if (DEBUG_REMOVE) Log.d(TAG, " Libraries: " + r);
}
@@ -12143,7 +12816,7 @@
IActivityManager am = ActivityManager.getService();
if (am != null) {
try {
- am.startService(null, intent, null, mContext.getOpPackageName(),
+ am.startService(null, intent, null, -1, null, mContext.getOpPackageName(),
UserHandle.USER_SYSTEM);
} catch (RemoteException e) {
}
@@ -12403,6 +13076,16 @@
Slog.w(TAG, "Cannot hide package: android");
return false;
}
+ // Cannot hide static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg != null && pkg.staticSharedLibName != null) {
+ Slog.w(TAG, "Cannot hide package: " + packageName
+ + " providing static shared library: "
+ + pkg.staticSharedLibName);
+ return false;
+ }
// Only allow protected packages to hide themselves.
if (hidden && !UserHandle.isSameApp(uid, pkgSetting.appId)
&& mProtectedPackages.isPackageStateProtected(userId, packageName)) {
@@ -12663,6 +13346,17 @@
return false;
}
+ // Cannot suspend static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg != null && pkg.applicationInfo.isStaticSharedLibrary()) {
+ Slog.w(TAG, "Cannot suspend package: " + packageName
+ + " providing static shared library: "
+ + pkg.staticSharedLibName);
+ return false;
+ }
+
return true;
}
@@ -15096,6 +15790,7 @@
res.removedInfo = new PackageRemovedInfo();
res.removedInfo.uid = oldPackage.applicationInfo.uid;
res.removedInfo.removedPackage = oldPackage.packageName;
+ res.removedInfo.isStaticSharedLib = pkg.staticSharedLibName != null;
res.removedInfo.isUpdate = true;
res.removedInfo.origUsers = installedUsers;
final PackageSetting ps = mSettings.getPackageLPr(pkgName);
@@ -15758,6 +16453,19 @@
return;
}
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
+ // Static shared libraries have synthetic package names
+ renameStaticSharedLibraryPackage(pkg);
+
+ // No static shared libs on external storage
+ if (onExternal) {
+ Slog.i(TAG, "Static shared libs can only be installed on internal storage.");
+ res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+ "Packages declaring static-shared libs cannot be updated");
+ return;
+ }
+ }
+
// If we are installing a clustered package add results for the children
if (pkg.childPackages != null) {
synchronized (mPackages) {
@@ -15882,11 +16590,23 @@
if (ps != null) {
if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
+ // Static shared libs have same package with different versions where
+ // we internally use a synthetic package name to allow multiple versions
+ // of the same package, therefore we need to compare signatures against
+ // the package setting for the latest library version.
+ PackageSetting signatureCheckPs = ps;
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
+ SharedLibraryEntry libraryEntry = getLatestSharedLibraVersionLPr(pkg);
+ if (libraryEntry != null) {
+ signatureCheckPs = mSettings.getPackageLPr(libraryEntry.apk);
+ }
+ }
+
// Quick sanity check that we're signed correctly if updating;
// we'll check this again later when scanning, but we want to
// bail early here before tripping over redefined permissions.
- if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
- if (!checkUpgradeKeySetLP(ps, pkg)) {
+ if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
+ if (!checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+ pkg.packageName + " upgrade keys do not match the "
+ "previously installed version");
@@ -15894,7 +16614,7 @@
}
} else {
try {
- verifySignaturesLP(ps, pkg);
+ verifySignaturesLP(signatureCheckPs, pkg);
} catch (PackageManagerException e) {
res.setError(e.error, e.getMessage());
return;
@@ -16011,14 +16731,6 @@
return;
}
- // Shared libraries for the package need to be updated.
- synchronized (mPackages) {
- try {
- updateSharedLibrariesLPr(pkg, null);
- } catch (PackageManagerException e) {
- Slog.e(TAG, "updateSharedLibrariesLPw failed: " + e.getMessage());
- }
- }
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
// Do not run PackageDexOptimizer through the local performDexOpt
// method because `pkg` may not be in `mPackages` yet.
@@ -16046,6 +16758,18 @@
try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
"installPackageLI")) {
if (replace) {
+ if (pkg.applicationInfo.isStaticSharedLibrary()) {
+ // Static libs have a synthetic package name containing the version
+ // and cannot be updated as an update would get a new package name,
+ // unless this is the exact same version code which is useful for
+ // development.
+ PackageParser.Package existingPkg = mPackages.get(pkg.packageName);
+ if (existingPkg != null && existingPkg.mVersionCode != pkg.mVersionCode) {
+ res.setError(INSTALL_FAILED_DUPLICATE_PACKAGE, "Packages declaring "
+ + "static-shared libs cannot be updated");
+ return;
+ }
+ }
replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
installerPackageName, res, args.installReason);
} else {
@@ -16291,22 +17015,37 @@
}
@Override
- public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer, int userId,
- int flags) {
- deletePackage(packageName, new LegacyPackageDeleteObserver(observer).getBinder(), userId,
- flags);
+ public void deletePackageAsUser(String packageName, int versionCode,
+ IPackageDeleteObserver observer, int userId, int flags) {
+ deletePackageVersioned(new VersionedPackage(packageName, versionCode),
+ new LegacyPackageDeleteObserver(observer).getBinder(), userId, flags);
}
@Override
- public void deletePackage(final String packageName,
+ public void deletePackageVersioned(VersionedPackage versionedPackage,
final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
- Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(versionedPackage);
Preconditions.checkNotNull(observer);
+ Preconditions.checkArgumentInRange(versionedPackage.getVersionCode(),
+ PackageManager.VERSION_CODE_HIGHEST,
+ Integer.MAX_VALUE, "versionCode must be >= -1");
+
+ final String packageName = versionedPackage.getPackageName();
+ // TODO: We will change version code to long, so in the new API it is long
+ final int versionCode = (int) versionedPackage.getVersionCode();
+ final String internalPackageName;
+ synchronized (mPackages) {
+ // Normalize package name to handle renamed packages and static libs
+ internalPackageName = resolveInternalPackageNameLPr(versionedPackage.getPackageName(),
+ // TODO: We will change version code to long, so in the new API it is long
+ (int) versionedPackage.getVersionCode());
+ }
+
final int uid = Binder.getCallingUid();
- if (!isOrphaned(packageName)
- && !isCallerAllowedToSilentlyUninstall(uid, packageName)) {
+ if (!isOrphaned(internalPackageName)
+ && !isCallerAllowedToSilentlyUninstall(uid, internalPackageName)) {
try {
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
@@ -16333,7 +17072,7 @@
return;
}
- if (!deleteAllUsers && getBlockUninstallForUser(packageName, userId)) {
+ if (!deleteAllUsers && getBlockUninstallForUser(internalPackageName, userId)) {
try {
observer.onPackageDeleted(packageName,
PackageManager.DELETE_FAILED_OWNER_BLOCKED, null);
@@ -16343,8 +17082,10 @@
}
if (DEBUG_REMOVE) {
- Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId
- + " deleteAllUsers: " + deleteAllUsers );
+ Slog.d(TAG, "deletePackageAsUser: pkg=" + internalPackageName + " user=" + userId
+ + " deleteAllUsers: " + deleteAllUsers + " version="
+ + (versionCode == PackageManager.VERSION_CODE_HIGHEST
+ ? "VERSION_CODE_HIGHEST" : versionCode));
}
// Queue up an async operation since the package deletion may take a little while.
mHandler.post(new Runnable() {
@@ -16352,18 +17093,22 @@
mHandler.removeCallbacks(this);
int returnCode;
if (!deleteAllUsers) {
- returnCode = deletePackageX(packageName, userId, deleteFlags);
+ returnCode = deletePackageX(internalPackageName, versionCode,
+ userId, deleteFlags);
} else {
- int[] blockUninstallUserIds = getBlockUninstallForUsers(packageName, users);
+ int[] blockUninstallUserIds = getBlockUninstallForUsers(
+ internalPackageName, users);
// If nobody is blocking uninstall, proceed with delete for all users
if (ArrayUtils.isEmpty(blockUninstallUserIds)) {
- returnCode = deletePackageX(packageName, userId, deleteFlags);
+ returnCode = deletePackageX(internalPackageName, versionCode,
+ userId, deleteFlags);
} else {
// Otherwise uninstall individually for users with blockUninstalls=false
final int userFlags = deleteFlags & ~PackageManager.DELETE_ALL_USERS;
for (int userId : users) {
if (!ArrayUtils.contains(blockUninstallUserIds, userId)) {
- returnCode = deletePackageX(packageName, userId, userFlags);
+ returnCode = deletePackageX(internalPackageName, versionCode,
+ userId, userFlags);
if (returnCode != PackageManager.DELETE_SUCCEEDED) {
Slog.w(TAG, "Package delete failed for user " + userId
+ ", returnCode " + returnCode);
@@ -16384,6 +17129,80 @@
});
}
+ private String resolveExternalPackageNameLPr(PackageParser.Package pkg) {
+ if (pkg.staticSharedLibName != null) {
+ return pkg.manifestPackageName;
+ }
+ return pkg.packageName;
+ }
+
+ private String resolveInternalPackageNameLPr(String packageName, int versionCode) {
+ // Handle renamed packages
+ String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
+ packageName = normalizedPackageName != null ? normalizedPackageName : packageName;
+
+ // Is this a static library?
+ SparseArray<SharedLibraryEntry> versionedLib =
+ mStaticLibsByDeclaringPackage.get(packageName);
+ if (versionedLib == null || versionedLib.size() <= 0) {
+ return packageName;
+ }
+
+ // Figure out which lib versions the caller can see
+ SparseIntArray versionsCallerCanSee = null;
+ final int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+ if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID
+ && callingAppId != Process.ROOT_UID) {
+ versionsCallerCanSee = new SparseIntArray();
+ String libName = versionedLib.valueAt(0).info.getName();
+ String[] uidPackages = getPackagesForUid(Binder.getCallingUid());
+ if (uidPackages != null) {
+ for (String uidPackage : uidPackages) {
+ PackageSetting ps = mSettings.getPackageLPr(uidPackage);
+ final int libIdx = ArrayUtils.indexOf(ps.usesStaticLibraries, libName);
+ if (libIdx >= 0) {
+ final int libVersion = ps.usesStaticLibrariesVersions[libIdx];
+ versionsCallerCanSee.append(libVersion, libVersion);
+ }
+ }
+ }
+ }
+
+ // Caller can see nothing - done
+ if (versionsCallerCanSee != null && versionsCallerCanSee.size() <= 0) {
+ return packageName;
+ }
+
+ // Find the version the caller can see and the app version code
+ SharedLibraryEntry highestVersion = null;
+ final int versionCount = versionedLib.size();
+ for (int i = 0; i < versionCount; i++) {
+ SharedLibraryEntry libEntry = versionedLib.valueAt(i);
+ if (versionsCallerCanSee != null && versionsCallerCanSee.indexOfKey(
+ libEntry.info.getVersion()) < 0) {
+ continue;
+ }
+ // TODO: We will change version code to long, so in the new API it is long
+ final int libVersionCode = (int) libEntry.info.getDeclaringPackage().getVersionCode();
+ if (versionCode != PackageManager.VERSION_CODE_HIGHEST) {
+ if (libVersionCode == versionCode) {
+ return libEntry.apk;
+ }
+ } else if (highestVersion == null) {
+ highestVersion = libEntry;
+ } else if (libVersionCode > highestVersion.info
+ .getDeclaringPackage().getVersionCode()) {
+ highestVersion = libEntry;
+ }
+ }
+
+ if (highestVersion != null) {
+ return highestVersion.apk;
+ }
+
+ return packageName;
+ }
+
private boolean isCallerAllowedToSilentlyUninstall(int callingUid, String pkgName) {
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID
|| callingUid == Process.SYSTEM_UID) {
@@ -16482,7 +17301,7 @@
* persisting settings for later use
* sending a broadcast if necessary
*/
- private int deletePackageX(String packageName, int userId, int deleteFlags) {
+ private int deletePackageX(String packageName, int versionCode, int userId, int deleteFlags) {
final PackageRemovedInfo info = new PackageRemovedInfo();
final boolean res;
@@ -16505,6 +17324,32 @@
Slog.w(TAG, "Not removing non-existent package " + packageName);
return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
}
+
+ if (versionCode != PackageManager.VERSION_CODE_HIGHEST
+ && uninstalledPs.versionCode != versionCode) {
+ Slog.w(TAG, "Not removing package " + packageName + " with versionCode "
+ + uninstalledPs.versionCode + " != " + versionCode);
+ return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
+ }
+
+ // Static shared libs can be declared by any package, so let us not
+ // allow removing a package if it provides a lib others depend on.
+ PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg != null && pkg.staticSharedLibName != null) {
+ SharedLibraryEntry libEntry = getSharedLibraryEntryLPr(pkg.staticSharedLibName,
+ pkg.staticSharedLibVersion);
+ if (libEntry != null) {
+ List<VersionedPackage> libClientPackages = getPackagesUsingSharedLibraryLPr(
+ libEntry.info, 0, userId);
+ if (!ArrayUtils.isEmpty(libClientPackages)) {
+ Slog.w(TAG, "Not removing package " + pkg.manifestPackageName
+ + " hosting lib " + libEntry.info.getName() + " version "
+ + libEntry.info.getVersion() + " used by " + libClientPackages);
+ return PackageManager.DELETE_FAILED_USED_SHARED_LIBRARY;
+ }
+ }
+ }
+
allUsers = sUserManager.getUserIds();
info.origUsers = uninstalledPs.queryInstalledUsers(allUsers, true);
}
@@ -16563,6 +17408,7 @@
boolean isUpdate;
boolean dataRemoved;
boolean removedForAllUsers;
+ boolean isStaticSharedLib;
// Clean up resources deleted packages.
InstallArgs args = null;
ArrayMap<String, PackageRemovedInfo> removedChildPackages;
@@ -16614,6 +17460,12 @@
}
private void sendPackageRemovedBroadcastInternal(boolean killApp) {
+ // Don't send static shared library removal broadcasts as these
+ // libs are visible only the the apps that depend on them an one
+ // cannot remove the library if it has a dependency.
+ if (isStaticSharedLib) {
+ return;
+ }
Bundle extras = new Bundle(2);
extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved);
@@ -16627,7 +17479,8 @@
extras, 0, null, null, removedUsers);
if (dataRemoved && !isRemovedPackageSystemUpdate) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED,
- removedPackage, extras, 0, null, null, removedUsers);
+ removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
+ null, null, removedUsers);
}
}
if (removedAppId >= 0) {
@@ -16656,6 +17509,8 @@
deletedPs = mSettings.mPackages.get(packageName);
if (outInfo != null) {
outInfo.removedPackage = packageName;
+ outInfo.isStaticSharedLib = deletedPkg != null
+ && deletedPkg.staticSharedLibName != null;
outInfo.removedUsers = deletedPs != null
? deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true)
: null;
@@ -16683,15 +17538,18 @@
schedulePackageCleaning(packageName, UserHandle.USER_ALL, true);
}
+ int removedAppId = -1;
+
// writer
synchronized (mPackages) {
if (deletedPs != null) {
if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL);
clearDefaultBrowserIfNeeded(packageName);
+ mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName);
+ removedAppId = mSettings.removePackageLPw(packageName);
if (outInfo != null) {
- mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName);
- outInfo.removedAppId = mSettings.removePackageLPw(packageName);
+ outInfo.removedAppId = removedAppId;
}
updatePermissionsLPw(deletedPs.name, null, 0);
if (deletedPs.sharedUser != null) {
@@ -16741,10 +17599,10 @@
mSettings.writeLPr();
}
}
- if (outInfo != null) {
+ if (removedAppId != -1) {
// A user ID was deleted here. Go through all users and remove it
// from KeyStore.
- removeKeystoreDataIfNeeded(UserHandle.USER_ALL, outInfo.removedAppId);
+ removeKeystoreDataIfNeeded(UserHandle.USER_ALL, removedAppId);
}
}
@@ -16860,6 +17718,7 @@
+ e.getMessage());
return false;
}
+
try {
// update shared libraries for the newly re-installed system package
updateSharedLibrariesLPr(newPkg, null);
@@ -16978,6 +17837,15 @@
Log.i(TAG, "Package doesn't exist in set block uninstall " + packageName);
return false;
}
+ // Cannot block uninstall of static shared libs as they are
+ // considered a part of the using app (emulating static linking).
+ // Also static libs are installed always on internal storage.
+ PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg != null && pkg.staticSharedLibName != null) {
+ Slog.w(TAG, "Cannot block uninstall of package: " + packageName
+ + " providing static shared library: " + pkg.staticSharedLibName);
+ return false;
+ }
if (!ps.getInstalled(userId)) {
// Can't block uninstall for an app that is not installed or enabled.
Log.i(TAG, "Package not installed in set block uninstall " + packageName);
@@ -17039,7 +17907,6 @@
if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
PackageSetting ps;
-
synchronized (mPackages) {
ps = mSettings.mPackages.get(packageName);
if (ps == null) {
@@ -17232,6 +18099,7 @@
if (outInfo != null) {
outInfo.removedPackage = ps.name;
+ outInfo.isStaticSharedLib = pkg != null && pkg.staticSharedLibName != null;
outInfo.removedAppId = ps.appId;
outInfo.removedUsers = userIds;
}
@@ -19252,6 +20120,7 @@
pw.println("Error: check-permission missing package argument");
return;
}
+
String pkg = args[opti];
opti++;
int user = UserHandle.getUserId(Binder.getCallingUid());
@@ -19264,6 +20133,10 @@
return;
}
}
+
+ // Normalize package name to handle renamed packages and static libs
+ pkg = resolveInternalPackageNameLPr(pkg, PackageManager.VERSION_CODE_HIGHEST);
+
pw.println(checkPermission(perm, pkg, user));
return;
} else if ("l".equals(cmd) || "libraries".equals(cmd)) {
@@ -19416,41 +20289,41 @@
boolean printedHeader = false;
final Iterator<String> it = mSharedLibraries.keySet().iterator();
while (it.hasNext()) {
- String name = it.next();
- SharedLibraryEntry ent = mSharedLibraries.get(name);
- if (!checkin) {
- if (!printedHeader) {
- if (dumpState.onTitlePrinted())
- pw.println();
- pw.println("Libraries:");
- printedHeader = true;
- }
- pw.print(" ");
- } else {
- pw.print("lib,");
+ String libName = it.next();
+ SparseArray<SharedLibraryEntry> versionedLib = mSharedLibraries.get(libName);
+ if (versionedLib == null) {
+ continue;
}
- pw.print(name);
- if (!checkin) {
- pw.print(" -> ");
- }
- if (ent.path != null) {
+ final int versionCount = versionedLib.size();
+ for (int i = 0; i < versionCount; i++) {
+ SharedLibraryEntry libEntry = versionedLib.valueAt(i);
if (!checkin) {
- pw.print("(jar) ");
- pw.print(ent.path);
+ if (!printedHeader) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Libraries:");
+ printedHeader = true;
+ }
+ pw.print(" ");
} else {
- pw.print(",jar,");
- pw.print(ent.path);
+ pw.print("lib,");
}
- } else {
+ pw.print(libEntry.info.getName());
+ if (libEntry.info.isStatic()) {
+ pw.print(" version=" + libEntry.info.getVersion());
+ }
if (!checkin) {
- pw.print("(apk) ");
- pw.print(ent.apk);
- } else {
- pw.print(",apk,");
- pw.print(ent.apk);
+ pw.print(" -> ");
}
+ if (libEntry.path != null) {
+ pw.print(" (jar) ");
+ pw.print(libEntry.path);
+ } else {
+ pw.print(" (apk) ");
+ pw.print(libEntry.apk);
+ }
+ pw.println();
}
- pw.println();
}
}
@@ -19581,7 +20454,8 @@
count = 0;
for (PackageSetting ps : allPackageSettings) {
final long status = ps.getDomainVerificationStatusForUser(userId);
- if (status >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ if (status >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
+ && !DEBUG_DOMAIN_VERIFICATION) {
continue;
}
pw.println(prefix + "Package: " + ps.name);
@@ -20459,9 +21333,11 @@
if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
FileUtils.deleteContentsAndDir(Environment.getUserSystemDirectory(userId));
FileUtils.deleteContentsAndDir(Environment.getDataSystemDeDirectory(userId));
+ FileUtils.deleteContentsAndDir(Environment.getDataMiscDeDirectory(userId));
}
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
FileUtils.deleteContentsAndDir(Environment.getDataSystemCeDirectory(userId));
+ FileUtils.deleteContentsAndDir(Environment.getDataMiscCeDirectory(userId));
}
}
@@ -20489,6 +21365,8 @@
.listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
Collections.addAll(files, FileUtils
.listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
+ Collections.addAll(files, FileUtils
+ .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
for (File file : files) {
if (!file.isDirectory()) continue;
@@ -20563,14 +21441,29 @@
}
}
+ private List<String> collectAbsoluteCodePaths() {
+ synchronized (mPackages) {
+ List<String> codePaths = new ArrayList<>();
+ final int packageCount = mSettings.mPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ final PackageSetting ps = mSettings.mPackages.valueAt(i);
+ codePaths.add(ps.codePath.getAbsolutePath());
+ }
+ return codePaths;
+ }
+ }
+
/**
* Examine all apps present on given mounted volume, and destroy apps that
* aren't expected, either due to uninstallation or reinstallation on
* another volume.
*/
private void reconcileApps(String volumeUuid) {
- final File[] files = FileUtils
- .listFilesOrEmpty(Environment.getDataAppDirectory(volumeUuid));
+ List<String> absoluteCodePaths = collectAbsoluteCodePaths();
+ List<File> filesToDelete = null;
+
+ final File[] files = FileUtils.listFilesOrEmpty(
+ Environment.getDataAppDirectory(volumeUuid));
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
@@ -20579,15 +21472,33 @@
continue;
}
- try {
- final PackageLite pkg = PackageParser.parsePackageLite(file,
- PackageParser.PARSE_MUST_BE_APK);
- assertPackageKnown(volumeUuid, pkg.packageName);
+ String absolutePath = file.getAbsolutePath();
- } catch (PackageParserException | PackageManagerException e) {
- logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
+ boolean pathValid = false;
+ final int absoluteCodePathCount = absoluteCodePaths.size();
+ for (int i = 0; i < absoluteCodePathCount; i++) {
+ String absoluteCodePath = absoluteCodePaths.get(i);
+ if (absolutePath.startsWith(absoluteCodePath)) {
+ pathValid = true;
+ break;
+ }
+ }
+
+ if (!pathValid) {
+ if (filesToDelete == null) {
+ filesToDelete = new ArrayList<>();
+ }
+ filesToDelete.add(file);
+ }
+ }
+
+ if (filesToDelete != null) {
+ final int fileToDeleteCount = filesToDelete.size();
+ for (int i = 0; i < fileToDeleteCount; i++) {
+ File fileToDelete = filesToDelete.get(i);
+ logCriticalInfo(Log.WARN, "Destroying orphaned" + fileToDelete);
synchronized (mInstallLock) {
- removeCodePathLI(file);
+ removeCodePathLI(fileToDelete);
}
}
}
@@ -21042,6 +21953,14 @@
"Cannot move system application");
}
+ final boolean isInternalStorage = VolumeInfo.ID_PRIVATE_INTERNAL.equals(volumeUuid);
+ final boolean allow3rdPartyOnInternal = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
+ if (isInternalStorage && !allow3rdPartyOnInternal) {
+ throw new PackageManagerException(MOVE_FAILED_3RD_PARTY_NOT_ALLOWED_ON_INTERNAL,
+ "3rd party apps are not allowed on internal storage");
+ }
+
if (pkg.applicationInfo.isExternalAsec()) {
currentAsec = true;
currentVolumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL;
@@ -21357,7 +22276,8 @@
}
mHandler.post(new Runnable() {
public void run() {
- deletePackageX(packageName, userHandle, 0);
+ deletePackageX(packageName, PackageManager.VERSION_CODE_HIGHEST,
+ userHandle, 0);
} //end run
});
}
@@ -21567,7 +22487,8 @@
// after this method returns.
mHandler.post(new Runnable() {
public void run() {
- deletePackageX(packageName, 0, PackageManager.DELETE_ALL_USERS);
+ deletePackageX(packageName, PackageManager.VERSION_CODE_HIGHEST,
+ 0, PackageManager.DELETE_ALL_USERS);
}
});
}
@@ -21924,9 +22845,24 @@
responseObj, origIntent, resolvedType, launchIntent, callingPackage, userId);
}
+ @Override
+ public void grantEphemeralAccess(int userId, Intent intent,
+ int targetAppId, int ephemeralAppId) {
+ synchronized (mPackages) {
+ mEphemeralApplicationRegistry.grantEphemeralAccessLPw(userId, intent,
+ targetAppId, ephemeralAppId);
+ }
+ }
+
public String getSetupWizardPackageName() {
return mSetupWizardPackage;
}
+
+ public void setExternalSourcesPolicy(ExternalSourcesPolicy policy) {
+ if (policy != null) {
+ mExternalSourcesPolicy = policy;
+ }
+ }
}
@Override
@@ -22016,4 +22952,38 @@
}
return PackageManager.INSTALL_REASON_UNKNOWN;
}
+
+ @Override
+ public boolean canRequestPackageInstalls(String packageName, int userId) {
+ int callingUid = Binder.getCallingUid();
+ int uid = getPackageUid(packageName, 0, userId);
+ if (callingUid != uid && callingUid != Process.ROOT_UID
+ && callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException(
+ "Caller uid " + callingUid + " does not own package " + packageName);
+ }
+ ApplicationInfo info = getApplicationInfo(packageName, 0, userId);
+ if (info == null) {
+ return false;
+ }
+ if (info.targetSdkVersion < Build.VERSION_CODES.O) {
+ throw new UnsupportedOperationException(
+ "Operation only supported on apps targeting Android O or higher");
+ }
+ String appOpPermission = Manifest.permission.REQUEST_INSTALL_PACKAGES;
+ String[] packagesDeclaringPermission = getAppOpPermissionPackages(appOpPermission);
+ if (!ArrayUtils.contains(packagesDeclaringPermission, packageName)) {
+ throw new SecurityException("Need to declare " + appOpPermission + " to call this api");
+ }
+ if (sUserManager.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, userId)) {
+ return false;
+ }
+ if (mExternalSourcesPolicy != null) {
+ int isTrusted = mExternalSourcesPolicy.getPackageTrustedToInstallApps(packageName, uid);
+ if (isTrusted != PackageManagerInternal.ExternalSourcesPolicy.USER_DEFAULT) {
+ return isTrusted == PackageManagerInternal.ExternalSourcesPolicy.USER_TRUSTED;
+ }
+ }
+ return checkUidPermission(appOpPermission, uid) == PERMISSION_GRANTED;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2751742..2f8d749 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -40,6 +40,7 @@
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.ResolveInfo;
+import android.content.pm.VersionedPackage;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.net.Uri;
@@ -161,7 +162,7 @@
if (file.isFile()) {
try {
ApkLite baseApk = PackageParser.parseApkLite(file, 0);
- PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null);
+ PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
pkgLite, false, params.sessionParams.abiOverride));
} catch (PackageParserException | IOException e) {
@@ -586,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 {
@@ -619,6 +621,9 @@
case "-3":
listThirdParty = true;
break;
+ case "--show-versioncode":
+ showVersionCode = true;
+ break;
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
@@ -664,6 +669,10 @@
pw.print("=");
}
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));
@@ -770,6 +779,7 @@
final PrintWriter pw = getOutPrintWriter();
int flags = 0;
int userId = UserHandle.USER_ALL;
+ int versionCode = PackageManager.VERSION_CODE_HIGHEST;
String opt;
while ((opt = getNextOption()) != null) {
@@ -780,6 +790,9 @@
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
+ case "--versionCode":
+ versionCode = Integer.parseInt(getNextArgRequired());
+ break;
default:
pw.println("Error: Unknown option: " + opt);
return 1;
@@ -819,7 +832,8 @@
}
final LocalIntentReceiver receiver = new LocalIntentReceiver();
- mInterface.getPackageInstaller().uninstall(packageName, null /*callerPackageName*/, flags,
+ mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
+ versionCode), null /*callerPackageName*/, flags,
receiver.getIntentSender(), userId);
final Intent result = receiver.getResult();
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 80a398a..5f348ab 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -46,10 +46,12 @@
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
int pVersionCode, int pkgFlags, int privateFlags, String parentPackageName,
- List<String> childPackageNames, int sharedUserId) {
+ List<String> childPackageNames, int sharedUserId, String[] usesStaticLibraries,
+ int[] usesStaticLibrariesVersions) {
super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
- pVersionCode, pkgFlags, privateFlags, parentPackageName, childPackageNames);
+ pVersionCode, pkgFlags, privateFlags, parentPackageName, childPackageNames,
+ usesStaticLibraries, usesStaticLibrariesVersions);
this.sharedUserId = sharedUserId;
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index b332fa5..b63edfd 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -32,6 +32,7 @@
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -68,6 +69,9 @@
File resourcePath;
String resourcePathString;
+ String[] usesStaticLibraries;
+ int[] usesStaticLibrariesVersions;
+
/**
* The path under which native libraries have been unpacked. This path is
* always derived at runtime, and is only stored here for cleanup when a
@@ -139,13 +143,16 @@
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
int pVersionCode, int pkgFlags, int pkgPrivateFlags,
- String parentPackageName, List<String> childPackageNames) {
+ String parentPackageName, List<String> childPackageNames,
+ String[] usesStaticLibraries, int[] usesStaticLibrariesVersions) {
super(pkgFlags, pkgPrivateFlags);
this.name = name;
this.realName = realName;
this.parentPackageName = parentPackageName;
this.childPackageNames = (childPackageNames != null)
? new ArrayList<>(childPackageNames) : null;
+ this.usesStaticLibraries = usesStaticLibraries;
+ this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
init(codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString,
secondaryCpuAbiString, cpuAbiOverrideString, pVersionCode);
}
@@ -250,6 +257,12 @@
versionCode = orig.versionCode;
volumeUuid = orig.volumeUuid;
categoryHint = orig.categoryHint;
+ usesStaticLibraries = orig.usesStaticLibraries != null
+ ? Arrays.copyOf(orig.usesStaticLibraries,
+ orig.usesStaticLibraries.length) : null;
+ usesStaticLibrariesVersions = orig.usesStaticLibrariesVersions != null
+ ? Arrays.copyOf(orig.usesStaticLibrariesVersions,
+ orig.usesStaticLibrariesVersions.length) : null;
}
private PackageUserState modifyUserState(int userId) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 8761a6d..281e445 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -183,6 +183,7 @@
private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions";
private static final String TAG_PERMISSIONS = "perms";
private static final String TAG_CHILD_PACKAGE = "child-package";
+ private static final String TAG_USES_STATIC_LIB = "uses-static-lib";
private static final String TAG_PERSISTENT_PREFERRED_ACTIVITIES =
"persistent-preferred-activities";
@@ -201,6 +202,7 @@
private static final String ATTR_CODE = "code";
private static final String ATTR_GRANTED = "granted";
private static final String ATTR_FLAGS = "flags";
+ private static final String ATTR_VERSION = "version";
private static final String ATTR_CE_DATA_INODE = "ceDataInode";
private static final String ATTR_INSTALLED = "inst";
@@ -561,7 +563,8 @@
p.legacyNativeLibraryPathString, p.primaryCpuAbiString,
p.secondaryCpuAbiString, p.cpuAbiOverrideString,
p.appId, p.versionCode, p.pkgFlags, p.pkgPrivateFlags,
- p.parentPackageName, p.childPackageNames);
+ p.parentPackageName, p.childPackageNames, p.usesStaticLibraries,
+ p.usesStaticLibrariesVersions);
mDisabledSysPackages.remove(name);
return ret;
}
@@ -578,7 +581,8 @@
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, int vc, int
pkgFlags, int pkgPrivateFlags, String parentPackageName,
- List<String> childPackageNames) {
+ List<String> childPackageNames, String[] usesStaticLibraries,
+ int[] usesStaticLibraryNames) {
PackageSetting p = mPackages.get(name);
if (p != null) {
if (p.appId == uid) {
@@ -591,7 +595,7 @@
p = new PackageSetting(name, realName, codePath, resourcePath,
legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags, parentPackageName,
- childPackageNames, 0 /*userId*/);
+ childPackageNames, 0 /*userId*/, usesStaticLibraries, usesStaticLibraryNames);
p.appId = uid;
if (addUserIdLPw(uid, p, name)) {
mPackages.put(name, p);
@@ -678,7 +682,8 @@
File codePath, File resourcePath, String legacyNativeLibraryPath, String primaryCpuAbi,
String secondaryCpuAbi, int versionCode, int pkgFlags, int pkgPrivateFlags,
UserHandle installUser, boolean allowInstall, String parentPkgName,
- List<String> childPkgNames, UserManagerService userManager) {
+ List<String> childPkgNames, UserManagerService userManager,
+ String[] usesStaticLibraries, int[] usesStaticLibrariesVersions) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -699,13 +704,16 @@
// overwrite the signatures in the original package setting.
pkgSetting.signatures = new PackageSignatures();
pkgSetting.versionCode = versionCode;
+ pkgSetting.usesStaticLibraries = usesStaticLibraries;
+ pkgSetting.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
// Update new package state.
pkgSetting.setTimeStamp(codePath.lastModified());
} else {
pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, resourcePath,
legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
- parentPkgName, childPkgNames, 0 /*sharedUserId*/);
+ parentPkgName, childPkgNames, 0 /*sharedUserId*/, usesStaticLibraries,
+ usesStaticLibrariesVersions);
pkgSetting.setTimeStamp(codePath.lastModified());
pkgSetting.sharedUser = sharedUser;
// If this is not a system app, it starts out stopped.
@@ -782,7 +790,8 @@
@NonNull File codePath, @Nullable String legacyNativeLibraryPath,
@Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi,
int pkgFlags, int pkgPrivateFlags, @Nullable List<String> childPkgNames,
- @NonNull UserManagerService userManager) throws PackageManagerException {
+ @NonNull UserManagerService userManager, @Nullable String[] usesStaticLibraries,
+ @Nullable int[] usesStaticLibrariesVersions) throws PackageManagerException {
final String pkgName = pkgSetting.name;
if (pkgSetting.sharedUser != sharedUser) {
PackageManagerService.reportSettingsProblem(Log.WARN,
@@ -844,6 +853,14 @@
if (childPkgNames != null) {
pkgSetting.childPackageNames = new ArrayList<>(childPkgNames);
}
+ if (usesStaticLibraries != null) {
+ pkgSetting.usesStaticLibraries = Arrays.copyOf(usesStaticLibraries,
+ usesStaticLibraries.length);
+ }
+ if (usesStaticLibrariesVersions != null) {
+ pkgSetting.usesStaticLibrariesVersions = Arrays.copyOf(usesStaticLibrariesVersions,
+ usesStaticLibrariesVersions.length);
+ }
}
/**
@@ -949,6 +966,16 @@
if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) {
p.sharedUser.signatures.assignSignatures(pkg.mSignatures);
}
+ // Update static shared library dependencies if needed
+ if (pkg.usesStaticLibraries != null && pkg.usesStaticLibrariesVersions != null
+ && pkg.usesStaticLibraries.size() == pkg.usesStaticLibrariesVersions.length) {
+ String[] usesStaticLibraries = new String[pkg.usesStaticLibraries.size()];
+ pkg.usesStaticLibraries.toArray(usesStaticLibraries);
+ p.usesStaticLibrariesVersions = pkg.usesStaticLibrariesVersions;
+ } else {
+ pkg.usesStaticLibraries = null;
+ p.usesStaticLibrariesVersions = null;
+ }
addPackageSettingLPw(p, p.sharedUser);
}
@@ -2163,6 +2190,53 @@
}
}
+ void readUsesStaticLibLPw(XmlPullParser parser, PackageSetting outPs)
+ throws IOException, XmlPullParserException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String libName = parser.getAttributeValue(null, ATTR_NAME);
+ String libVersionStr = parser.getAttributeValue(null, ATTR_VERSION);
+
+ int libVersion = -1;
+ try {
+ libVersion = Integer.parseInt(libVersionStr);
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+
+ if (libName != null && libVersion >= 0) {
+ outPs.usesStaticLibraries = ArrayUtils.appendElement(String.class,
+ outPs.usesStaticLibraries, libName);
+ outPs.usesStaticLibrariesVersions = ArrayUtils.appendInt(
+ outPs.usesStaticLibrariesVersions, libVersion);
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ void writeUsesStaticLibLPw(XmlSerializer serializer, String[] usesStaticLibraries,
+ int[] usesStaticLibraryVersions) throws IOException {
+ if (ArrayUtils.isEmpty(usesStaticLibraries) || ArrayUtils.isEmpty(usesStaticLibraryVersions)
+ || usesStaticLibraries.length != usesStaticLibraryVersions.length) {
+ return;
+ }
+ final int libCount = usesStaticLibraries.length;
+ for (int i = 0; i < libCount; i++) {
+ final String libName = usesStaticLibraries[i];
+ final int libVersion = usesStaticLibraryVersions[i];
+ serializer.startTag(null, TAG_USES_STATIC_LIB);
+ serializer.attribute(null, ATTR_NAME, libName);
+ serializer.attribute(null, ATTR_VERSION, Integer.toString(libVersion));
+ serializer.endTag(null, TAG_USES_STATIC_LIB);
+ }
+ }
+
// Note: assumed "stopped" field is already cleared in all packages.
// Legacy reader, used to read in the old file format after an upgrade. Not used after that.
void readStoppedLPw() {
@@ -2622,6 +2696,8 @@
writeChildPackagesLPw(serializer, pkg.childPackageNames);
+ writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
+
// If this is a shared user, the permissions will be written there.
if (pkg.sharedUser == null) {
writePermissionsLPr(serializer, pkg.getPermissionsState()
@@ -2692,6 +2768,8 @@
writeChildPackagesLPw(serializer, pkg.childPackageNames);
+ writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
+
pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
writePermissionsLPr(serializer, pkg.getPermissionsState()
@@ -3465,7 +3543,7 @@
PackageSetting ps = new PackageSetting(name, realName, codePathFile,
new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr,
secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags,
- parentPackageName, null /*childPackageNames*/, 0 /*sharedUserId*/);
+ parentPackageName, null /*childPackageNames*/, 0 /*sharedUserId*/, null, null);
String timeStampStr = parser.getAttributeValue(null, "ft");
if (timeStampStr != null) {
try {
@@ -3520,6 +3598,8 @@
ps.childPackageNames = new ArrayList<>();
}
ps.childPackageNames.add(childPackageName);
+ } else if (parser.getName().equals(TAG_USES_STATIC_LIB)) {
+ readUsesStaticLibLPw(parser, ps);
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <updated-package>: " + parser.getName());
@@ -3705,7 +3785,8 @@
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,
secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,
- pkgPrivateFlags, parentPackageName, null /*childPackageNames*/);
+ pkgPrivateFlags, parentPackageName, null /*childPackageNames*/,
+ null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
+ userId + " pkg=" + packageSetting);
@@ -3724,7 +3805,8 @@
codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
versionCode, pkgFlags, pkgPrivateFlags, parentPackageName,
- null /*childPackageNames*/, sharedUserId);
+ null /*childPackageNames*/, sharedUserId,
+ null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/);
packageSetting.setTimeStamp(timeStamp);
packageSetting.firstInstallTime = firstInstallTime;
packageSetting.lastUpdateTime = lastUpdateTime;
@@ -4294,6 +4376,7 @@
ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET, "RESIZEABLE_ACTIVITIES_EXPLICITLY_SET",
ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION, "RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION",
ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND, "BACKUP_IN_FOREGROUND",
+ ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY, "STATIC_SHARED_LIBRARY",
};
void dumpVersionLPr(IndentingPrintWriter pw) {
@@ -4486,23 +4569,39 @@
}
pw.println("]");
if (ps.pkg.libraryNames != null && ps.pkg.libraryNames.size() > 0) {
- pw.print(prefix); pw.println(" libraries:");
- for (int i=0; i<ps.pkg.libraryNames.size(); i++) {
- pw.print(prefix); pw.print(" "); pw.println(ps.pkg.libraryNames.get(i));
+ pw.print(prefix); pw.println(" dynamic libraries:");
+ for (int i = 0; i<ps.pkg.libraryNames.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.println(ps.pkg.libraryNames.get(i));
}
}
+ if (ps.pkg.staticSharedLibName != null) {
+ pw.print(prefix); pw.println(" static library:");
+ pw.print(prefix); pw.print(" ");
+ pw.print("name:"); pw.print(ps.pkg.staticSharedLibName);
+ pw.print(" version:"); pw.println(ps.pkg.staticSharedLibVersion);
+ }
if (ps.pkg.usesLibraries != null && ps.pkg.usesLibraries.size() > 0) {
pw.print(prefix); pw.println(" usesLibraries:");
for (int i=0; i<ps.pkg.usesLibraries.size(); i++) {
pw.print(prefix); pw.print(" "); pw.println(ps.pkg.usesLibraries.get(i));
}
}
+ if (ps.pkg.usesStaticLibraries != null
+ && ps.pkg.usesStaticLibraries.size() > 0) {
+ pw.print(prefix); pw.println(" usesStaticLibraries:");
+ for (int i=0; i<ps.pkg.usesStaticLibraries.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.print(ps.pkg.usesStaticLibraries.get(i)); pw.print(" version:");
+ pw.println(ps.pkg.usesStaticLibrariesVersions[i]);
+ }
+ }
if (ps.pkg.usesOptionalLibraries != null
&& ps.pkg.usesOptionalLibraries.size() > 0) {
pw.print(prefix); pw.println(" usesOptionalLibraries:");
for (int i=0; i<ps.pkg.usesOptionalLibraries.size(); i++) {
pw.print(prefix); pw.print(" ");
- pw.println(ps.pkg.usesOptionalLibraries.get(i));
+ pw.println(ps.pkg.usesOptionalLibraries.get(i));
}
}
if (ps.pkg.usesLibraryFiles != null
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index a156356..6eac5e3 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -25,7 +25,6 @@
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.PinItemRequest;
import android.content.pm.ShortcutInfo;
-import android.os.Binder;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
@@ -168,8 +167,8 @@
mLock = lock;
}
- public boolean isRequestPinnedShortcutSupported(int callingUserId) {
- return getRequestPinShortcutConfirmationActivity(callingUserId) != null;
+ public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
+ return getRequestPinConfirmationActivity(callingUserId, requestType) != null;
}
/**
@@ -185,8 +184,10 @@
// First, make sure the launcher supports it.
// Find the confirmation activity in the default launcher.
+ final int requestType = inShortcut != null ?
+ PinItemRequest.REQUEST_TYPE_SHORTCUT : PinItemRequest.REQUEST_TYPE_APPWIDGET;
final Pair<ComponentName, Integer> confirmActivity =
- getRequestPinShortcutConfirmationActivity(userId);
+ getRequestPinConfirmationActivity(userId, requestType);
// If the launcher doesn't support it, just return a rejected result and finish.
if (confirmActivity == null) {
@@ -203,20 +204,15 @@
// Next, validate the incoming shortcut, etc.
final PinItemRequest request;
if (inShortcut != null) {
- request = requestPinShortcutLocked(inShortcut, resultIntent, confirmActivity,
- true /* ignoreIfAlreadyPinned */);
+ request = requestPinShortcutLocked(inShortcut, resultIntent, confirmActivity);
} else {
int launcherUid = mService.injectGetPackageUid(
confirmActivity.first.getPackageName(), launcherUserId);
request = new PinItemRequest(inAppWidget,
new PinItemRequestInner(this, resultIntent, launcherUid));
}
-
- if (request == null) {
- sendResultIntent(resultIntent, null);
- return true;
- }
- return startRequestConfirmActivity(confirmActivity.first, launcherUserId, request);
+ return startRequestConfirmActivity(confirmActivity.first, launcherUserId, request,
+ requestType);
}
/**
@@ -238,23 +234,17 @@
mService.throwIfUserLockedL(launcherUserId);
// Next, validate the incoming shortcut, etc.
- PinItemRequest request = requestPinShortcutLocked(inShortcut, null,
- Pair.create(defaultLauncher, launcherUserId), false /* ignoreIfAlreadyPinned */);
- if (request == null) {
- return null;
- }
+ final PinItemRequest request = requestPinShortcutLocked(inShortcut, null,
+ Pair.create(defaultLauncher, launcherUserId));
return new Intent().putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request);
}
/**
* Handle {@link android.content.pm.ShortcutManager#requestPinShortcut)}.
- *
- * @param ignoreIfAlreadyPinned if true and the {@param inShortcut} is already pinned for
- * {@param confirmActivity}, null is returned instead.
*/
+ @NonNull
private PinItemRequest requestPinShortcutLocked(ShortcutInfo inShortcut,
- IntentSender resultIntent, Pair<ComponentName, Integer> confirmActivity,
- boolean ignoreIfAlreadyPinned) {
+ IntentSender resultIntentOriginal, Pair<ComponentName, Integer> confirmActivity) {
final ShortcutPackage ps = mService.getPackageShortcutsForPublisherLocked(
inShortcut.getPackage(), inShortcut.getUserId());
@@ -272,16 +262,20 @@
final String launcherPackage = confirmActivity.first.getPackageName();
final int launcherUserId = confirmActivity.second;
+ IntentSender resultIntentToSend = resultIntentOriginal;
+
if (existsAlready) {
validateExistingShortcut(existing);
final boolean isAlreadyPinned = mService.getLauncherShortcutsLocked(
launcherPackage, existing.getUserId(), launcherUserId).hasPinned(existing);
- // See if it's already pinned.
- if (ignoreIfAlreadyPinned && isAlreadyPinned) {
- Log.i(TAG, "Launcher's already pinning shortcut " + existing.getId()
- + " for package " + existing.getPackage());
- return null;
+ if (isAlreadyPinned) {
+ // When the shortcut is already pinned by this launcher, the request will always
+ // succeed, so just send the result at this point.
+ sendResultIntent(resultIntentOriginal, null);
+
+ // So, do not send the intent again.
+ resultIntentToSend = null;
}
// Pass a clone, not the original.
@@ -289,7 +283,7 @@
shortcutForLauncher = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
if (!isAlreadyPinned) {
- // FLAG_PINNED is still set, if it's pinned by other launchers.
+ // FLAG_PINNED may still be set, if it's pinned by other launchers.
shortcutForLauncher.clearFlags(ShortcutInfo.FLAG_PINNED);
}
} else {
@@ -320,8 +314,8 @@
// Create a request object.
final PinShortcutRequestInner inner =
- new PinShortcutRequestInner(this, inShortcut, shortcutForLauncher, resultIntent,
- launcherPackage, launcherUserId,
+ new PinShortcutRequestInner(this, inShortcut, shortcutForLauncher,
+ resultIntentToSend, launcherPackage, launcherUserId,
mService.injectGetPackageUid(launcherPackage, launcherUserId),
existsAlready);
@@ -338,9 +332,13 @@
}
private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId,
- PinItemRequest request) {
+ PinItemRequest request, int requestType) {
+ final String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ?
+ LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT :
+ LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET;
+
// Start the activity.
- final Intent confirmIntent = new Intent(LauncherApps.ACTION_CONFIRM_PIN_ITEM);
+ final Intent confirmIntent = new Intent(action);
confirmIntent.setComponent(activity);
confirmIntent.putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request);
confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -359,13 +357,13 @@
}
/**
- * Find the activity that handles {@link LauncherApps#ACTION_CONFIRM_PIN_ITEM} in the
+ * Find the activity that handles {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} in the
* default launcher.
*/
@Nullable
@VisibleForTesting
- Pair<ComponentName, Integer> getRequestPinShortcutConfirmationActivity(
- int callingUserId) {
+ Pair<ComponentName, Integer> getRequestPinConfirmationActivity(
+ int callingUserId, int requestType) {
// Find the default launcher.
final int launcherUserId = mService.getParentOrSelfUserId(callingUserId);
final ComponentName defaultLauncher = mService.getDefaultLauncher(launcherUserId);
@@ -375,7 +373,7 @@
return null;
}
final ComponentName activity = mService.injectGetPinConfirmationActivity(
- defaultLauncher.getPackageName(), launcherUserId);
+ defaultLauncher.getPackageName(), launcherUserId, requestType);
return (activity == null) ? null : Pair.create(activity, launcherUserId);
}
@@ -410,6 +408,16 @@
return false;
}
+ final ShortcutLauncher launcher = mService.getLauncherShortcutsLocked(
+ launcherPackage, appUserId, launcherUserId);
+ launcher.attemptToRestoreIfNeededAndSave();
+ if (launcher.hasPinned(original)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Shortcut " + original + " already pinned.");
+ }
+ return true;
+ }
+
final ShortcutPackage ps = mService.getPackageShortcutsForPublisherLocked(
appPackageName, appUserId);
final ShortcutInfo current = ps.findShortcutById(shortcutId);
@@ -447,9 +455,6 @@
Slog.d(TAG, "Pinning " + shortcutId);
}
- final ShortcutLauncher launcher = mService.getLauncherShortcutsLocked(
- launcherPackage, appUserId, launcherUserId);
- launcher.attemptToRestoreIfNeededAndSave();
launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId);
if (current == null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index ae709fe..c0fc24c 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.
@@ -2116,10 +2117,11 @@
}
@Override
- public boolean isRequestPinShortcutSupported(int callingUserId) {
+ public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
final long token = injectClearCallingIdentity();
try {
- return mShortcutRequestPinProcessor.isRequestPinnedShortcutSupported(callingUserId);
+ return mShortcutRequestPinProcessor
+ .isRequestPinItemSupported(callingUserId, requestType);
} finally {
injectRestoreCallingIdentity(token);
}
@@ -2620,6 +2622,11 @@
Preconditions.checkNotNull(appWidget);
return requestPinItem(callingPackage, userId, null, appWidget, resultIntent);
}
+
+ @Override
+ public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
+ return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType);
+ }
}
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -3073,6 +3080,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 +3108,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);
@@ -3241,16 +3256,19 @@
}
/**
- * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_ITEM} activity in a given package.
+ * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} or
+ * {@link LauncherApps#ACTION_CONFIRM_PIN_APPWIDGET} activity in a given package depending on
+ * the requestType.
*/
@Nullable
ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName,
- int launcherUserId) {
+ int launcherUserId, int requestType) {
Preconditions.checkNotNull(launcherPackageName);
+ String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ?
+ LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT :
+ LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET;
- final Intent confirmIntent = new Intent(LauncherApps.ACTION_CONFIRM_PIN_ITEM);
- confirmIntent.setPackage(launcherPackageName);
-
+ final Intent confirmIntent = new Intent(action).setPackage(launcherPackageName);
final List<ResolveInfo> candidates = queryActivities(
confirmIntent, launcherUserId, /* exportedOnly =*/ false);
for (ResolveInfo ri : candidates) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e646ffc..9d2d9e5 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;
@@ -718,7 +716,7 @@
return null;
}
int parentUserId = profile.profileGroupId;
- if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+ if (parentUserId == userHandle || parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
return null;
} else {
return getUserInfoLU(parentUserId);
@@ -1567,7 +1565,7 @@
}
synchronized(mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
- if (!userInfo.canHaveProfile()) {
+ if (userInfo == null || !userInfo.canHaveProfile()) {
return false;
}
int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
@@ -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/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 6d06838..e809213 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -132,8 +132,8 @@
// - new installed splits
// If we can't find the owner of the dex we simply do not track it. The impact is
// that the dex file will not be considered for offline optimizations.
- // TODO(calin): add hooks for install/uninstall notifications to
- // capture new or obsolete packages.
+ // TODO(calin): add hooks for move/uninstall notifications to
+ // capture package moves or obsolete packages.
if (DEBUG) {
Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
}
@@ -157,6 +157,20 @@
}
}
+ public void notifyPackageInstalled(PackageInfo info, int userId) {
+ cachePackageCodeLocation(info, userId);
+ }
+
+ private void cachePackageCodeLocation(PackageInfo info, int userId) {
+ PackageCodeLocations pcl = mPackageCodeLocationsCache.get(info.packageName);
+ if (pcl != null) {
+ pcl.mergeAppDataDirs(info.applicationInfo, userId);
+ } else {
+ mPackageCodeLocationsCache.put(info.packageName,
+ new PackageCodeLocations(info.applicationInfo, userId));
+ }
+ }
+
private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
// Cache the code locations for the installed packages. This allows for
@@ -166,13 +180,8 @@
int userId = entry.getKey();
for (PackageInfo pi : packageInfoList) {
// Cache the code locations.
- PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName);
- if (pcl != null) {
- pcl.mergeAppDataDirs(pi.applicationInfo, userId);
- } else {
- mPackageCodeLocationsCache.put(pi.packageName,
- new PackageCodeLocations(pi.applicationInfo, userId));
- }
+ cachePackageCodeLocation(pi, userId);
+
// Cache a map from package name to the set of user ids who installed the package.
// We will use it to sync the data and remove obsolete entries from
// mPackageDexUsage.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 32b8c9b..4350ed9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -19,6 +19,8 @@
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.HOME_STACK_ID;
+import static android.content.Context.DISPLAY_SERVICE;
+import static android.content.Context.WINDOW_SERVICE;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -140,6 +142,7 @@
import android.database.ContentObserver;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
@@ -2254,8 +2257,7 @@
}
lp.format = PixelFormat.TRANSLUCENT;
lp.setTitle("PointerLocation");
- WindowManager wm = (WindowManager)
- mContext.getSystemService(Context.WINDOW_SERVICE);
+ WindowManager wm = (WindowManager) mContext.getSystemService(WINDOW_SERVICE);
lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
wm.addView(mPointerLocationView, lp);
mWindowManagerFuncs.registerPointerEventListener(mPointerLocationView);
@@ -2265,7 +2267,7 @@
private void disablePointerLocation() {
if (mPointerLocationView != null) {
mWindowManagerFuncs.unregisterPointerEventListener(mPointerLocationView);
- WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ WindowManager wm = (WindowManager) mContext.getSystemService(WINDOW_SERVICE);
wm.removeView(mPointerLocationView);
mPointerLocationView = null;
}
@@ -2826,7 +2828,7 @@
@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 logo, int windowFlags, Configuration overrideConfig, int displayId) {
if (!SHOW_SPLASH_SCREENS) {
return null;
}
@@ -2927,7 +2929,13 @@
params.setTitle("Splash Screen " + packageName);
- wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ // Obtain proper context to launch on the right display.
+ final Context displayContext = getDisplayContext(context, displayId);
+ if (displayContext == null) {
+ // Can't show splash screen on requested display, so skip showing at all.
+ return null;
+ }
+ wm = (WindowManager) displayContext.getSystemService(WINDOW_SERVICE);
view = win.getDecorView();
if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "Adding splash screen window for "
@@ -2957,6 +2965,24 @@
return null;
}
+ /** Obtain proper context for showing splash screen on the provided display. */
+ private Context getDisplayContext(Context context, int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ // The default context fits.
+ return context;
+ }
+
+ final DisplayManager dm = (DisplayManager) context.getSystemService(DISPLAY_SERVICE);
+ final Display targetDisplay = dm.getDisplay(displayId);
+ if (targetDisplay == null) {
+ // Failed to obtain the non-default display where splash screen should be shown,
+ // lets not show at all.
+ return null;
+ }
+
+ return context.createDisplayContext(targetDisplay);
+ }
+
/**
* Preflight adding a window to the system.
*
@@ -3537,6 +3563,9 @@
dispatchDirectAudioEvent(event);
return -1;
}
+ } else if (keyCode == KeyEvent.KEYCODE_TAB && event.isMetaPressed()) {
+ // Pass through keyboard navigation keys.
+ return 0;
}
// Toggle Caps Lock on META-ALT.
@@ -5870,9 +5899,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;
}
}
@@ -5885,8 +5913,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/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8aefebc..238866a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -57,6 +57,8 @@
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.util.EventLog;
+import android.util.KeyValueListParser;
+import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -69,6 +71,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.server.EventLogTags;
+import com.android.server.RescueParty;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.Watchdog;
@@ -520,6 +523,65 @@
// True if we are currently in VR Mode.
private boolean mIsVrModeEnabled;
+ /**
+ * All times are in milliseconds. These constants are kept synchronized with the system
+ * global Settings. Any access to this class or its fields should be done while
+ * holding the PowerManagerService.mLock lock.
+ */
+ private final class Constants extends ContentObserver {
+ // Key names stored in the settings value.
+ private static final String KEY_NO_CACHED_WAKE_LOCKS = "no_cached_wake_locks";
+
+ private static final boolean DEFAULT_NO_CACHED_WAKE_LOCKS = true;
+
+ // Prevent processes that are cached from holding wake locks?
+ public boolean NO_CACHED_WAKE_LOCKS = DEFAULT_NO_CACHED_WAKE_LOCKS;
+
+ private ContentResolver mResolver;
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+ public Constants(Handler handler) {
+ super(handler);
+ }
+
+ public void start(ContentResolver resolver) {
+ mResolver = resolver;
+ mResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.POWER_MANAGER_CONSTANTS), false, this);
+ updateConstants();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateConstants();
+ }
+
+ private void updateConstants() {
+ synchronized (mLock) {
+ try {
+ mParser.setString(Settings.Global.getString(mResolver,
+ Settings.Global.POWER_MANAGER_CONSTANTS));
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on
+ // with defaults.
+ Slog.e(TAG, "Bad alarm manager settings", e);
+ }
+
+ NO_CACHED_WAKE_LOCKS = mParser.getBoolean(KEY_NO_CACHED_WAKE_LOCKS,
+ DEFAULT_NO_CACHED_WAKE_LOCKS);
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println(" Settings " + Settings.Global.POWER_MANAGER_CONSTANTS + ":");
+
+ pw.print(" "); pw.print(KEY_NO_CACHED_WAKE_LOCKS); pw.print("=");
+ pw.println(NO_CACHED_WAKE_LOCKS);
+ }
+ }
+
+ final Constants mConstants;
+
private native void nativeInit();
private static native void nativeAcquireSuspendBlocker(String name);
@@ -536,6 +598,7 @@
Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
mHandlerThread.start();
mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
+ mConstants = new Constants(mHandler);
synchronized (mLock) {
mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
@@ -614,6 +677,9 @@
mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"),
mPolicy);
+ final ContentResolver resolver = mContext.getContentResolver();
+ mConstants.start(resolver);
+
mWirelessChargerDetector = new WirelessChargerDetector(sensorManager,
createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"),
mHandler);
@@ -627,7 +693,6 @@
mDisplayPowerCallbacks, mHandler, sensorManager);
// Register for settings changes.
- final ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.SCREENSAVER_ENABLED),
false, mSettingsObserver, UserHandle.USER_ALL);
@@ -2549,7 +2614,14 @@
private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
final String reason, boolean wait) {
if (mHandler == null || !mSystemReady) {
- throw new IllegalStateException("Too early to call shutdown() or reboot()");
+ if (RescueParty.isAttemptingFactoryReset()) {
+ // If we're stuck in a really low-level reboot loop, and a
+ // rescue party is trying to prompt the user for a factory data
+ // reset, we must GET TO DA CHOPPA!
+ PowerManagerService.lowLevelReboot(reason);
+ } else {
+ throw new IllegalStateException("Too early to call shutdown() or reboot()");
+ }
}
Runnable runnable = new Runnable() {
@@ -2786,7 +2858,7 @@
state.mProcState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
disabled = true;
}
- } else {
+ } else if (mConstants.NO_CACHED_WAKE_LOCKS) {
disabled = !wakeLock.mUidState.mActive &&
wakeLock.mUidState.mProcState
!= ActivityManager.PROCESS_STATE_NONEXISTENT &&
@@ -2996,6 +3068,7 @@
final WirelessChargerDetector wcd;
synchronized (mLock) {
pw.println("Power Manager State:");
+ mConstants.dump(pw);
pw.println(" mDirty=0x" + Integer.toHexString(mDirty));
pw.println(" mWakefulness=" + PowerManagerInternal.wakefulnessToString(mWakefulness));
pw.println(" mWakefulnessChanging=" + mWakefulnessChanging);
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 90c711a..a3837b2 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -338,9 +338,11 @@
mTotalMemory = (long)mDataFileStats.getBlockCount() *
mDataFileStats.getBlockSize();
mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
- mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
- mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
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/storage/DiskStatsLoggingService.java b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
index 0a3abf3..7c43162 100644
--- a/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
+++ b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
@@ -21,6 +21,7 @@
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.AsyncTask;
@@ -28,6 +29,7 @@
import android.os.Environment;
import android.os.Environment.UserEnvironment;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -54,7 +56,7 @@
public boolean onStartJob(JobParameters params) {
// We need to check the preconditions again because they may not be enforced for
// subsequent runs.
- if (!isCharging(this)) {
+ if (!isCharging(this) || !isDumpsysTaskEnabled(getContentResolver())) {
jobFinished(params, true);
return false;
}
@@ -105,6 +107,12 @@
}
@VisibleForTesting
+ static boolean isDumpsysTaskEnabled(ContentResolver resolver) {
+ // The default is to treat the task as enabled.
+ return Settings.Global.getInt(resolver, Settings.Global.ENABLE_DISKSTATS_LOGGING, 1) != 0;
+ }
+
+ @VisibleForTesting
static class LogRunnable implements Runnable {
private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
diff --git a/services/core/java/com/android/server/storage/FileCollector.java b/services/core/java/com/android/server/storage/FileCollector.java
index 90f9f139..59cfaf7 100644
--- a/services/core/java/com/android/server/storage/FileCollector.java
+++ b/services/core/java/com/android/server/storage/FileCollector.java
@@ -26,7 +26,6 @@
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
/**
* FileCollector walks over a directory and categorizes storage usage by their type.
@@ -44,8 +43,9 @@
AUDIO })
private @interface FileTypes {}
-
- private static final Map<String, Integer> EXTENSION_MAP = new ArrayMap<String, Integer>();
+ // NOTE: If you update these extensions, you'll also want to update
+ // matchgen.py over in installd which is used for non-quota stats.
+ private static final ArrayMap<String, Integer> EXTENSION_MAP = new ArrayMap<>();
static {
// Audio
EXTENSION_MAP.put("aac", AUDIO);
@@ -144,6 +144,36 @@
EXTENSION_MAP.put("xwd", IMAGES);
}
+ private static File mkdir(File parent, String name) {
+ final File file = new File(parent, name);
+ file.mkdir();
+ return file;
+ }
+
+ /**
+ * Update the mapping used by sdcardfs to map from file extensions to GIDs
+ * used for statistics purposes.
+ */
+ public static void updateKernelExtensions() {
+ final File root = new File("/config/sdcardfs/extensions/");
+ if (!root.exists()) return;
+
+ final File audio = mkdir(root, Integer.toString(android.os.Process.MEDIA_AUDIO_GID));
+ final File video = mkdir(root, Integer.toString(android.os.Process.MEDIA_VIDEO_GID));
+ final File image = mkdir(root, Integer.toString(android.os.Process.MEDIA_IMAGE_GID));
+
+ for (int i = 0; i < EXTENSION_MAP.size(); i++) {
+ final String ext = EXTENSION_MAP.keyAt(i);
+ final int type = EXTENSION_MAP.valueAt(i);
+
+ switch (type) {
+ case AUDIO: mkdir(audio, ext); break;
+ case VIDEO: mkdir(video, ext); break;
+ case IMAGES: mkdir(image, ext); break;
+ }
+ }
+ }
+
/**
* Returns the file categorization measurement result.
* @param path Directory to collect and categorize storage in.
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index e7c5384..6c1648c 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -321,6 +321,19 @@
}
/**
+ * @see android.service.trust.TrustAgentService#onUnlockLockout(int)
+ */
+ public void onUnlockLockout(int timeoutMs) {
+ try {
+ if (mTrustAgentService != null) {
+ mTrustAgentService.onUnlockLockout(timeoutMs);
+ }
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ }
+
+ /**
* @see android.service.trust.TrustAgentService#onDeviceLocked()
*/
public void onDeviceLocked() {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index c69b87c..71b725e 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -105,6 +105,7 @@
private static final int MSG_FLUSH_TRUST_USUALLY_MANAGED = 10;
private static final int MSG_UNLOCK_USER = 11;
private static final int MSG_STOP_USER = 12;
+ private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13;
private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
@@ -335,13 +336,16 @@
if (!mStrongAuthTracker.canAgentsRunForUser(userInfo.id)) {
int flag = mStrongAuthTracker.getStrongAuthForUser(userInfo.id);
- if (flag != StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
- || !directUnlock) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
- + ": prevented by StrongAuthTracker = 0x"
- + Integer.toHexString(mStrongAuthTracker.getStrongAuthForUser(
- userInfo.id)));
- continue;
+ if (flag != StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) {
+ if (flag != StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ || !directUnlock) {
+ if (DEBUG)
+ Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
+ + ": prevented by StrongAuthTracker = 0x"
+ + Integer.toHexString(mStrongAuthTracker.getStrongAuthForUser(
+ userInfo.id)));
+ continue;
+ }
}
}
@@ -650,6 +654,15 @@
}
}
+ private void dispatchUnlockLockout(int timeoutMs, int userId) {
+ for (int i = 0; i < mActiveAgents.size(); i++) {
+ AgentInfo info = mActiveAgents.valueAt(i);
+ if (info.userId == userId) {
+ info.agent.onUnlockLockout(timeoutMs);
+ }
+ }
+ }
+
// Listeners
private void addListener(ITrustListener listener) {
@@ -745,6 +758,13 @@
}
@Override
+ public void reportUnlockLockout(int timeoutMs, int userId) throws RemoteException {
+ enforceReportPermission();
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_LOCKOUT, timeoutMs, userId)
+ .sendToTarget();
+ }
+
+ @Override
public void reportEnabledTrustAgentsChanged(int userId) throws RemoteException {
enforceReportPermission();
// coalesce refresh messages.
@@ -975,6 +995,9 @@
case MSG_DISPATCH_UNLOCK_ATTEMPT:
dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2);
break;
+ case MSG_DISPATCH_UNLOCK_LOCKOUT:
+ dispatchUnlockLockout(msg.arg1, msg.arg2);
+ break;
case MSG_ENABLED_AGENTS_CHANGED:
refreshAgentList(UserHandle.USER_ALL);
// This is also called when the security mode of a user changes.
diff --git a/services/core/java/com/android/server/vr/CompatibilityDisplay.java b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
new file mode 100644
index 0000000..15edaaf
--- /dev/null
+++ b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
@@ -0,0 +1,119 @@
+
+package com.android.server.vr;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.vr.IVrStateCallbacks;
+import android.service.vr.IVrManager;
+import android.util.Log;
+
+import com.android.server.vr.VrManagerService;
+
+/**
+ * Creates a 2D Virtual Display while VR Mode is enabled. This display will be used to run and
+ * render 2D app within a VR experience. For example, bringing up the 2D calculator app in VR.
+ */
+class CompatibilityDisplay {
+ private final static String TAG = "CompatDisplay";
+ private final static boolean DEBUG = false;
+
+ // TODO: Go over these values and figure out what is best
+ private final static int HEIGHT = 960;
+ private final static int WIDTH = 720;
+ private final static int DPI = 320;
+
+ private final DisplayManager mDisplayManager;
+ private final IVrManager mVrManager;
+
+ // TODO: Lock initially created when VrStateCallback was connected through Binder. This may not
+ // be necessary with the direct access to VrManager.
+ private final Object vdLock = new Object();
+
+ private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+ @Override
+ public void onVrStateChanged(boolean enabled) {
+ if (enabled != mIsVrModeEnabled) {
+ mIsVrModeEnabled = enabled;
+ if (enabled) {
+ // TODO: Consider not creating the display until ActivityManager needs one on
+ // which to display a 2D application.
+ startVirtualDisplay();
+ } else {
+ stopVirtualDisplay();
+ }
+ }
+ }
+ };
+
+ private VirtualDisplay mVirtualDisplay;
+ private boolean mIsVrModeEnabled;
+
+ public CompatibilityDisplay(DisplayManager displayManager, IVrManager vrManager) {
+ mDisplayManager = displayManager;
+ mVrManager = vrManager;
+ }
+
+ /**
+ * Initializes the compabilitiy display by listening to VR mode changes.
+ */
+ public void init() {
+ startVrModeListener();
+ }
+
+ private void startVrModeListener() {
+ if (mVrManager != null) {
+ try {
+ mVrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not register VR State listener.", e);
+ }
+ }
+ }
+
+ private void startVirtualDisplay() {
+ if (DEBUG) {
+ Log.d(TAG, "Starting VD, DM:" + mDisplayManager);
+ }
+
+ if (mDisplayManager == null) {
+ Log.w(TAG, "Cannot create virtual display because mDisplayManager == null");
+ return;
+ }
+
+ synchronized (vdLock) {
+ if (mVirtualDisplay != null) {
+ Log.e(TAG, "Starting the virtual display when one already exists", new Exception());
+ return;
+ }
+
+ mVirtualDisplay = mDisplayManager.createVirtualDisplay("VR 2D Display", WIDTH, HEIGHT,
+ DPI,
+ null /* Surface */, 0 /* flags */);
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "VD created: " + mVirtualDisplay);
+ }
+ }
+
+ private void stopVirtualDisplay() {
+ if (DEBUG) {
+ Log.i(TAG, "Santos, stopping VD");
+ }
+
+ synchronized (vdLock) {
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ mVirtualDisplay = null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 84cf0c6..e8385fc 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -31,6 +31,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -51,6 +52,7 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+
import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
@@ -86,9 +88,9 @@
* {@link android.app.Activity#setVrModeEnabled)}. An application may also implement a service to
* be run while in VR mode by implementing {@link android.service.vr.VrListenerService}.
*
- * @see {@link android.service.vr.VrListenerService}
- * @see {@link com.android.server.vr.VrManagerInternal}
- * @see {@link com.android.server.vr.VrStateListener}
+ * @see android.service.vr.VrListenerService
+ * @see com.android.server.vr.VrManagerInternal
+ * @see com.android.server.vr.VrStateListener
*
* @hide
*/
@@ -136,6 +138,7 @@
private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager();
/** Tracks the state of the screen and keyguard UI.*/
private int mSystemSleepFlags = FLAG_NONE;
+ private CompatibilityDisplay mCompatibilityDisplay;
private static final int MSG_VR_STATE_CHANGE = 0;
private static final int MSG_PENDING_VR_STATE_CHANGE = 1;
@@ -493,6 +496,11 @@
mComponentObserver.rebuildAll();
}
+
+ DisplayManager dm =
+ (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
+ mCompatibilityDisplay = new CompatibilityDisplay(dm, mVrManager);
+ mCompatibilityDisplay.init();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mLock) {
mVrModeAllowed = true;
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 02b46ec..a0381f7 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);
@@ -287,6 +294,11 @@
WebViewZygote.setMultiprocessEnabled(enableMultiProcess);
}
+ @Override
+ public boolean isMultiProcessDefaultEnabled() {
+ return true;
+ }
+
// flags declaring we want extra info from the package manager for webview providers
private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
| PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index fd137eb..d57edcd 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,8 +52,17 @@
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);
public void notifyZygote(boolean enableMultiProcess);
+ public boolean isMultiProcessDefaultEnabled();
}
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..fedd55a9 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);
@@ -242,12 +240,31 @@
}
boolean isMultiProcessEnabled() {
- return mSystemInterface.getMultiProcessSetting(mContext) != 0;
+ PackageInfo current = getCurrentWebViewPackage();
+ if (current == null) return false;
+ int currentVersion = current.versionCode;
+ int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
+ if (mSystemInterface.isMultiProcessDefaultEnabled()) {
+ // Multiprocess should be enabled unless the user has turned it off manually for this
+ // version or newer, as we want to re-enable it when it's updated to get fresh
+ // bug reports.
+ return settingValue > -currentVersion;
+ } else {
+ // Multiprocess should not be enabled, unless the user has turned it on manually for
+ // any version.
+ return settingValue > 0;
+ }
}
void enableMultiProcess(boolean enable) {
+ // The value we store for the setting is the version code of the current package, if it's
+ // enabled, or the negation of the version code of the current package, if it's disabled.
+ // Users who have a setting from before this scheme was implemented will have it set to 0 or
+ // 1 instead.
PackageInfo current = getCurrentWebViewPackage();
- mSystemInterface.setMultiProcessSetting(mContext, enable ? 1 : 0);
+ int currentVersion = current != null ? current.versionCode : 1;
+ mSystemInterface.setMultiProcessSetting(mContext,
+ enable ? currentVersion : -currentVersion);
mSystemInterface.notifyZygote(enable);
if (current != null) {
mSystemInterface.killPackageDependents(current.packageName);
@@ -376,38 +393,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 +424,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 +444,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 +472,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 +697,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 +767,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 34f6752..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());
@@ -492,7 +493,7 @@
private boolean createSnapshot() {
final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
- mContainer.mTask);
+ mContainer.mTask.mTaskId, mContainer.mTask.mUserId, false /* restoreFromDisk */);
if (snapshot == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 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/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index b99dda1..015c084 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -77,6 +77,8 @@
boolean dimFullscreen();
/** Returns the display info. of the dim layer user. */
DisplayInfo getDisplayInfo();
+ /** Returns true if the dim layer user is currently attached to a display */
+ boolean isAttachedToDisplay();
/** Gets the bounds of the dim layer user. */
void getDimBounds(Rect outBounds);
String toShortString();
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
index 04ae72f..3a6e328 100644
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -191,8 +191,21 @@
boolean result = false;
for (int i = mState.size() - 1; i >= 0; i--) {
- DimLayer.DimLayerUser user = mState.keyAt(i);
- DimLayerState state = mState.valueAt(i);
+ final DimLayer.DimLayerUser user = mState.keyAt(i);
+ final DimLayerState state = mState.valueAt(i);
+
+ if (!user.isAttachedToDisplay()) {
+ // Leaked dim user that is no longer attached to the display. Go ahead and clean it
+ // clean-up and log what happened.
+ // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up
+ // it self when it was detached from the display. Need to investigate how the dim
+ // user is leaking...
+ Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString()
+ + " state=" + state);
+ removeDimLayerUser(user);
+ continue;
+ }
+
// We have to check that we are actually the shared fullscreen layer
// for this path. If we began as non fullscreen and became fullscreen
// (e.g. Docked stack closing), then we may not be the shared layer
@@ -309,6 +322,9 @@
}
mState.remove(dimLayerUser);
}
+ if (mState.isEmpty()) {
+ mSharedFullScreenDimLayer = null;
+ }
}
@VisibleForTesting
@@ -316,6 +332,11 @@
return mState.containsKey(dimLayerUser);
}
+ @VisibleForTesting
+ boolean hasSharedFullScreenDimLayer() {
+ return mSharedFullScreenDimLayer != null;
+ }
+
void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
applyDim(dimLayerUser, animator, false /* aboveApp */);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 592eaec..679f178 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;
@@ -625,6 +627,8 @@
};
/**
+ * Create new {@link DisplayContent} instance, add itself to the root window container and
+ * initialize direct children.
* @param display May not be null.
* @param service You know.
* @param layersController window layer controller used to assign layer to the windows on this
@@ -634,6 +638,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;
@@ -652,6 +663,9 @@
super.addChild(mTaskStackContainers, null);
super.addChild(mAboveAppWindowsContainers, null);
super.addChild(mImeWindowsContainers, null);
+
+ // Add itself as a child to the root container.
+ mService.mRoot.addChild(this, null);
}
int getDisplayId() {
@@ -957,75 +971,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 +1154,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 +1232,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 +1429,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 +1438,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 */
@@ -1488,6 +1470,13 @@
boolean canAddToastWindowForUid(int uid) {
// We allow one toast window per UID being shown at a time.
+ // Also if the app is focused adding more than one toast at
+ // a time for better backwards compatibility.
+ final WindowState focusedWindowForUid = getWindow(w ->
+ w.mOwnerUid == uid && w.isFocused());
+ if (focusedWindowForUid != null) {
+ return true;
+ }
final WindowState win = getWindow(w ->
w.mAttrs.type == TYPE_TOAST && w.mOwnerUid == uid && !w.mPermanentlyHidden
&& !w.mWindowRemovalAllowed);
@@ -1525,6 +1514,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();
@@ -2252,10 +2244,11 @@
}
// Don't include wallpaper in bounds calculation
- if (includeDecor && !stackBounds.isEmpty()) {
- frame.set(stackBounds);
- } else if (includeDecor) {
- mutableIncludeFullDisplay.value = true;
+ if (!mutableIncludeFullDisplay.value && includeDecor) {
+ final TaskStack stack = w.getStack();
+ if (stack != null) {
+ stack.getBounds(frame);
+ }
} else if (!mutableIncludeFullDisplay.value && !w.mIsWallpaper) {
final Rect wf = w.mFrame;
final Rect cr = w.mContentInsets;
@@ -2274,7 +2267,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;
}
@@ -2433,6 +2426,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;
@@ -2642,10 +2656,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 e6bc7f4..5a2ee9a 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -102,12 +102,14 @@
private int mDividerWindowWidth;
private int mDividerWindowWidthInactive;
private int mDividerInsets;
+ private int mTaskHeightInMinimizedMode;
private boolean mResizing;
private WindowState mWindow;
private final Rect mTmpRect = new Rect();
private final Rect mTmpRect2 = new Rect();
private final Rect mTmpRect3 = new Rect();
private final Rect mLastRect = new Rect();
+ private final Rect mMiddlePositionDockedStackRect = new Rect();
private boolean mLastVisibility = false;
private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
= new RemoteCallbackList<>();
@@ -189,6 +191,40 @@
return (int) (minWidth / mDisplayContent.getDisplayMetrics().density);
}
+ /**
+ * The middlePositionDockedStackRect is half the screen area that sits at the top (portrait) or
+ * left (landscape).
+ *
+ * @return fixed rect for temp stack
+ */
+ Rect getMiddlePositionDockedStackRect() {
+ return mMinimizedDock && isHomeStackResizable() ? mMiddlePositionDockedStackRect : null;
+ }
+
+ void getHomeStackBoundsInDockedMode(Rect outBounds) {
+ final DisplayInfo di = mDisplayContent.getDisplayInfo();
+ mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+ mTmpRect);
+ int dividerSize = mDividerWindowWidth - 2 * mDividerInsets;
+ Configuration configuration = mDisplayContent.getConfiguration();
+ if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
+ outBounds.set(0, mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top,
+ di.logicalWidth, di.logicalHeight);
+ } else {
+ outBounds.set(mTaskHeightInMinimizedMode + dividerSize + mTmpRect.left, 0,
+ di.logicalWidth, di.logicalHeight);
+ }
+ }
+
+ boolean isHomeStackResizable() {
+ final TaskStack homeStack = mDisplayContent.getHomeStack();
+ if (homeStack == null) {
+ return false;
+ }
+ final Task homeTask = homeStack.findHomeTask();
+ return homeTask != null && homeTask.isResizeable();
+ }
+
private void initSnapAlgorithmForRotations() {
final Configuration baseConfig = mDisplayContent.getConfiguration();
@@ -228,11 +264,34 @@
com.android.internal.R.dimen.docked_stack_divider_insets);
mDividerWindowWidthInactive = WindowManagerService.dipToPixel(
DIVIDER_WIDTH_INACTIVE_DP, mDisplayContent.getDisplayMetrics());
+ mTaskHeightInMinimizedMode = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.task_height_of_minimized_mode);
initSnapAlgorithmForRotations();
}
+ /**
+ * Calculates the constant rects {@link mMiddlePositionDockedStackRect} based on orientation,
+ * stable insets and display size.
+ */
+ private void updateConstantRects() {
+ final DisplayInfo di = mDisplayContent.getDisplayInfo();
+ mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+ mTmpRect);
+ int dividerSize = mDividerWindowWidth - 2 * mDividerInsets;
+ Configuration configuration = mDisplayContent.getConfiguration();
+ boolean isHorizontal = configuration.orientation == Configuration.ORIENTATION_PORTRAIT;
+ int middlePosition = DockedDividerUtils.calculateMiddlePosition(isHorizontal, mTmpRect,
+ di.logicalWidth, di.logicalHeight, dividerSize);
+ if (isHorizontal) {
+ mMiddlePositionDockedStackRect.set(0, 0, di.logicalWidth, middlePosition);
+ } else {
+ mMiddlePositionDockedStackRect.set(0, 0, middlePosition, di.logicalHeight);
+ }
+ }
+
void onConfigurationChanged() {
loadDimens();
+ updateConstantRects();
}
boolean isResizing() {
@@ -412,7 +471,19 @@
return mImeHideRequested;
}
- private void notifyDockedStackMinimizedChanged(boolean minimizedDock, long animDuration) {
+ private void notifyDockedStackMinimizedChanged(boolean minimizedDock, boolean animate,
+ boolean isHomeStackResizable) {
+ long animDuration = 0;
+ if (animate) {
+ final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+ final long transitionDuration = isAnimationMaximizing()
+ ? mService.mAppTransition.getLastClipRevealTransitionDuration()
+ : DEFAULT_APP_TRANSITION_DURATION;
+ mAnimationDuration = (long)
+ (transitionDuration * mService.getTransitionAnimationScaleLocked());
+ mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
+ animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction);
+ }
mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
minimizedDock ? 1 : 0, 0).sendToTarget();
@@ -420,7 +491,8 @@
for (int i = 0; i < size; ++i) {
final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
try {
- listener.onDockedStackMinimizedChanged(minimizedDock, animDuration);
+ listener.onDockedStackMinimizedChanged(minimizedDock, animDuration,
+ isHomeStackResizable);
} catch (RemoteException e) {
Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e);
}
@@ -457,16 +529,16 @@
void registerDockedStackListener(IDockedStackListener listener) {
mDockedStackListeners.register(listener);
notifyDockedDividerVisibilityChanged(wasVisible());
- notifyDockedStackExistsChanged(
- mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID) != null);
- notifyDockedStackMinimizedChanged(mMinimizedDock, 0 /* animDuration */);
+ notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
+ notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
+ isHomeStackResizable());
notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
}
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 +616,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());
@@ -578,17 +650,23 @@
final boolean imeChanged = clearImeAdjustAnimation();
boolean minimizedChange = false;
- if (minimizedDock) {
- if (animate) {
- startAdjustAnimation(0f, 1f);
- } else {
- minimizedChange |= setMinimizedDockedStack(true);
- }
+ if (isHomeStackResizable()) {
+ notifyDockedStackMinimizedChanged(minimizedDock, true /* animate */,
+ true /* isHomeStackResizable */);
+ minimizedChange = true;
} else {
- if (animate) {
- startAdjustAnimation(1f, 0f);
+ if (minimizedDock) {
+ if (animate) {
+ startAdjustAnimation(0f, 1f);
+ } else {
+ minimizedChange |= setMinimizedDockedStack(true);
+ }
} else {
- minimizedChange |= setMinimizedDockedStack(false);
+ if (animate) {
+ startAdjustAnimation(1f, 0f);
+ } else {
+ minimizedChange |= setMinimizedDockedStack(false);
+ }
}
}
if (imeChanged || minimizedChange) {
@@ -689,7 +767,7 @@
private boolean setMinimizedDockedStack(boolean minimized) {
final TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
- notifyDockedStackMinimizedChanged(minimized, 0);
+ notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
}
@@ -739,18 +817,12 @@
}
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;
- final long transitionDuration = isAnimationMaximizing()
- ? mService.mAppTransition.getLastClipRevealTransitionDuration()
- : DEFAULT_APP_TRANSITION_DURATION;
- mAnimationDuration = (long)
- (transitionDuration * mService.getTransitionAnimationScaleLocked());
- mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
- notifyDockedStackMinimizedChanged(mMinimizedDock,
- (long) (mAnimationDuration * mMaximizeMeetFraction));
+ notifyDockedStackMinimizedChanged(mMinimizedDock, true /* animate */,
+ isHomeStackResizable() /* isHomeStackResizable */);
}
float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator)
@@ -835,6 +907,11 @@
}
@Override
+ public boolean isAttachedToDisplay() {
+ return mDisplayContent != null;
+ }
+
+ @Override
public void getDimBounds(Rect outBounds) {
// This dim layer user doesn't need this.
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 36520a9..1ae987f 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -148,7 +148,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
display.getDisplayId());
mDragWindowHandle.name = "drag";
mDragWindowHandle.inputChannel = mServerChannel;
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 24783bc..b92bfb9 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -48,7 +48,8 @@
mApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mWindowHandle = new InputWindowHandle(mApplicationHandle, null, Display.DEFAULT_DISPLAY);
+ mWindowHandle = new InputWindowHandle(mApplicationHandle, null, null,
+ Display.DEFAULT_DISPLAY);
mWindowHandle.name = name;
mWindowHandle.inputChannel = mServerChannel;
mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f754775..5f53d84 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -77,6 +77,8 @@
// Array of window handles to provide to the input dispatcher.
private InputWindowHandle[] mInputWindowHandles;
private int mInputWindowHandleCount;
+ private InputWindowHandle mFocusedInputWindowHandle;
+
private boolean mAddInputConsumerHandle;
private boolean mAddPipInputConsumerHandle;
private boolean mAddWallpaperInputConsumerHandle;
@@ -327,12 +329,16 @@
+ child + ", " + inputWindowHandle);
}
addInputWindowHandle(inputWindowHandle);
+ if (hasFocus) {
+ mFocusedInputWindowHandle = inputWindowHandle;
+ }
}
private void clearInputWindowHandlesLw() {
while (mInputWindowHandleCount != 0) {
mInputWindowHandles[--mInputWindowHandleCount] = null;
}
+ mFocusedInputWindowHandle = null;
}
void setUpdateInputWindowsNeededLw() {
@@ -609,7 +615,7 @@
}
// Send windows to native code.
- mService.mInputManager.setInputWindows(mInputWindowHandles);
+ mService.mInputManager.setInputWindows(mInputWindowHandles, mFocusedInputWindowHandle);
clearInputWindowHandlesLw();
}
@@ -619,7 +625,7 @@
final InputChannel inputChannel = w.mInputChannel;
final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
if (inputChannel == null || inputWindowHandle == null || w.mRemoved
- || w.isAdjustedForMinimizedDock()) {
+ || w.canReceiveTouchInput()) {
// Skip this window because it cannot possibly receive input.
return;
}
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 34633c2..596c3d8 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -82,6 +82,7 @@
// Used to calculate stack bounds across rotations
private final DisplayInfo mDisplayInfo = new DisplayInfo();
+ private final Rect mStableInsets = new Rect();
// The size and position information that describes where the pinned stack will go by default.
private int mDefaultStackGravity;
@@ -113,6 +114,7 @@
public void setIsMinimized(final boolean isMinimized) {
mHandler.post(() -> {
mIsMinimized = isMinimized;
+ mSnapAlgorithm.setMinimized(isMinimized);
});
}
@@ -250,10 +252,12 @@
}
/**
- * @return the repositioned PIP bounds given it's pre-change bounds, and the new display info.
+ * @return the repositioned PIP bounds given it's pre-change bounds, and the new display
+ * content.
*/
- Rect onDisplayChanged(Rect preChangeStackBounds, DisplayInfo displayInfo) {
+ Rect onDisplayChanged(Rect preChangeStackBounds, DisplayContent displayContent) {
final Rect postChangeStackBounds = new Rect(preChangeStackBounds);
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (!mDisplayInfo.equals(displayInfo)) {
// Calculate the snap fraction of the current stack along the old movement bounds, and
// then update the stack bounds to the same fraction along the rotated movement bounds.
@@ -269,8 +273,9 @@
if (mIsMinimized) {
final Point displaySize = new Point(mDisplayInfo.logicalWidth,
mDisplayInfo.logicalHeight);
+ mService.getStableInsetsLocked(displayContent.getDisplayId(), mStableInsets);
mSnapAlgorithm.applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds,
- displaySize);
+ displaySize, mStableInsets);
}
}
return postChangeStackBounds;
@@ -329,7 +334,9 @@
*/
void setActions(List<RemoteAction> actions) {
mActions.clear();
- mActions.addAll(actions);
+ if (actions != null) {
+ mActions.addAll(actions);
+ }
notifyActionsChanged(mActions);
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0cc6c70..80e6655 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -213,7 +213,6 @@
final int displayId = display.getDisplayId();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
- addChild(dc, null);
final DisplayInfo displayInfo = dc.getDisplayInfo();
final Rect rect = new Rect();
@@ -382,6 +381,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 +545,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 +680,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/SplashScreenStartingData.java b/services/core/java/com/android/server/wm/SplashScreenStartingData.java
index ee4209f..4b14f86 100644
--- a/services/core/java/com/android/server/wm/SplashScreenStartingData.java
+++ b/services/core/java/com/android/server/wm/SplashScreenStartingData.java
@@ -54,6 +54,6 @@
StartingSurface createStartingSurface(AppWindowToken atoken) {
return mService.mPolicy.addSplashScreen(atoken.token, mPkg, mTheme, mCompatInfo,
mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
- mMergedOverrideConfiguration);
+ mMergedOverrideConfiguration, atoken.getDisplayContent().getDisplayId());
}
}
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..e2ea2c5
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -0,0 +1,317 @@
+/*
+ * 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, Rect outTempBounds,
+ Rect outTempInsetBounds, boolean ignoreVisibility) {
+ synchronized (mWindowMap) {
+ if (mContainer != null) {
+ mContainer.getStackDockedModeBoundsLocked(outBounds, outTempBounds,
+ outTempInsetBounds, ignoreVisibility);
+ return;
+ }
+ outBounds.setEmpty();
+ outTempBounds.setEmpty();
+ outTempInsetBounds.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 void getBoundsForNewConfiguration(Rect outBounds, Rect outTempBounds) {
+ synchronized(mWindowMap) {
+ mContainer.getBoundsForNewConfiguration(outBounds, outTempBounds);
+ }
+ }
+
+ 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 824d460..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);
+ }
}
}
@@ -628,7 +637,12 @@
@Override
public DisplayInfo getDisplayInfo() {
- return mStack.getDisplayContent().getDisplayInfo();
+ return getDisplayContent().getDisplayInfo();
+ }
+
+ @Override
+ public boolean isAttachedToDisplay() {
+ return getDisplayContent() != null;
}
void forceWindowsScaleable(boolean force) {
@@ -642,6 +656,14 @@
}
}
+ void setTaskDescription(TaskDescription taskDescription) {
+ mTaskDescription = taskDescription;
+ }
+
+ TaskDescription getTaskDescription() {
+ return mTaskDescription;
+ }
+
@Override
boolean fillsParent() {
return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId);
@@ -683,6 +705,5 @@
pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
wtoken.dump(pw, triplePrefix);
}
-
}
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 267566b..90106a9 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -269,7 +269,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
mDisplay.getDisplayId());
mDragWindowHandle.name = TAG;
mDragWindowHandle.inputChannel = mServerChannel;
@@ -693,6 +693,11 @@
}
@Override
+ public boolean isAttachedToDisplay() {
+ return mTask != null && mTask.getDisplayContent() != null;
+ }
+
+ @Override
public void getDimBounds(Rect out) {
// This dim layer user doesn't need this.
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index c86229b..601bf28 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -19,8 +19,11 @@
import android.annotation.Nullable;
import android.app.ActivityManager.TaskSnapshot;
import android.util.ArrayMap;
+import android.util.LruCache;
import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Map.Entry;
/**
* Caches snapshots. See {@link TaskSnapshotController}.
@@ -29,51 +32,125 @@
*/
class TaskSnapshotCache {
- private final ArrayMap<AppWindowToken, Task> mAppTaskMap = new ArrayMap<>();
- private final ArrayMap<Task, CacheEntry> mCache = new ArrayMap<>();
+ // TODO: Make this more dynamic to accomodate for different clients.
+ private static final int RETRIEVAL_CACHE_SIZE = 4;
+
+ private final WindowManagerService mService;
+ private final TaskSnapshotLoader mLoader;
+ private final ArrayMap<AppWindowToken, Integer> mAppTaskMap = new ArrayMap<>();
+ private final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>();
+ private final LruCache<Integer, TaskSnapshot> mRetrievalCache =
+ new LruCache<>(RETRIEVAL_CACHE_SIZE);
+
+ TaskSnapshotCache(WindowManagerService service, TaskSnapshotLoader loader) {
+ mService = service;
+ mLoader = loader;
+ }
void putSnapshot(Task task, TaskSnapshot snapshot) {
- final CacheEntry entry = mCache.get(task);
+ final CacheEntry entry = mRunningCache.get(task.mTaskId);
if (entry != null) {
mAppTaskMap.remove(entry.topApp);
}
final AppWindowToken top = task.getTopChild();
- mAppTaskMap.put(top, task);
- mCache.put(task, new CacheEntry(snapshot, task.getTopChild()));
- }
-
- @Nullable TaskSnapshot getSnapshot(Task task) {
- final CacheEntry entry = mCache.get(task);
- return entry != null ? entry.snapshot : null;
+ mAppTaskMap.put(top, task.mTaskId);
+ mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, task.getTopChild()));
+ mRetrievalCache.put(task.mTaskId, snapshot);
}
/**
- * Cleans the cache after an app window token's process died.
+ * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
*/
- void cleanCache(AppWindowToken wtoken) {
- final Task task = mAppTaskMap.get(wtoken);
- if (task != null) {
- removeEntry(task);
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+
+ synchronized (mService.mWindowMap) {
+ // Try the running cache.
+ final CacheEntry entry = mRunningCache.get(taskId);
+ if (entry != null) {
+ return entry.snapshot;
+ }
+
+ // Try the retrieval cache.
+ final TaskSnapshot snapshot = mRetrievalCache.get(taskId);
+ if (snapshot != null) {
+ return snapshot;
+ }
+ }
+
+ // Try to restore from disk if asked.
+ if (!restoreFromDisk) {
+ return null;
+ }
+ return tryRestoreFromDisk(taskId, userId);
+ }
+
+ /**
+ * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
+ */
+ private TaskSnapshot tryRestoreFromDisk(int taskId, int userId) {
+ final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId);
+ if (snapshot == null) {
+ return null;
+ }
+ synchronized (mService.mWindowMap) {
+ mRetrievalCache.put(taskId, snapshot);
+ }
+ return snapshot;
+ }
+
+ /**
+ * Called when an app token has been removed
+ */
+ void onAppRemoved(AppWindowToken wtoken) {
+ final Integer taskId = mAppTaskMap.get(wtoken);
+ if (taskId != null) {
+ removeRunningEntry(taskId);
+ }
+ if (wtoken.mTask != null) {
+ mRetrievalCache.remove(wtoken.mTask.mTaskId);
}
}
- private void removeEntry(Task task) {
- final CacheEntry entry = mCache.get(task);
+ /**
+ * Callend when an app window token's process died.
+ */
+ void onAppDied(AppWindowToken wtoken) {
+ final Integer taskId = mAppTaskMap.get(wtoken);
+ if (taskId != null) {
+ removeRunningEntry(taskId);
+ }
+ }
+
+ void onTaskRemoved(int taskId) {
+ removeRunningEntry(taskId);
+ mRetrievalCache.remove(taskId);
+ }
+
+ private void removeRunningEntry(int taskId) {
+ final CacheEntry entry = mRunningCache.get(taskId);
if (entry != null) {
mAppTaskMap.remove(entry.topApp);
- mCache.remove(task);
+ mRunningCache.remove(taskId);
}
}
void dump(PrintWriter pw, String prefix) {
final String doublePrefix = prefix + " ";
final String triplePrefix = doublePrefix + " ";
+ final String quadruplePrefix = triplePrefix + " ";
pw.println(prefix + "SnapshotCache");
- for (int i = mCache.size() - 1; i >= 0; i--) {
- final CacheEntry entry = mCache.valueAt(i);
- pw.println(doublePrefix + "Entry taskId=" + mCache.keyAt(i).mTaskId);
- pw.println(triplePrefix + "topApp=" + entry.topApp);
- pw.println(triplePrefix + "snapshot=" + entry.snapshot);
+ pw.println(doublePrefix + "RunningCache");
+ for (int i = mRunningCache.size() - 1; i >= 0; i--) {
+ final CacheEntry entry = mRunningCache.valueAt(i);
+ pw.println(triplePrefix + "Entry taskId=" + mRunningCache.keyAt(i));
+ pw.println(quadruplePrefix + "topApp=" + entry.topApp);
+ pw.println(quadruplePrefix + "snapshot=" + entry.snapshot);
+ }
+ pw.println(doublePrefix + "RetrievalCache");
+ final Map<Integer, TaskSnapshot> retrievalSnapshot = mRetrievalCache.snapshot();
+ for (Entry<Integer, TaskSnapshot> entry : retrievalSnapshot.entrySet()) {
+ pw.println(triplePrefix + "Entry taskId=" + entry.getKey());
+ pw.println(quadruplePrefix + "snapshot=" + entry.getValue());
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index df8679d..2b74f84 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -22,9 +22,12 @@
import android.app.ActivityManager.StackId;
import android.app.ActivityManager.TaskSnapshot;
import android.graphics.GraphicBuffer;
+import android.os.Environment;
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;
@@ -45,22 +48,47 @@
class TaskSnapshotController {
private final WindowManagerService mService;
- private final TaskSnapshotCache mCache = new TaskSnapshotCache();
+ private final TaskSnapshotCache mCache;
+ private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
+ Environment::getDataSystemCeDirectory);
+ private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
private final ArraySet<Task> mTmpTasks = new ArraySet<>();
TaskSnapshotController(WindowManagerService service) {
mService = service;
+ mCache = new TaskSnapshotCache(mService, mLoader);
+ }
+
+ void systemReady() {
+ mPersister.start();
}
void onTransitionStarting() {
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)) {
@@ -69,6 +97,7 @@
final TaskSnapshot snapshot = snapshotTask(task);
if (snapshot != null) {
mCache.putSnapshot(task, snapshot);
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
if (task.getController() != null) {
task.getController().reportSnapshotChanged(snapshot);
}
@@ -76,8 +105,12 @@
}
}
- @Nullable TaskSnapshot getSnapshot(Task task) {
- return mCache.getSnapshot(task);
+ /**
+ * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
+ * MANAGER LOCK WHEN CALLING THIS METHOD!
+ */
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+ return mCache.getSnapshot(taskId, userId, restoreFromDisk);
}
/**
@@ -128,17 +161,26 @@
* Called when an {@link AppWindowToken} has been removed.
*/
void onAppRemoved(AppWindowToken wtoken) {
- // TODO: Clean from both recents and running cache.
- mCache.cleanCache(wtoken);
+ mCache.onAppRemoved(wtoken);
}
/**
* Called when the process of an {@link AppWindowToken} has died.
*/
void onAppDied(AppWindowToken wtoken) {
+ mCache.onAppDied(wtoken);
+ }
- // TODO: Only clean from running cache.
- mCache.cleanCache(wtoken);
+ void notifyTaskRemovedFromRecents(int taskId, int userId) {
+ mCache.onTaskRemoved(taskId);
+ mPersister.onTaskRemovedFromRecents(taskId, userId);
+ }
+
+ /**
+ * See {@link TaskSnapshotPersister#removeObsoleteFiles}
+ */
+ void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
+ mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
new file mode 100644
index 0000000..4340822
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.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 com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.util.Slog;
+
+import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/**
+ * Loads a persisted {@link TaskSnapshot} from disk.
+ * <p>
+ * Do not hold the window manager lock when accessing this class.
+ * <p>
+ * Test class: {@link TaskSnapshotPersisterLoaderTest}
+ */
+class TaskSnapshotLoader {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotLoader" : TAG_WM;
+
+ private final TaskSnapshotPersister mPersister;
+
+ TaskSnapshotLoader(TaskSnapshotPersister persister) {
+ mPersister = persister;
+ }
+
+ /**
+ * Loads a task from the disk.
+ * <p>
+ * Do not hold the window manager lock when calling this method, as we directly read data from
+ * disk here, which might be slow.
+ *
+ * @param taskId The id of the task to load.
+ * @param userId The id of the user the task belonged to.
+ * @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
+ */
+ TaskSnapshot loadTask(int taskId, int userId) {
+ final File protoFile = mPersister.getProtoFile(taskId, userId);
+ final File bitmapFile = mPersister.getBitmapFile(taskId, userId);
+ if (!protoFile.exists() || !bitmapFile.exists()) {
+ return null;
+ }
+ try {
+ final byte[] bytes = Files.readAllBytes(protoFile.toPath());
+ final TaskSnapshotProto proto = TaskSnapshotProto.parseFrom(bytes);
+ final Options options = new Options();
+ options.inPreferredConfig = Config.HARDWARE;
+ final Bitmap bitmap = BitmapFactory.decodeFile(bitmapFile.getPath(), options);
+ if (bitmap == null) {
+ Slog.w(TAG, "Failed to load bitmap: " + bitmapFile.getPath());
+ return null;
+ }
+ final GraphicBuffer buffer = bitmap.createGraphicBufferHandle();
+ if (buffer == null) {
+ Slog.w(TAG, "Failed to retrieve gralloc buffer for bitmap: "
+ + bitmapFile.getPath());
+ return null;
+ }
+ return new TaskSnapshot(buffer, proto.orientation,
+ new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom));
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
new file mode 100644
index 0000000..3a06c38
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -0,0 +1,335 @@
+/*
+ * 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 com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.TestApi;
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AtomicFile;
+import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayDeque;
+
+/**
+ * Persists {@link TaskSnapshot}s to disk.
+ * <p>
+ * Test class: {@link TaskSnapshotPersisterLoaderTest}
+ */
+class TaskSnapshotPersister {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
+ private static final String SNAPSHOTS_DIRNAME = "snapshots";
+ private static final long DELAY_MS = 100;
+ private static final String PROTO_EXTENSION = ".proto";
+ private static final String BITMAP_EXTENSION = ".png";
+
+ @GuardedBy("mLock")
+ private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
+ @GuardedBy("mLock")
+ private boolean mQueueIdling;
+ private boolean mStarted;
+ private final Object mLock = new Object();
+ private final DirectoryResolver mDirectoryResolver;
+
+ /**
+ * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
+ * called.
+ */
+ @GuardedBy("mLock")
+ private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
+
+ TaskSnapshotPersister(DirectoryResolver resolver) {
+ mDirectoryResolver = resolver;
+ }
+
+ /**
+ * Starts persisting.
+ */
+ void start() {
+ if (!mStarted) {
+ mStarted = true;
+ mPersister.start();
+ }
+ }
+
+ /**
+ * Persists a snapshot of a task to disk.
+ *
+ * @param taskId The id of the task that needs to be persisted.
+ * @param userId The id of the user this tasks belongs to.
+ * @param snapshot The snapshot to persist.
+ */
+ void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
+ synchronized (mLock) {
+ mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
+ sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
+ }
+ }
+
+ /**
+ * Callend when a task has been removed.
+ *
+ * @param taskId The id of task that has been removed.
+ * @param userId The id of the user the task belonged to.
+ */
+ void onTaskRemovedFromRecents(int taskId, int userId) {
+ synchronized (mLock) {
+ mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
+ sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId));
+ }
+ }
+
+ /**
+ * In case a write/delete operation was lost because the system crashed, this makes sure to
+ * clean up the directory to remove obsolete files.
+ *
+ * @param persistentTaskIds A set of task ids that exist in our in-memory model.
+ * @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
+ * model.
+ */
+ void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
+ synchronized (mLock) {
+ mPersistedTaskIdsSinceLastRemoveObsolete.clear();
+ sendToQueueLocked(new RemoveObsoleteFilesQueueItem(persistentTaskIds, runningUserIds));
+ }
+ }
+
+ @TestApi
+ void waitForQueueEmpty() {
+ while (true) {
+ synchronized (mLock) {
+ if (mWriteQueue.isEmpty() && mQueueIdling) {
+ return;
+ }
+ }
+ SystemClock.sleep(100);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void sendToQueueLocked(WriteQueueItem item) {
+ mWriteQueue.offer(item);
+ mLock.notifyAll();
+ }
+
+ private File getDirectory(int userId) {
+ return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), SNAPSHOTS_DIRNAME);
+ }
+
+ File getProtoFile(int taskId, int userId) {
+ return new File(getDirectory(userId), taskId + PROTO_EXTENSION);
+ }
+
+ File getBitmapFile(int taskId, int userId) {
+ return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
+ }
+
+ private boolean createDirectory(int userId) {
+ final File dir = getDirectory(userId);
+ return dir.exists() || dir.mkdirs();
+ }
+
+ private void deleteSnapshot(int taskId, int userId) {
+ final File protoFile = getProtoFile(taskId, userId);
+ final File bitmapFile = getBitmapFile(taskId, userId);
+ protoFile.delete();
+ bitmapFile.delete();
+ }
+
+ interface DirectoryResolver {
+ File getSystemDirectoryForUser(int userId);
+ }
+
+ private Thread mPersister = new Thread("TaskSnapshotPersister") {
+ public void run() {
+ android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ while (true) {
+ WriteQueueItem next;
+ synchronized (mLock) {
+ next = mWriteQueue.poll();
+ }
+ if (next != null) {
+ next.write();
+ SystemClock.sleep(DELAY_MS);
+ }
+ synchronized (mLock) {
+ if (!mWriteQueue.isEmpty()) {
+ continue;
+ }
+ try {
+ mQueueIdling = true;
+ mLock.wait();
+ mQueueIdling = false;
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ };
+
+ private abstract class WriteQueueItem {
+ abstract void write();
+ }
+
+ private class StoreWriteQueueItem extends WriteQueueItem {
+ private final int mTaskId;
+ private final int mUserId;
+ private final TaskSnapshot mSnapshot;
+
+ StoreWriteQueueItem(int taskId, int userId, TaskSnapshot snapshot) {
+ mTaskId = taskId;
+ mUserId = userId;
+ mSnapshot = snapshot;
+ }
+
+ @Override
+ void write() {
+ if (!createDirectory(mUserId)) {
+ Slog.e(TAG, "Unable to create snapshot directory for user dir="
+ + getDirectory(mUserId));
+ }
+ boolean failed = false;
+ if (!writeProto()) {
+ failed = true;
+ }
+ if (!writeBuffer()) {
+ writeBuffer();
+ failed = true;
+ }
+ if (failed) {
+ deleteSnapshot(mTaskId, mUserId);
+ }
+ }
+
+ boolean writeProto() {
+ final TaskSnapshotProto proto = new TaskSnapshotProto();
+ proto.orientation = mSnapshot.getOrientation();
+ proto.insetLeft = mSnapshot.getContentInsets().left;
+ proto.insetTop = mSnapshot.getContentInsets().top;
+ proto.insetRight = mSnapshot.getContentInsets().right;
+ proto.insetBottom = mSnapshot.getContentInsets().bottom;
+ final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
+ final File file = getProtoFile(mTaskId, mUserId);
+ final AtomicFile atomicFile = new AtomicFile(file);
+ FileOutputStream fos = null;
+ try {
+ fos = atomicFile.startWrite();
+ fos.write(bytes);
+ atomicFile.finishWrite(fos);
+ } catch (IOException e) {
+ atomicFile.failWrite(fos);
+ Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+ return false;
+ }
+ return true;
+ }
+
+ boolean writeBuffer() {
+ final File file = getBitmapFile(mTaskId, mUserId);
+ final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
+ try {
+ FileOutputStream fos = new FileOutputStream(file);
+ bitmap.compress(CompressFormat.PNG, 0 /* quality */, fos);
+ fos.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private class DeleteWriteQueueItem extends WriteQueueItem {
+ private final int mTaskId;
+ private final int mUserId;
+
+ DeleteWriteQueueItem(int taskId, int userId) {
+ mTaskId = taskId;
+ mUserId = userId;
+ }
+
+ @Override
+ void write() {
+ deleteSnapshot(mTaskId, mUserId);
+ }
+ }
+
+ @VisibleForTesting
+ class RemoveObsoleteFilesQueueItem extends WriteQueueItem {
+ private final ArraySet<Integer> mPersistentTaskIds;
+ private final int[] mRunningUserIds;
+
+ @VisibleForTesting
+ RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
+ int[] runningUserIds) {
+ mPersistentTaskIds = persistentTaskIds;
+ mRunningUserIds = runningUserIds;
+ }
+
+ @Override
+ void write() {
+ final ArraySet<Integer> newPersistedTaskIds;
+ synchronized (mLock) {
+ newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
+ }
+ for (int userId : mRunningUserIds) {
+ final File dir = getDirectory(userId);
+ final String[] files = dir.list();
+ if (files == null) {
+ continue;
+ }
+ for (String file : files) {
+ final int taskId = getTaskId(file);
+ if (!mPersistentTaskIds.contains(taskId)
+ && !newPersistedTaskIds.contains(taskId)) {
+ new File(dir, file).delete();
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ int getTaskId(String fileName) {
+ if (!fileName.endsWith(PROTO_EXTENSION) && !fileName.endsWith(BITMAP_EXTENSION)) {
+ return -1;
+ }
+ final int end = fileName.lastIndexOf('.');
+ if (end == -1) {
+ return -1;
+ }
+ try {
+ return Integer.parseInt(fileName.substring(0, end));
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+ }
+}
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 eeea532..0ff1f0c 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.
@@ -228,7 +213,7 @@
mAdjustedBounds.set(bounds);
final boolean adjusted = !mAdjustedBounds.isEmpty();
Rect insetBounds = null;
- if (adjusted && isAdjustedForMinimizedDock()) {
+ if (adjusted && isAdjustedForMinimizedDockedStack()) {
insetBounds = mBounds;
} else if (adjusted && mAdjustedForIme) {
if (mImeGoingAway) {
@@ -413,7 +398,7 @@
switch (mStackId) {
case PINNED_STACK_ID:
mTmpRect2 = mDisplayContent.getPinnedStackController().onDisplayChanged(mBounds,
- getDisplayInfo());
+ mDisplayContent);
break;
case DOCKED_STACK_ID:
repositionDockedStackAfterRotation(mTmpRect2);
@@ -435,9 +420,14 @@
return true;
}
- void getBoundsForNewConfiguration(Rect outBounds) {
+ void getBoundsForNewConfiguration(Rect outBounds, Rect outTempBounds) {
outBounds.set(mBoundsAfterRotation);
mBoundsAfterRotation.setEmpty();
+ final DockedStackDividerController controller = getDisplayContent()
+ .mDividerControllerLocked;
+ if (controller.isMinimizedDock() && mStackId == DOCKED_STACK_ID) {
+ outTempBounds.set(controller.getMiddlePositionDockedStackRect());
+ }
}
/**
@@ -497,7 +487,8 @@
mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
mService.mContext.getResources(), displayWidth, displayHeight,
- dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds);
+ dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds,
+ isMinimizedDockAndHomeStackResizable());
final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
// Recalculate the bounds based on the position of the target.
@@ -658,7 +649,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())) {
@@ -684,20 +675,31 @@
// Update the pinned stack controller after the display info is updated
if (mStackId == PINNED_STACK_ID) {
mDisplayContent.getPinnedStackController().onDisplayChanged(oldBounds,
- getDisplayInfo());
+ mDisplayContent);
}
super.onDisplayChanged(dc);
}
- void getStackDockedModeBoundsLocked(Rect outBounds, boolean ignoreVisibility) {
+ void getStackDockedModeBoundsLocked(Rect outBounds, Rect outTempBounds,
+ Rect outTempInsetBounds, boolean ignoreVisibility) {
+ if (mStackId == HOME_STACK_ID && findHomeTask().isResizeable()) {
+ // Calculate the home stack bounds when in docked mode
+ getDisplayContent().mDividerControllerLocked
+ .getHomeStackBoundsInDockedMode(outTempBounds);
+ outTempInsetBounds.set(outTempBounds);
+ } else {
+ outTempBounds.setEmpty();
+ outTempInsetBounds.setEmpty();
+ }
+
if ((mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId))
|| mDisplayContent == null) {
outBounds.set(mBounds);
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(
@@ -804,9 +806,15 @@
mService.mDockedStackCreateBounds = null;
final Rect bounds = new Rect();
- getStackDockedModeBoundsLocked(bounds, true /*ignoreVisibility*/);
- mService.mH.obtainMessage(RESIZE_STACK, DOCKED_STACK_ID,
- 1 /*allowResizeInDockedMode*/, bounds).sendToTarget();
+ final Rect tempBounds = new Rect();
+ final Rect tempInsetBounds = new Rect();
+ getStackDockedModeBoundsLocked(bounds, tempBounds, tempInsetBounds, true /*ignoreVisibility*/);
+ getController().requestResize(bounds);
+ }
+
+ @Override
+ StackWindowController getController() {
+ return (StackWindowController) super.getController();
}
@Override
@@ -957,8 +965,9 @@
}
}
- boolean isAdjustedForMinimizedDock() {
- return mMinimizeAmount != 0f;
+ boolean shouldIgnoreInput() {
+ return isAdjustedForMinimizedDockedStack() || mStackId == DOCKED_STACK_ID &&
+ isMinimizedDockAndHomeStackResizable();
}
/**
@@ -1086,6 +1095,11 @@
return true;
}
+ private boolean isMinimizedDockAndHomeStackResizable() {
+ return mDisplayContent.mDividerControllerLocked.isMinimizedDock()
+ && mDisplayContent.mDividerControllerLocked.isHomeStackResizable();
+ }
+
/**
* @return the distance in pixels how much the stack gets minimized from it's original size
*/
@@ -1209,6 +1223,11 @@
}
@Override
+ public boolean isAttachedToDisplay() {
+ return mDisplayContent != null;
+ }
+
+ @Override
public String toString() {
return "{stackId=" + mStackId + " tasks=" + mChildren + "}";
}
@@ -1350,9 +1369,17 @@
* tasks (including the focused).
*
* We save the focused task region once we find it, and add it back at the end.
+ *
+ * If the task is home stack and it is resizable in the minimized state, we want to
+ * exclude the docked stack from touch so we need the entire screen area and not just a
+ * small portion which the home stack currently is resized to.
*/
- task.getDimBounds(mTmpRect);
+ if (task.isHomeTask() && isMinimizedDockAndHomeStackResizable()) {
+ mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ } else {
+ task.getDimBounds(mTmpRect);
+ }
if (task == focusedTask) {
// Add the focused task rect back into the exclude region once we are done
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 96b79a6..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,25 +206,57 @@
}
}
- /**
- * @return a graphic buffer representing a screenshot of a task
- */
- public TaskSnapshot getSnapshot() {
+ public void setTaskDescription(TaskDescription taskDescription) {
synchronized (mWindowMap) {
if (mContainer == null) {
- Slog.w(TAG_WM, "getSnapshot: taskId " + mTaskId + " not found.");
- return null;
+ Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found.");
+ return;
}
- return mService.mTaskSnapshotController.getSnapshot(mContainer);
+ 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 dcc0c6f..d62c62e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -97,6 +97,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IActivityManager;
@@ -705,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;
@@ -1115,7 +1111,8 @@
+ displayId + ". Aborting.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
- if (!displayContent.hasAccess(session.mUid)) {
+ if (!displayContent.hasAccess(session.mUid)
+ && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
+ "does not have access: " + displayId + ". Aborting.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
@@ -2571,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) {
@@ -2825,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);
@@ -2878,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,
@@ -2896,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;
@@ -3033,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}
@@ -3503,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.
@@ -3517,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;
@@ -3901,6 +3731,24 @@
return true;
}
+ public TaskSnapshot getTaskSnapshot(int taskId, int userId) {
+ return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */);
+ }
+
+ /**
+ * In case a task write/delete operation was lost because the system crashed, this makes sure to
+ * clean up the directory to remove obsolete files.
+ *
+ * @param persistentTaskIds A set of task ids that exist in our in-memory model.
+ * @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
+ * model.
+ */
+ public void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
+ synchronized (mWindowMap) {
+ mTaskSnapshotController.removeObsoleteTaskFiles(persistentTaskIds, runningUserIds);
+ }
+ }
+
/**
* Takes a snapshot of the screen. In landscape mode this grabs the whole screen.
* In portrait mode, it grabs the full screenshot.
@@ -4978,6 +4826,14 @@
mDisplayMetrics, dw, dh, displayId);
config.densityDpi = displayInfo.logicalDensityDpi;
+ config.colorMode =
+ (displayInfo.isHdr()
+ ? Configuration.COLOR_MODE_HDR_YES
+ : Configuration.COLOR_MODE_HDR_NO)
+ | (displayInfo.isWideColorGamut()
+ ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES
+ : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO);
+
// Update the configuration based on available input devices, lid switch,
// and platform configuration.
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
@@ -5346,6 +5202,7 @@
public void systemReady() {
mPolicy.systemReady();
+ mTaskSnapshotController.systemReady();
}
// -------------------------------------------------------------
@@ -5396,8 +5253,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;
@@ -5823,23 +5678,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--) {
@@ -6985,6 +6823,18 @@
}
}
+ /**
+ * Called when a task has been removed from the recent tasks list.
+ * <p>
+ * Note: This doesn't go through {@link TaskWindowContainerController} yet as the window
+ * container may not exist when this happens.
+ */
+ public void notifyTaskRemovedFromRecents(int taskId, int userId) {
+ synchronized (mWindowMap) {
+ mTaskSnapshotController.notifyTaskRemovedFromRecents(taskId, userId);
+ }
+ }
+
@Override
public int getDockedDividerInsetsLw() {
return getDefaultDisplayContentLocked().getDockedDividerController().getContentInsets();
@@ -7613,26 +7463,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;
@@ -8090,7 +7920,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..050adfe 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -657,7 +657,8 @@
mYOffset = 0;
mLayer = 0;
mInputWindowHandle = new InputWindowHandle(
- mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, getDisplayId());
+ mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, c,
+ getDisplayId());
}
void attach() {
@@ -1671,11 +1672,6 @@
return !mLastReportedConfiguration.equals(getConfiguration());
}
- boolean isAdjustedForMinimizedDock() {
- return mAppToken != null && mAppToken.mTask != null
- && mAppToken.mTask.mStack.isAdjustedForMinimizedDock();
- }
-
void onWindowReplacementTimeout() {
if (mWillReplaceWindow) {
// Since the window already timed out, remove it immediately now.
@@ -2304,6 +2300,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 +2310,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();
}
@@ -2363,7 +2360,13 @@
&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
&& (mAppToken == null || mAppToken.windowsAreFocusable())
- && !isAdjustedForMinimizedDock();
+ && !canReceiveTouchInput();
+ }
+
+ /** @return true if this window desires touch events. */
+ boolean canReceiveTouchInput() {
+ return mAppToken != null && mAppToken.mTask != null
+ && mAppToken.mTask.mStack.shouldIgnoreInput();
}
@Override
@@ -3198,8 +3201,10 @@
if (task == null) {
return false;
}
+ if (!StackId.isStackAffectedByDragResizing(getStackId())) {
+ return false;
+ }
if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
-
// Floating windows never enter drag resize mode.
return false;
}
diff --git a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
index 17a6c297..545b3d7 100644
--- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
+++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
@@ -19,6 +19,7 @@
#include "JNIHelp.h"
#include "jni.h"
+#include <math.h>
#include <stdlib.h>
#include <android/hardware/thermal/1.0/IThermal.h>
@@ -56,10 +57,16 @@
jmethodID initMethod;
} gCpuUsageInfoClassInfo;
+jfloat gUndefinedTemperature;
+
static sp<IThermal> gThermalModule;
// ----------------------------------------------------------------------------
+float finalizeTemperature(float temperature) {
+ return isnan(temperature) ? gUndefinedTemperature : temperature;
+}
+
static void nativeInit(JNIEnv* env, jobject obj) {
// TODO(b/31632518)
if (gThermalModule == nullptr) {
@@ -128,16 +135,16 @@
if (static_cast<int>(list[i].type) == type) {
switch (source) {
case TEMPERATURE_CURRENT:
- values[length++] = list[i].currentValue;
+ values[length++] = finalizeTemperature(list[i].currentValue);
break;
case TEMPERATURE_THROTTLING:
- values[length++] = list[i].throttlingThreshold;
+ values[length++] = finalizeTemperature(list[i].throttlingThreshold);
break;
case TEMPERATURE_SHUTDOWN:
- values[length++] = list[i].shutdownThreshold;
+ values[length++] = finalizeTemperature(list[i].shutdownThreshold);
break;
case TEMPERATURE_THROTTLING_BELOW_VR_MIN:
- values[length++] = list[i].vrThrottlingThreshold;
+ values[length++] = finalizeTemperature(list[i].vrThrottlingThreshold);
break;
}
}
@@ -204,6 +211,12 @@
gCpuUsageInfoClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
gCpuUsageInfoClassInfo.initMethod = GetMethodIDOrDie(env, gCpuUsageInfoClassInfo.clazz,
"<init>", "(JJ)V");
+
+ clazz = env->FindClass("android/os/HardwarePropertiesManager");
+ jfieldID undefined_temperature_field = GetStaticFieldIDOrDie(env, clazz,
+ "UNDEFINED_TEMPERATURE", "F");
+ gUndefinedTemperature = env->GetStaticFloatField(clazz, undefined_temperature_field);
+
return res;
}
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 118562f..50bae794 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -43,7 +43,7 @@
if (mHal != nullptr) {
return;
}
- mHal = IVibrator::getService("vibrator");
+ mHal = IVibrator::getService();
}
static jboolean vibratorExists(JNIEnv* /* env */, jobject /* clazz */)
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 6791da9..8baa96b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -207,6 +207,7 @@
void setPointerIconType(int32_t iconId);
void reloadPointerIcons();
void setCustomPointerIcon(const SpriteIcon& icon);
+ void setPointerCapture(bool enabled);
/* --- InputReaderPolicyInterface implementation --- */
@@ -276,6 +277,9 @@
// Show touches feature enable/disable.
bool showTouches;
+ // Pointer capture feature enable/disable.
+ bool pointerCapture;
+
// Sprite controller singleton, created on first use.
sp<SpriteController> spriteController;
@@ -312,6 +316,7 @@
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
+ mLocked.pointerCapture = false;
}
mInteractive = true;
@@ -339,6 +344,7 @@
dump.appendFormat(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump.appendFormat(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
+ dump.appendFormat(INDENT "Pointer Capture Enabled: %s\n", toString(mLocked.pointerCapture));
}
dump.append("\n");
@@ -460,6 +466,8 @@
outConfig->showTouches = mLocked.showTouches;
+ outConfig->pointerCapture = mLocked.pointerCapture;
+
outConfig->setDisplayInfo(false /*external*/, mLocked.internalViewport);
outConfig->setDisplayInfo(true /*external*/, mLocked.externalViewport);
} // release lock
@@ -767,6 +775,22 @@
InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
}
+void NativeInputManager::setPointerCapture(bool enabled) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mLocked.pointerCapture == enabled) {
+ return;
+ }
+
+ ALOGI("Setting pointer capture to %s.", enabled ? "enabled" : "disabled");
+ mLocked.pointerCapture = enabled;
+ } // release lock
+
+ mInputManager->getReader()->requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+}
+
void NativeInputManager::setInteractive(bool interactive) {
mInteractive = interactive;
}
@@ -1323,6 +1347,12 @@
im->setFocusedApplication(env, applicationHandleObj);
}
+static void nativeSetPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
+ jboolean enabled) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ im->setPointerCapture(enabled);
+}
+
static void nativeSetInputDispatchMode(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr, jboolean enabled, jboolean frozen) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1509,6 +1539,8 @@
(void*) nativeSetInputWindows },
{ "nativeSetFocusedApplication", "(JLcom/android/server/input/InputApplicationHandle;)V",
(void*) nativeSetFocusedApplication },
+ { "nativeSetPointerCapture", "(JZ)V",
+ (void*) nativeSetPointerCapture },
{ "nativeSetInputDispatchMode", "(JZZ)V",
(void*) nativeSetInputDispatchMode },
{ "nativeSetSystemUiVisibility", "(JI)V",
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/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index 179fba0..9f528b1 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -327,7 +327,7 @@
JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
// TODO(b/31632518)
- sp<ITvInput> tvInput = ITvInput::getService("tv.input");
+ sp<ITvInput> tvInput = ITvInput::getService();
if (tvInput == nullptr) {
ALOGE("Couldn't get tv.input service.");
return nullptr;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8835ab2..280ff2d0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -33,6 +33,13 @@
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_BLOCK_UNINSTALL;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
+import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
+import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
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 +266,17 @@
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,
+ DELEGATION_BLOCK_UNINSTALL,
+ DELEGATION_ENABLE_SYSTEM_APP,
+ DELEGATION_KEEP_UNINSTALLED_PACKAGES,
+ DELEGATION_PACKAGE_ACCESS,
+ DELEGATION_PERMISSION_GRANT
+ };
+
/**
* System property whose value is either "true" or "false", indicating whether
* device owner is present.
@@ -468,12 +486,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 +1414,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 +1430,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);
}
@@ -1510,6 +1531,10 @@
return (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
}
+ AlarmManager getAlarmManager() {
+ return (AlarmManager) mContext.getSystemService(AlarmManager.class);
+ }
+
IWindowManager getIWindowManager() {
return IWindowManager.Stub
.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
@@ -1609,6 +1634,11 @@
mContext.getSystemService(PowerManager.class).reboot(reason);
}
+ void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
+ throws IOException {
+ RecoverySystem.rebootWipeUserData(mContext, shutdown, reason, force);
+ }
+
boolean systemPropertiesGetBoolean(String key, boolean def) {
return SystemProperties.getBoolean(key, def);
}
@@ -2065,7 +2095,7 @@
long token = mInjector.binderClearCallingIdentity();
try {
int affectedUserHandle = parent ? getProfileParentId(userHandle) : userHandle;
- AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ AlarmManager am = mInjector.getAlarmManager();
PendingIntent pi = PendingIntent.getBroadcastAsUser(context, REQUEST_EXPIRE_PASSWORD,
new Intent(ACTION_EXPIRED_PASSWORD_NOTIFICATION),
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT,
@@ -2372,13 +2402,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();
@@ -2562,10 +2598,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();
@@ -2600,6 +2662,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"));
@@ -4522,9 +4601,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 {
@@ -4538,35 +4617,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();
@@ -4608,8 +4658,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 {
@@ -4651,8 +4702,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();
@@ -4676,9 +4727,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();
@@ -4709,8 +4762,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();
@@ -4795,33 +4849,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;
}
/**
@@ -4869,7 +5157,7 @@
}
}
- private void wipeDataNoLock(boolean wipeExtRequested, String reason, boolean force) {
+ private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason) {
wtfIfInLock();
if (wipeExtRequested) {
@@ -4878,94 +5166,87 @@
sm.wipeAdoptableDisks();
}
try {
- RecoverySystem.rebootWipeUserData(mContext, false /* shutdown */, reason, force);
+ mInjector.recoverySystemRebootWipeUserData(
+ /*shutdown=*/ false, reason, /*force=*/ true);
} catch (IOException | SecurityException e) {
Slog.w(LOG_TAG, "Failed requesting data wipe", e);
}
}
+ private void forceWipeUser(int userId) {
+ try {
+ IActivityManager am = mInjector.getIActivityManager();
+ if (am.getCurrentUser().id == userId) {
+ am.switchUser(UserHandle.USER_SYSTEM);
+ }
+
+ boolean userRemoved = mUserManagerInternal.removeUserEvenWhenDisallowed(userId);
+ if (!userRemoved) {
+ Slog.w(LOG_TAG, "Couldn't remove user " + userId);
+ } else if (isManagedProfile(userId)) {
+ sendWipeProfileNotification();
+ }
+ } catch (RemoteException re) {
+ // Shouldn't happen
+ }
+ }
+
@Override
public void wipeData(int flags) {
if (!mHasFeature) {
return;
}
- final int userHandle = mInjector.userHandleGetCallingUserId();
- enforceFullCrossUsersPermission(userHandle);
+ enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
- final String source;
+ final ActiveAdmin admin;
synchronized (this) {
- // This API can only be called by an active device admin,
- // so try to retrieve it to check that the caller is one.
- final ActiveAdmin admin = getActiveAdminForCallerLocked(null,
- DeviceAdminInfo.USES_POLICY_WIPE_DATA);
- source = admin.info.getComponent().flattenToShortString();
-
- long ident = mInjector.binderClearCallingIdentity();
- try {
- final String restriction;
- if (userHandle == UserHandle.USER_SYSTEM) {
- restriction = UserManager.DISALLOW_FACTORY_RESET;
- } else if (isManagedProfile(userHandle)) {
- restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
- } else {
- restriction = UserManager.DISALLOW_REMOVE_USER;
- }
- if (isAdminAffectedByRestriction(
- admin.info.getComponent(), restriction, userHandle)) {
- throw new SecurityException("Cannot wipe data. " + restriction
- + " restriction is set for user " + userHandle);
- }
-
- if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
- if (!isDeviceOwner(admin.info.getComponent(), userHandle)) {
- throw new SecurityException(
- "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
- }
- PersistentDataBlockManager manager = (PersistentDataBlockManager)
- mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
- if (manager != null) {
- manager.wipe();
- }
- }
-
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA);
}
- final boolean wipeExtRequested = (flags & WIPE_EXTERNAL_STORAGE) != 0;
- wipeDeviceNoLock(wipeExtRequested, userHandle,
- "DevicePolicyManager.wipeData() from " + source, /*force=*/ true);
+ String reason = "DevicePolicyManager.wipeData() from "
+ + admin.info.getComponent().flattenToShortString();
+ wipeDataNoLock(
+ admin.info.getComponent(), flags, reason, admin.getUserHandle().getIdentifier());
}
- private void wipeDeviceNoLock(
- boolean wipeExtRequested, final int userHandle, String reason, boolean force) {
+ private void wipeDataNoLock(ComponentName admin, int flags, String reason, int userId) {
wtfIfInLock();
long ident = mInjector.binderClearCallingIdentity();
try {
- // TODO If split user is enabled and the device owner is set in the primary user (rather
- // than system), we should probably trigger factory reset. Current code just remove
- // that user (but still clears FRP...)
- if (userHandle == UserHandle.USER_SYSTEM) {
- wipeDataNoLock(wipeExtRequested, reason, force);
+ // First check whether the admin is allowed to wipe the device/user/profile.
+ final String restriction;
+ if (userId == UserHandle.USER_SYSTEM) {
+ restriction = UserManager.DISALLOW_FACTORY_RESET;
+ } else if (isManagedProfile(userId)) {
+ restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
} else {
- try {
- IActivityManager am = mInjector.getIActivityManager();
- if (am.getCurrentUser().id == userHandle) {
- am.switchUser(UserHandle.USER_SYSTEM);
- }
+ restriction = UserManager.DISALLOW_REMOVE_USER;
+ }
+ if (isAdminAffectedByRestriction(admin, restriction, userId)) {
+ throw new SecurityException("Cannot wipe data. " + restriction
+ + " restriction is set for user " + userId);
+ }
- boolean userRemoved = force
- ? mUserManagerInternal.removeUserEvenWhenDisallowed(userHandle)
- : mUserManager.removeUser(userHandle);
- if (!userRemoved) {
- Slog.w(LOG_TAG, "Couldn't remove user " + userHandle);
- } else if (isManagedProfile(userHandle)) {
- sendWipeProfileNotification();
- }
- } catch (RemoteException re) {
- // Shouldn't happen
+ if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
+ if (!isDeviceOwner(admin, userId)) {
+ throw new SecurityException(
+ "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
}
+ PersistentDataBlockManager manager = (PersistentDataBlockManager)
+ mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ if (manager != null) {
+ manager.wipe();
+ }
+ }
+
+ // TODO If split user is enabled and the device owner is set in the primary user
+ // (rather than system), we should probably trigger factory reset. Current code just
+ // removes that user (but still clears FRP...)
+ if (userId == UserHandle.USER_SYSTEM) {
+ forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0,
+ reason);
+ } else {
+ forceWipeUser(userId);
}
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -5105,25 +5386,21 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+ boolean wipeData = false;
+ ActiveAdmin strictestAdmin = null;
final long ident = mInjector.binderClearCallingIdentity();
try {
- boolean wipeData = false;
- int identifier = 0;
synchronized (this) {
DevicePolicyData policy = getUserData(userHandle);
policy.mFailedPasswordAttempts++;
saveSettingsLocked(userHandle);
if (mHasFeature) {
- ActiveAdmin strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
+ strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
userHandle, /* parent */ false);
int max = strictestAdmin != null
? strictestAdmin.maximumFailedPasswordsForWipe : 0;
if (max > 0 && policy.mFailedPasswordAttempts >= max) {
- // Wipe the user/profile associated with the policy that was violated. This
- // is not necessarily calling user: if the policy that fired was from a
- // managed profile rather than the main user profile, we wipe former only.
wipeData = true;
- identifier = strictestAdmin.getUserHandle().getIdentifier();
}
sendAdminCommandForLockscreenPoliciesLocked(
@@ -5131,14 +5408,33 @@
DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle);
}
}
- if (wipeData) {
- // Call without holding lock.
- wipeDeviceNoLock(false, identifier, "reportFailedPasswordAttempt()", false);
- }
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
+ if (wipeData && strictestAdmin != null) {
+ final int userId = strictestAdmin.getUserHandle().getIdentifier();
+ Slog.i(LOG_TAG, "Max failed password attempts policy reached for admin: "
+ + strictestAdmin.info.getComponent().flattenToShortString()
+ + ". Calling wipeData for user " + userId);
+
+ // Attempt to wipe the device/user/profile associated with the admin, as if the
+ // admin had called wipeData(). That way we can check whether the admin is actually
+ // allowed to wipe the device (e.g. a regular device admin shouldn't be able to wipe the
+ // device if the device owner has set DISALLOW_FACTORY_RESET, but the DO should be
+ // able to do so).
+ // IMPORTANT: Call without holding the lock to prevent deadlock.
+ try {
+ wipeDataNoLock(strictestAdmin.info.getComponent(),
+ /*flags=*/ 0,
+ /*reason=*/ "reportFailedPasswordAttempt()",
+ userId);
+ } catch (SecurityException e) {
+ Slog.w(LOG_TAG, "Failed to wipe user " + userId
+ + " after max failed password attempts reached.", e);
+ }
+ }
+
if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0,
/*method strength*/ 1);
@@ -5574,7 +5870,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) {
@@ -5585,7 +5881,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);
@@ -5604,7 +5900,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() {
@@ -5613,7 +5909,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;
}
}
@@ -6003,32 +6312,38 @@
}
@Override
- public void setKeepUninstalledPackages(ComponentName who, List<String> packageList) {
+ public void setKeepUninstalledPackages(ComponentName who, String callerPackage,
+ List<String> packageList) {
if (!mHasFeature) {
return;
}
- Preconditions.checkNotNull(who, "ComponentName is null");
Preconditions.checkNotNull(packageList, "packageList is null");
final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- ActiveAdmin admin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
- admin.keepUninstalledPackages = packageList;
+ // Ensure the caller is a DO or a keep uninstalled packages delegate.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+ DELEGATION_KEEP_UNINSTALLED_PACKAGES);
+ // Get the device owner
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ // Set list of packages to be kept even if uninstalled.
+ deviceOwner.keepUninstalledPackages = packageList;
+ // Save settings.
saveSettingsLocked(userHandle);
+ // Notify package manager.
mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
}
}
@Override
- public List<String> getKeepUninstalledPackages(ComponentName who) {
- Preconditions.checkNotNull(who, "ComponentName is null");
+ public List<String> getKeepUninstalledPackages(ComponentName who, String callerPackage) {
if (!mHasFeature) {
return null;
}
// TODO In split system user mode, allow apps on user 0 to query the list
synchronized (this) {
- // Check if this is the device owner who is calling
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ // Ensure the caller is a DO or a keep uninstalled packages delegate.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+ DELEGATION_KEEP_UNINSTALLED_PACKAGES);
return getKeepUninstalledPackagesLocked();
}
}
@@ -6097,7 +6412,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);
@@ -6133,6 +6449,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);
@@ -6240,6 +6563,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);
@@ -6374,11 +6698,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);
@@ -6962,66 +7286,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();
@@ -7711,8 +8000,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();
@@ -7727,12 +8018,13 @@
}
@Override
- public String[] setPackagesSuspended(ComponentName who, String[] packageNames,
- boolean suspended) {
- Preconditions.checkNotNull(who, "ComponentName is null");
+ public String[] setPackagesSuspended(ComponentName who, String callerPackage,
+ String[] packageNames, boolean suspended) {
int callingUserId = UserHandle.getCallingUserId();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Ensure the caller is a DO/PO or a package access delegate.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_PACKAGE_ACCESS);
long id = mInjector.binderClearCallingIdentity();
try {
@@ -7749,10 +8041,12 @@
}
@Override
- public boolean isPackageSuspended(ComponentName who, String packageName) {
- Preconditions.checkNotNull(who, "ComponentName is null");
+ public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
int callingUserId = UserHandle.getCallingUserId();
synchronized (this) {
+ // Ensure the caller is a DO/PO or a package access delegate.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_PACKAGE_ACCESS);
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long id = mInjector.binderClearCallingIdentity();
@@ -7864,12 +8158,13 @@
}
@Override
- public boolean setApplicationHidden(ComponentName who, String packageName,
+ public boolean setApplicationHidden(ComponentName who, String callerPackage, String packageName,
boolean hidden) {
- Preconditions.checkNotNull(who, "ComponentName is null");
int callingUserId = UserHandle.getCallingUserId();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Ensure the caller is a DO/PO or a package access delegate.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_PACKAGE_ACCESS);
long id = mInjector.binderClearCallingIdentity();
try {
@@ -7886,11 +8181,13 @@
}
@Override
- public boolean isApplicationHidden(ComponentName who, String packageName) {
- Preconditions.checkNotNull(who, "ComponentName is null");
+ public boolean isApplicationHidden(ComponentName who, String callerPackage,
+ String packageName) {
int callingUserId = UserHandle.getCallingUserId();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Ensure the caller is a DO/PO or a package access delegate.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_PACKAGE_ACCESS);
long id = mInjector.binderClearCallingIdentity();
try {
@@ -7907,12 +8204,11 @@
}
@Override
- public void enableSystemApp(ComponentName who, String packageName) {
- Preconditions.checkNotNull(who, "ComponentName is null");
+ public void enableSystemApp(ComponentName who, String callerPackage, String packageName) {
synchronized (this) {
- // This API can only be called by an active device admin,
- // so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Ensure the caller is a DO/PO or an enable system app delegate.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_ENABLE_SYSTEM_APP);
int userId = UserHandle.getCallingUserId();
long id = mInjector.binderClearCallingIdentity();
@@ -7942,12 +8238,11 @@
}
@Override
- public int enableSystemAppWithIntent(ComponentName who, Intent intent) {
- Preconditions.checkNotNull(who, "ComponentName is null");
+ public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
synchronized (this) {
- // This API can only be called by an active device admin,
- // so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Ensure the caller is a DO/PO or an enable system app delegate.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_ENABLE_SYSTEM_APP);
int userId = UserHandle.getCallingUserId();
long id = mInjector.binderClearCallingIdentity();
@@ -8046,12 +8341,13 @@
}
@Override
- public void setUninstallBlocked(ComponentName who, String packageName,
+ public void setUninstallBlocked(ComponentName who, String callerPackage, String packageName,
boolean uninstallBlocked) {
- Preconditions.checkNotNull(who, "ComponentName is null");
final int userId = UserHandle.getCallingUserId();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Ensure the caller is a DO/PO or a block uninstall delegate
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_BLOCK_UNINSTALL);
long id = mInjector.binderClearCallingIdentity();
try {
@@ -8665,18 +8961,20 @@
ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
if (profileOwner != null) {
- return createShowAdminSupportIntent(profileOwner, userId);
+ return DevicePolicyManagerService.this
+ .createShowAdminSupportIntent(profileOwner, userId);
}
final Pair<Integer, ComponentName> deviceOwner =
mOwners.getDeviceOwnerUserIdAndComponent();
if (deviceOwner != null && deviceOwner.first == userId) {
- return createShowAdminSupportIntent(deviceOwner.second, userId);
+ return DevicePolicyManagerService.this
+ .createShowAdminSupportIntent(deviceOwner.second, userId);
}
// We're not specifying the device admin because there isn't one.
if (useDefaultIfNoAdmin) {
- return createShowAdminSupportIntent(null, userId);
+ return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId);
}
return null;
}
@@ -8704,11 +9002,12 @@
if (enforcedByDo && enforcedByPo) {
// In this case, we'll show an admin support dialog that does not
// specify the admin.
- return createShowAdminSupportIntent(null, userId);
+ return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId);
} else if (enforcedByPo) {
final ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
if (profileOwner != null) {
- return createShowAdminSupportIntent(profileOwner, userId);
+ return DevicePolicyManagerService.this
+ .createShowAdminSupportIntent(profileOwner, userId);
}
// This could happen if another thread has changed the profile owner since we called
// getUserRestrictionSource
@@ -8717,7 +9016,8 @@
final Pair<Integer, ComponentName> deviceOwner
= mOwners.getDeviceOwnerUserIdAndComponent();
if (deviceOwner != null) {
- return createShowAdminSupportIntent(deviceOwner.second, deviceOwner.first);
+ return DevicePolicyManagerService.this
+ .createShowAdminSupportIntent(deviceOwner.second, deviceOwner.first);
}
// This could happen if another thread has changed the device owner since we called
// getUserRestrictionSource
@@ -8725,15 +9025,57 @@
}
return null;
}
+ }
- private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
- // This method is called with AMS lock held, so don't take DPMS lock
- final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
- intent.putExtra(Intent.EXTRA_USER_ID, userId);
- intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
+ private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
+ // This method is called with AMS lock held, so don't take DPMS lock
+ final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
+ intent.putExtra(Intent.EXTRA_USER_ID, userId);
+ intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ @Override
+ public Intent createAdminSupportIntent(String restriction) {
+ Preconditions.checkNotNull(restriction);
+ final int uid = mInjector.binderGetCallingUid();
+ final int userId = UserHandle.getUserId(uid);
+ Intent intent = null;
+ if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction) ||
+ DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+ synchronized(this) {
+ final DevicePolicyData policy = getUserData(userId);
+ final int N = policy.mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ final ActiveAdmin admin = policy.mAdminList.get(i);
+ if ((admin.disableCamera &&
+ DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) ||
+ (admin.disableScreenCapture && DevicePolicyManager
+ .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
+ intent = createShowAdminSupportIntent(admin.info.getComponent(), userId);
+ break;
+ }
+ }
+ // For the camera, a device owner on a different user can disable it globally,
+ // so we need an additional check.
+ if (intent == null
+ && DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
+ final ActiveAdmin admin = getDeviceOwnerAdminLocked();
+ if (admin != null && admin.disableCamera) {
+ intent = createShowAdminSupportIntent(admin.info.getComponent(),
+ mOwners.getDeviceOwnerUserId());
+ }
+ }
+ }
+ } else {
+ // if valid, |restriction| can only be a user restriction
+ intent = mLocalService.createUserRestrictionSupportIntent(userId, restriction);
}
+ if (intent != null) {
+ intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, restriction);
+ }
+ return intent;
}
/**
@@ -8814,7 +9156,7 @@
}
@Override
- public void notifyPendingSystemUpdate(long updateReceivedTime) {
+ public void notifyPendingSystemUpdate(@Nullable SystemUpdateInfo info) {
mContext.enforceCallingOrSelfPermission(permission.NOTIFY_PENDING_SYSTEM_UPDATE,
"Only the system update service can broadcast update information");
@@ -8824,13 +9166,14 @@
return;
}
- if (!mOwners.saveSystemUpdateInfo(updateReceivedTime)) {
- // Received time hasn't changed, don't send duplicate notification.
+ if (!mOwners.saveSystemUpdateInfo(info)) {
+ // Pending system update hasn't changed, don't send duplicate notification.
return;
}
- final Intent intent = new Intent(DeviceAdminReceiver.ACTION_NOTIFY_PENDING_SYSTEM_UPDATE);
- intent.putExtra(DeviceAdminReceiver.EXTRA_SYSTEM_UPDATE_RECEIVED_TIME, updateReceivedTime);
+ final Intent intent = new Intent(DeviceAdminReceiver.ACTION_NOTIFY_PENDING_SYSTEM_UPDATE)
+ .putExtra(DeviceAdminReceiver.EXTRA_SYSTEM_UPDATE_RECEIVED_TIME,
+ info == null ? -1 : info.getReceivedTime());
final long ident = mInjector.binderClearCallingIdentity();
try {
@@ -8877,10 +9220,13 @@
}
@Override
- public void setPermissionPolicy(ComponentName admin, int policy) throws RemoteException {
+ public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy)
+ throws RemoteException {
int userId = UserHandle.getCallingUserId();
synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Ensure the caller is a DO/PO or a permission grant state delegate.
+ enforceCanManageScope(admin, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_PERMISSION_GRANT);
DevicePolicyData userPolicy = getUserData(userId);
if (userPolicy.mPermissionPolicy != policy) {
userPolicy.mPermissionPolicy = policy;
@@ -8899,11 +9245,13 @@
}
@Override
- public boolean setPermissionGrantState(ComponentName admin, String packageName,
- String permission, int grantState) throws RemoteException {
+ public boolean setPermissionGrantState(ComponentName admin, String callerPackage,
+ String packageName, String permission, int grantState) throws RemoteException {
UserHandle user = mInjector.binderGetCallingUserHandle();
synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Ensure the caller is a DO/PO or a permission grant state delegate.
+ enforceCanManageScope(admin, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_PERMISSION_GRANT);
long ident = mInjector.binderClearCallingIdentity();
try {
if (getTargetSdk(packageName, user.getIdentifier())
@@ -8943,12 +9291,16 @@
}
@Override
- public int getPermissionGrantState(ComponentName admin, String packageName,
- String permission) throws RemoteException {
+ public int getPermissionGrantState(ComponentName admin, String callerPackage,
+ String packageName, String permission) throws RemoteException {
PackageManager packageManager = mInjector.getPackageManager();
UserHandle user = mInjector.binderGetCallingUserHandle();
- enforceProfileOwnerOrSystemUser(admin);
+ if (!isCallerWithSystemUid()) {
+ // Ensure the caller is a DO/PO or a permission grant state delegate.
+ enforceCanManageScope(admin, callerPackage,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, DELEGATION_PERMISSION_GRANT);
+ }
synchronized (this) {
long ident = mInjector.binderClearCallingIdentity();
try {
@@ -9105,21 +9457,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;
}
@@ -9129,6 +9485,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/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 7d68412..9b4de043 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -16,12 +16,15 @@
package com.android.server.devicepolicy;
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.NetworkEvent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.SystemClock;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -42,11 +45,26 @@
// If this value changes, update DevicePolicyManager#retrieveNetworkLogs() javadoc
private static final int MAX_EVENTS_PER_BATCH = 1200;
private static final long BATCH_FINALIZATION_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(90);
+ private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS =
+ TimeUnit.MINUTES.toMillis(30);
- static final int LOG_NETWORK_EVENT_MSG = 1;
- static final int FINALIZE_BATCH_MSG = 2;
+ private static final String NETWORK_LOGGING_TIMEOUT_ALARM_TAG = "NetworkLogging.batchTimeout";
private final DevicePolicyManagerService mDpm;
+ private final AlarmManager mAlarmManager;
+
+ private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
+ @Override
+ public void onAlarm() {
+ Log.d(TAG, "Received a batch finalization timeout alarm, finalizing "
+ + mNetworkEvents.size() + " pending events.");
+ synchronized (NetworkLoggingHandler.this) {
+ finalizeBatchAndNotifyDeviceOwnerLocked();
+ }
+ }
+ };
+
+ static final int LOG_NETWORK_EVENT_MSG = 1;
// threadsafe as it's Handler's thread confined
@GuardedBy("this")
@@ -68,6 +86,7 @@
NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
super(looper);
mDpm = dpm;
+ mAlarmManager = mDpm.mInjector.getAlarmManager();
}
@Override
@@ -85,19 +104,19 @@
}
break;
}
- case FINALIZE_BATCH_MSG: {
- synchronized (NetworkLoggingHandler.this) {
- finalizeBatchAndNotifyDeviceOwnerLocked();
- }
+ default: {
+ Log.d(TAG, "NetworkLoggingHandler received an unknown of message.");
break;
}
}
}
void scheduleBatchFinalization() {
- removeMessages(FINALIZE_BATCH_MSG);
- sendMessageDelayed(obtainMessage(FINALIZE_BATCH_MSG), BATCH_FINALIZATION_TIMEOUT_MS);
- Log.d(TAG, "Scheduled new batch finalization " + BATCH_FINALIZATION_TIMEOUT_MS
+ final long when = SystemClock.elapsedRealtime() + BATCH_FINALIZATION_TIMEOUT_MS;
+ mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, when,
+ BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS, NETWORK_LOGGING_TIMEOUT_ALARM_TAG,
+ mBatchTimeoutAlarmListener, this);
+ Log.d(TAG, "Scheduled a new batch finalization alarm " + BATCH_FINALIZATION_TIMEOUT_MS
+ "ms from now.");
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 99c76b1..a5500dd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -476,19 +476,20 @@
}
/**
- * @return Whether update received time has changed.
+ * Saves the given {@link SystemUpdateInfo} if it is different from the existing one, or if
+ * none exists.
+ *
+ * @return Whether the saved system update information has changed.
*/
- boolean saveSystemUpdateInfo(long receivedTime) {
- final SystemUpdateInfo newSystemUpdateInfo = SystemUpdateInfo.of(receivedTime);
+ boolean saveSystemUpdateInfo(@Nullable SystemUpdateInfo newInfo) {
synchronized (mLock) {
// Check if we already have the same update information.
- if (Objects.equals(newSystemUpdateInfo, mSystemUpdateInfo)) {
+ if (Objects.equals(newInfo, mSystemUpdateInfo)) {
return false;
}
- mSystemUpdateInfo = newSystemUpdateInfo;
+ mSystemUpdateInfo = newInfo;
new DeviceOwnerReadWriter().writeToFileLocked();
-
return true;
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b6c518b..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);
@@ -486,6 +489,18 @@
mActivityManagerService.initPowerManagement();
traceEnd();
+ // Bring up recovery system in case a rescue party needs a reboot
+ if (!SystemProperties.getBoolean("config.disable_noncore", false)) {
+ traceBeginAndSlog("StartRecoverySystemService");
+ mSystemServiceManager.startService(RecoverySystemService.class);
+ traceEnd();
+ }
+
+ // Now that we have the bare essentials of the OS up and running, take
+ // note that we just booted, which might send out a rescue party if
+ // we're stuck in a runtime restart loop.
+ RescueParty.noteBoot(mSystemContext);
+
// Manages LEDs and display backlight so we need it to bring up the display.
traceBeginAndSlog("StartLightsService");
mSystemServiceManager.startService(LightsService.class);
@@ -523,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());
}
@@ -643,6 +658,11 @@
boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");
+ // For debugging RescueParty
+ if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_system", false)) {
+ throw new RuntimeException();
+ }
+
try {
Slog.i(TAG, "Reading configuration...");
traceBeginAndSlog("ReadingSystemConfig");
@@ -919,6 +939,12 @@
traceEnd();
}
+ if (!disableNonCoreServices) {
+ traceBeginAndSlog("StartFontServiceManager");
+ mSystemServiceManager.startService(FontManagerService.Lifecycle.class);
+ traceEnd();
+ }
+
if (!disableNonCoreServices && !disableTextServices) {
traceBeginAndSlog("StartTextServicesManager");
mSystemServiceManager.startService(TextServicesManagerService.Lifecycle.class);
@@ -1026,12 +1052,6 @@
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartRecoverSystemService");
- mSystemServiceManager.startService(RecoverySystemService.class);
- traceEnd();
- }
-
/*
* StorageManagerService has a few dependencies: Notification Manager and
* AppWidget Provider. Make sure StorageManagerService is completely started
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 8dd05b1..2624f0b 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -30,6 +30,7 @@
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.NetworkUtils;
+import android.net.TrafficStats;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.DhcpErrorEvent;
@@ -303,6 +304,7 @@
}
private boolean initUdpSocket() {
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP);
try {
mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
@@ -314,6 +316,8 @@
} catch(SocketException|ErrnoException e) {
Log.e(TAG, "Error creating UDP socket", e);
return false;
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
}
return true;
}
diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
index 6802cff..ba1621d 100644
--- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
+++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
@@ -22,6 +22,7 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
+import android.net.TrafficStats;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructGroupReq;
@@ -563,6 +564,7 @@
private boolean createSocket() {
final int SEND_TIMEOUT_MS = 300;
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_NEIGHBOR);
try {
mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
// Setting SNDTIMEO is purely for defensive purposes.
@@ -574,6 +576,8 @@
} catch (ErrnoException | IOException e) {
Log.e(TAG, "Failed to create RA daemon socket: " + e);
return false;
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
}
return true;
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 785c3fa..f89fd47 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,28 @@
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(() -> {
+ 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 +319,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 +345,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 +353,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) {
@@ -380,18 +440,43 @@
private boolean deletePreloadsFolderContents() {
final File dir = mInjector.getDataPreloadsDirectory();
+ final File[] files = FileUtils.listFilesOrEmpty(dir);
+ final File fileCacheDirectory = mInjector.getDataPreloadsFileCacheDirectory();
Slog.i(TAG, "Deleting contents of " + dir);
- return FileUtils.deleteContents(dir);
+ boolean success = true;
+ for (File file : files) {
+ if (file.isFile()) {
+ if (!file.delete()) {
+ success = false;
+ Slog.w(TAG, "Cannot delete file " + file);
+ }
+ } else {
+ // Do not remove file_cache dir
+ if (!file.equals(fileCacheDirectory)) {
+ if (!FileUtils.deleteContentsAndDir(file)) {
+ success = false;
+ Slog.w(TAG, "Cannot delete dir and its content " + file);
+ }
+ } else {
+ Slog.i(TAG, "Skipping directory with file cache " + file);
+ }
+ }
+ }
+ return success;
}
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 +512,8 @@
}
private void startDemoMode() {
+ mDeviceInDemoMode = true;
+
mPreloadAppsInstaller = mInjector.getPreloadAppsInstaller();
mInjector.initializeWakeLock();
if (mCameraIdsWithFlash == null) {
@@ -434,6 +521,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 +564,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 +589,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 +629,7 @@
@Override
public void onUserActivity() {
- if (!mDeviceInDemoMode) {
+ if (!mDeviceInDemoMode || mIsCarrierDemoMode) {
return;
}
long timeOfActivity = SystemClock.uptimeMillis();
@@ -682,7 +780,7 @@
}
boolean isWakeLockHeld() {
- return mWakeLock.isHeld();
+ return mWakeLock != null && mWakeLock.isHeld();
}
void acquireWakeLock() {
@@ -738,6 +836,10 @@
return Environment.getDataPreloadsDirectory();
}
+ File getDataPreloadsFileCacheDirectory() {
+ return Environment.getDataPreloadsFileCacheDirectory();
+ }
+
void publishLocalService(RetailDemoModeService service,
RetailDemoModeServiceInternal localService) {
service.publishLocalService(RetailDemoModeServiceInternal.class, localService);
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..e183a31 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,55 +35,112 @@
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;
+import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
-@SmallTest
-@RunWith(AndroidJUnit4.class)
public class NotificationManagerServiceTest {
+ private static final long WAIT_FOR_IDLE_TIMEOUT = 2;
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
+ @Test
@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();
+ if (queue.isIdle()) {
+ return;
+ }
+ CountDownLatch latch = new CountDownLatch(1);
+ queue.addIdleHandler(new MessageQueue.IdleHandler() {
+ @Override public boolean queueIdle() {
+ latch.countDown();
+ return false;
+ }
+ });
+ // Timeout is valid in the cases where the queue goes idle before the IdleHandler
+ // is added.
+ latch.await(WAIT_FOR_IDLE_TIMEOUT, TimeUnit.SECONDS);
+ 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 +262,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 f8061f6..5be6e91 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
import org.junit.Before;
@@ -29,6 +30,7 @@
import org.xmlpull.v1.XmlSerializer;
import android.app.Notification;
+import android.app.NotificationChannelGroup;
import android.content.Context;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -49,6 +51,9 @@
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -100,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")
@@ -110,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")
@@ -135,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;
@@ -166,7 +171,7 @@
serializer.flush();
for (String channelId : channelIds) {
- mHelper.deleteNotificationChannel(pkg, uid, channelId);
+ mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
}
return baos;
}
@@ -181,6 +186,7 @@
assertEquals(expected.getSound(), actual.getSound());
assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
+ assertEquals(expected.getGroup(), actual.getGroup());
}
@Test
@@ -235,6 +241,7 @@
@Test
public void testChannelXml() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("1", "2");
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
@@ -244,17 +251,19 @@
channel2.setBypassDnd(true);
channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel2.enableVibration(true);
+ channel2.setGroup(ncg.getId());
channel2.setVibrationPattern(new long[] {100, 67, 145, 156});
- mHelper.createNotificationChannel(pkg, uid, channel1, false);
+ mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ 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.deleteNotificationChannel(pkg, uid, channel1.getId());
- mHelper.deleteNotificationChannel(pkg, uid, channel2.getId());
- mHelper.deleteNotificationChannel(pkg, uid, 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())),
@@ -262,10 +271,17 @@
parser.nextTag();
mHelper.readXml(parser, false);
- assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId()));
- compareChannels(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId()));
- assertNotNull(
- mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID));
+ 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));
+ assertNotNull(mHelper.getNotificationChannel(
+ pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ assertEquals(ncg.getId(),
+ mHelper.getNotificationChannelGroups(pkg, uid, false).getList().get(0).getId());
+ assertEquals(channel2.getGroup(), mHelper.getNotificationChannelGroups(
+ pkg, uid, false).getList().get(0).getChannels().get(0).getGroup());
}
@Test
@@ -284,8 +300,8 @@
parser.nextTag();
mHelper.readXml(parser, false);
- final NotificationChannel updated =
- mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
+ final NotificationChannel updated = mHelper.getNotificationChannel(
+ pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
assertFalse(updated.canBypassDnd());
assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,updated.getLockscreenVisibility());
@@ -298,8 +314,8 @@
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
mHelper.createNotificationChannel(pkg, uid, channel1, true);
- final NotificationChannel defaultChannel =
- mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+ pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mHelper.updateNotificationChannel(pkg, uid, defaultChannel);
@@ -313,7 +329,7 @@
mHelper.readXml(parser, false);
assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID).getImportance());
+ pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
}
@Test
@@ -331,8 +347,8 @@
parser.nextTag();
mHelper.readXml(parser, false);
- final NotificationChannel updated1 =
- mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
+ final NotificationChannel updated1 = mHelper.getNotificationChannel(
+ pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
assertTrue(updated1.canBypassDnd());
assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
@@ -340,8 +356,8 @@
| NotificationChannel.USER_LOCKED_PRIORITY
| NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
- final NotificationChannel updated2 =
- mHelper.getNotificationChannel(pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID);
+ final NotificationChannel updated2 = mHelper.getNotificationChannel(
+ pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID, false);
// clamped
assertEquals(NotificationManager.IMPORTANCE_LOW, updated2.getImportance());
assertFalse(updated2.canBypassDnd());
@@ -378,7 +394,7 @@
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
}
@Test
@@ -399,7 +415,7 @@
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
}
@Test
@@ -421,7 +437,7 @@
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
}
@Test
@@ -442,7 +458,7 @@
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
}
@Test
@@ -463,7 +479,7 @@
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
}
@Test
@@ -484,7 +500,7 @@
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
}
@Test
@@ -503,7 +519,7 @@
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
}
@Test
@@ -529,13 +545,13 @@
mHelper.updateNotificationChannel(pkg, uid, channel2);
// all fields should be changed
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
}
@Test
public void testGetChannelWithFallback() throws Exception {
NotificationChannel channel =
- mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage");
+ mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage", false);
assertEquals(NotificationChannel.DEFAULT_CHANNEL_ID, channel.getId());
}
@@ -557,7 +573,7 @@
mHelper.createNotificationChannel(pkg, uid, channel, true);
NotificationChannel savedChannel =
- mHelper.getNotificationChannel(pkg, uid, channel.getId());
+ mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
assertEquals(channel.getName(), savedChannel.getName());
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -584,7 +600,7 @@
mHelper.createNotificationChannel(pkg, uid, channel, true);
NotificationChannel savedChannel =
- mHelper.getNotificationChannel(pkg, uid, channel.getId());
+ mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
assertEquals(channel.getName(), savedChannel.getName());
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -592,4 +608,276 @@
assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
}
+
+ @Test
+ public void testGetDeletedChannel() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.enableVibration(true);
+ channel.setVibrationPattern(new long[] {100, 67, 145, 156});
+
+ mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+
+ // Does not return deleted channel
+ NotificationChannel response =
+ mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ assertNull(response);
+
+ // Returns deleted channel
+ response = mHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+ compareChannels(channel, response);
+ assertTrue(response.isDeleted());
+ }
+
+ @Test
+ public void testGetDeletedChannels() throws Exception {
+ Map<String, NotificationChannel> channelMap = new HashMap<>();
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+ channel.enableVibration(true);
+ channel.setVibrationPattern(new long[] {100, 67, 145, 156});
+ channelMap.put(channel.getId(), channel);
+ NotificationChannel channel2 =
+ new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
+ channelMap.put(channel2.getId(), channel2);
+ mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(pkg, uid, channel2, true);
+
+ mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+
+ // Returns only non-deleted channels
+ List<NotificationChannel> channels =
+ mHelper.getNotificationChannels(pkg, uid, false).getList();
+ assertEquals(2, channels.size()); // Default channel + non-deleted channel
+ for (NotificationChannel nc : channels) {
+ if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+ compareChannels(channel2, nc);
+ }
+ }
+
+ // Returns deleted channels too
+ channels = mHelper.getNotificationChannels(pkg, uid, true).getList();
+ assertEquals(3, channels.size()); // Includes default channel
+ for (NotificationChannel nc : channels) {
+ if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+ compareChannels(channelMap.get(nc.getId()), nc);
+ }
+ }
+ }
+
+ @Test
+ public void testUpdateDeletedChannels() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(pkg, uid, channel, true);
+
+ mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+
+ channel.setSound(new Uri.Builder().scheme("test").build());
+ try {
+ mHelper.updateNotificationChannel(pkg, uid, channel);
+ fail("Updated deleted channel");
+ } catch (IllegalArgumentException e) {
+ // :)
+ }
+
+ try {
+ mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+ fail("Updated deleted channel");
+ } catch (IllegalArgumentException e) {
+ // :)
+ }
+ }
+
+ @Test
+ public void testCreateDeletedChannel() throws Exception {
+ long[] vibration = new long[] {100, 67, 145, 156};
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setVibrationPattern(vibration);
+
+ mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+
+ NotificationChannel newChannel = new NotificationChannel(
+ channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
+ newChannel.setVibrationPattern(new long[] {100});
+
+ mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+
+ // No long deleted, using old settings
+ compareChannels(channel,
+ mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+ }
+
+ @Test
+ public void testCreateChannel_alreadyExists() throws Exception {
+ long[] vibration = new long[] {100, 67, 145, 156};
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setVibrationPattern(vibration);
+
+ mHelper.createNotificationChannel(pkg, uid, channel, true);
+
+ NotificationChannel newChannel = new NotificationChannel(
+ channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
+ newChannel.setVibrationPattern(new long[] {100});
+
+ mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+
+ // Old settings not overridden
+ compareChannels(channel,
+ mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+ }
+
+ @Test
+ public void testPermanentlyDeleteChannels() throws Exception {
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+
+ mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(pkg, uid, channel2, false);
+
+ mHelper.permanentlyDeleteNotificationChannels(pkg, uid);
+
+ // Only default channel remains
+ assertEquals(1, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+ }
+
+ @Test
+ public void testOnPackageChanged_packageRemoval() throws Exception {
+ // Deleted
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(pkg, uid, channel1, true);
+
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+
+ assertEquals(0, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+
+ // Not deleted
+ mHelper.createNotificationChannel(pkg, uid, channel1, true);
+
+ mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+ }
+
+ @Test
+ public void testOnPackageChanged_packageRemoval_importance() throws Exception {
+ mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
+
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
+ }
+
+ @Test
+ public void testOnPackageChanged_packageRemoval_groups() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
+ mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+
+ assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid, true).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());
+ }
+
+ @Test
+ public void testCreateGroup() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid, false).getList().get(0));
+ }
+
+ @Test
+ public void testCannotCreateChannel_badGroup() throws Exception {
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1.setGroup("garbage");
+ try {
+ mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ fail("Created a channel with a bad group");
+ } catch (IllegalArgumentException e) {}
+ }
+
+ @Test
+ public void testCannotCreateChannel_goodGroup() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannel(pkg, uid, channel1, true);
+
+ assertEquals(ncg.getId(),
+ mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false).getGroup());
+ }
+
+ @Test
+ public void testGetChannelGroups() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
+ mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ NotificationChannel channel1a =
+ new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1a.setGroup(ncg.getId());
+ mHelper.createNotificationChannel(pkg, uid, channel1a, true);
+
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setGroup(ncg2.getId());
+ mHelper.createNotificationChannel(pkg, uid, channel2, true);
+
+ NotificationChannel channel3 =
+ new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(pkg, uid, channel3, true);
+
+ List<NotificationChannelGroup> actual =
+ mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ assertEquals(3, actual.size());
+ for (NotificationChannelGroup group: actual) {
+ if (group.getId() == null) {
+ assertEquals(2, group.getChannels().size()); // misc channel too
+ assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
+ || channel3.getId().equals(group.getChannels().get(1).getId()));
+ } else if (group.getId().equals(ncg.getId())) {
+ assertEquals(2, group.getChannels().size());
+ if (group.getChannels().get(0).getId().equals(channel1.getId())) {
+ assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
+ } else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
+ assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
+ } else {
+ fail("expected channel not found");
+ }
+ } else if (group.getId().equals(ncg2.getId())) {
+ assertEquals(1, group.getChannels().size());
+ assertEquals(channel2.getId(), group.getChannels().get(0).getId());
+ }
+ }
+ }
}
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/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
new file mode 100644
index 0000000..c89d35c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+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.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IProgressListener;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.os.storage.IStorageManager;
+import android.security.KeyStore;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.test.AndroidTestCase;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.LockSettingsService.SynchronizedStrongAuthTracker;
+import com.android.server.LockSettingsStorage.CredentialHash;
+import com.android.server.MockGateKeeperService.AuthToken;
+import com.android.server.MockGateKeeperService.VerifyHandle;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+public class BaseLockSettingsServiceTests extends AndroidTestCase {
+ protected static final int PRIMARY_USER_ID = 0;
+ protected static final int MANAGED_PROFILE_USER_ID = 12;
+ protected static final int SECONDARY_USER_ID = 20;
+
+ private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+ private static final UserInfo MANAGED_PROFILE_INFO = new UserInfo(MANAGED_PROFILE_USER_ID, null,
+ null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
+ private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
+ UserInfo.FLAG_INITIALIZED);
+
+ LockSettingsService mService;
+
+ MockLockSettingsContext mContext;
+ LockSettingsStorageTestable mStorage;
+
+ LockPatternUtils mLockPatternUtils;
+ MockGateKeeperService mGateKeeperService;
+ NotificationManager mNotificationManager;
+ UserManager mUserManager;
+ MockStorageManager mStorageManager;
+ IActivityManager mActivityManager;
+
+ KeyStore mKeyStore;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mLockPatternUtils = mock(LockPatternUtils.class);
+ mGateKeeperService = new MockGateKeeperService();
+ mNotificationManager = mock(NotificationManager.class);
+ mUserManager = mock(UserManager.class);
+ mStorageManager = new MockStorageManager();
+ mActivityManager = mock(IActivityManager.class);
+ mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager);
+ mStorage = new LockSettingsStorageTestable(mContext,
+ new File(getContext().getFilesDir(), "locksettings"));
+ File storageDir = mStorage.mStorageDir;
+ if (storageDir.exists()) {
+ FileUtils.deleteContents(storageDir);
+ } else {
+ storageDir.mkdirs();
+ }
+
+ mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils,
+ mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager);
+ when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
+ when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(Arrays.asList(
+ new UserInfo[] {PRIMARY_USER_INFO, MANAGED_PROFILE_INFO}));
+ when(mUserManager.getUserInfo(eq(MANAGED_PROFILE_USER_ID))).thenReturn(
+ MANAGED_PROFILE_INFO);
+ when(mUserManager.getProfileParent(eq(MANAGED_PROFILE_USER_ID))).thenReturn(
+ PRIMARY_USER_INFO);
+ when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
+
+ when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ mStorageManager.unlockUser((int)args[0], (byte[])args[2],
+ (IProgressListener) args[3]);
+ return true;
+ }
+ });
+
+ when(mLockPatternUtils.getLockSettings()).thenReturn(mService);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mStorage.closeDatabase();
+ File db = getContext().getDatabasePath("locksettings.db");
+ assertTrue(!db.exists() || db.delete());
+
+ File storageDir = mStorage.mStorageDir;
+ assertTrue(FileUtils.deleteContents(storageDir));
+ }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
new file mode 100644
index 0000000..613ec0b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.storage.IStorageManager;
+import android.security.KeyStore;
+import android.service.gatekeeper.IGateKeeperService;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.io.FileNotFoundException;
+
+public class LockSettingsServiceTestable extends LockSettingsService {
+
+ private static class MockInjector extends LockSettingsService.Injector {
+
+ private LockSettingsStorage mLockSettingsStorage;
+ private KeyStore mKeyStore;
+ private IActivityManager mActivityManager;
+ private LockPatternUtils mLockPatternUtils;
+ private IStorageManager mStorageManager;
+
+ public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
+ IActivityManager activityManager, LockPatternUtils lockPatternUtils,
+ IStorageManager storageManager) {
+ super(context);
+ mLockSettingsStorage = storage;
+ mKeyStore = keyStore;
+ mActivityManager = activityManager;
+ mLockPatternUtils = lockPatternUtils;
+ mStorageManager = storageManager;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mock(Handler.class);
+ }
+
+ @Override
+ public LockSettingsStorage getStorage() {
+ return mLockSettingsStorage;
+ }
+
+ @Override
+ public LockSettingsStrongAuth getStrongAuth() {
+ return mock(LockSettingsStrongAuth.class);
+ }
+
+ @Override
+ public SynchronizedStrongAuthTracker getStrongAuthTracker() {
+ return mock(SynchronizedStrongAuthTracker.class);
+ }
+
+ @Override
+ public IActivityManager getActivityManager() {
+ return mActivityManager;
+ }
+
+ @Override
+ public LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
+ @Override
+ public KeyStore getKeyStore() {
+ return mKeyStore;
+ }
+
+ @Override
+ public IStorageManager getStorageManager() {
+ return mStorageManager;
+ }
+ }
+
+ protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
+ LockSettingsStorage storage, IGateKeeperService gatekeeper, KeyStore keystore,
+ IStorageManager storageManager, IActivityManager mActivityManager) {
+ super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
+ storageManager));
+ mGateKeeperService = gatekeeper;
+ }
+
+ @Override
+ protected void tieProfileLockToParent(int userId, String password) {
+ mStorage.writeChildProfileLock(userId, password.getBytes());
+ }
+
+ @Override
+ protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException {
+ byte[] storedData = mStorage.readChildProfileLock(userId);
+ if (storedData == null) {
+ throw new FileNotFoundException("Child profile lock file not found");
+ }
+ return new String(storedData);
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
new file mode 100644
index 0000000..4c2e171
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
@@ -0,0 +1,228 @@
+/*
+ * 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;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.LockSettingsStorage.CredentialHash;
+import com.android.server.MockGateKeeperService.VerifyHandle;
+
+/**
+ * runtest frameworks-services -c com.android.server.LockSettingsServiceTests
+ */
+public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testCreatePasswordPrimaryUser() throws RemoteException {
+ testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD);
+ }
+
+ public void testCreatePatternPrimaryUser() throws RemoteException {
+ testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN);
+ }
+
+ public void testChangePasswordPrimaryUser() throws RemoteException {
+ testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
+ "asdfghjk", CREDENTIAL_TYPE_PASSWORD);
+ }
+
+ public void testChangePatternPrimaryUser() throws RemoteException {
+ testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
+ "1596321", CREDENTIAL_TYPE_PATTERN);
+ }
+
+ public void testChangePasswordFailPrimaryUser() throws RemoteException {
+ final long sid = 1234;
+ final String FAILED_MESSAGE = "Failed to enroll password";
+ initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+
+ try {
+ mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, "badpwd",
+ PRIMARY_USER_ID);
+ fail("Did not fail when enrolling using incorrect credential");
+ } catch (RemoteException expected) {
+ assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+ }
+ try {
+ mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ fail("Did not fail when enrolling using incorrect credential");
+ } catch (RemoteException expected) {
+ assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+ }
+ assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+ }
+
+ public void testClearPasswordPrimaryUser() throws RemoteException {
+ final String PASSWORD = "password";
+ initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
+ mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD, PRIMARY_USER_ID);
+ assertFalse(mService.havePassword(PRIMARY_USER_ID));
+ assertFalse(mService.havePattern(PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testManagedProfileUnifiedChallenge() throws RemoteException {
+ final String UnifiedPassword = "testManagedProfileUnifiedChallenge-pwd";
+ mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PRIMARY_USER_ID);
+ mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // clear auth token and wait for verify challenge from primary user to re-generate it.
+ mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+ // verify credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ .getResponseCode());
+
+ // Verify that we have a new auth token for the profile
+ assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+ * credential as part of verifyCredential() before the new credential is committed in
+ * StorageManager. So we relax the check in our mock StorageManager to allow that.
+ */
+ mStorageManager.setIgnoreBadUnlock(true);
+ // Change primary password and verify that profile SID remains
+ mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ UnifiedPassword, PRIMARY_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ }
+
+ public void testManagedProfileSeparateChallenge() throws RemoteException {
+ final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
+ final String profilePassword = "testManagedProfileSeparateChallenge-profile";
+ mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PRIMARY_USER_ID);
+ /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+ * credential as part of verifyCredential() before the new credential is committed in
+ * StorageManager. So we relax the check in our mock StorageManager to allow that.
+ */
+ mStorageManager.setIgnoreBadUnlock(true);
+ mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ MANAGED_PROFILE_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // clear auth token and make sure verify challenge from primary user does not regenerate it.
+ mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+ // verify primary credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ .getResponseCode());
+ assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+
+ // verify profile credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ // Change primary credential and make sure we don't affect profile
+ mStorageManager.setIgnoreBadUnlock(true);
+ mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ primaryPassword, PRIMARY_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ }
+
+ private void testCreateCredential(int userId, String credential, int type)
+ throws RemoteException {
+ mService.setLockCredential(credential, type, null, userId);
+ assertVerifyCredentials(userId, credential, type, -1);
+ }
+
+ private void testChangeCredentials(int userId, String newCredential, int newType,
+ String oldCredential, int oldType) throws RemoteException {
+ final long sid = 1234;
+ initializeStorageWithCredential(userId, oldCredential, oldType, sid);
+ mService.setLockCredential(newCredential, newType, oldCredential, userId);
+ assertVerifyCredentials(userId, newCredential, newType, sid);
+ }
+
+ private void assertVerifyCredentials(int userId, String credential, int type, long sid)
+ throws RemoteException{
+ final long challenge = 54321;
+ VerifyCredentialResponse response = mService.verifyCredential(credential, type, challenge,
+ userId);
+
+ assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
+ if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
+ final int incorrectType;
+ if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ assertTrue(mService.havePassword(userId));
+ assertFalse(mService.havePattern(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+ } else if (type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN){
+ assertFalse(mService.havePassword(userId));
+ assertTrue(mService.havePattern(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ } else {
+ assertFalse(mService.havePassword(userId));
+ assertFalse(mService.havePassword(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ }
+ // check for bad type
+ assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(credential,
+ incorrectType, challenge, userId).getResponseCode());
+ // check for bad credential
+ assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential("0" + credential,
+ type, challenge, userId).getResponseCode());
+ }
+
+ private void initializeStorageWithCredential(int userId, String credential, int type, long sid) {
+ byte[] oldHash = new VerifyHandle(credential.getBytes(), sid).toBytes();
+ if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
+ } else {
+ mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
new file mode 100644
index 0000000..e81b02f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.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 com.android.server;
+
+import android.content.Context;
+
+import java.io.File;
+
+public class LockSettingsStorageTestable extends LockSettingsStorage {
+
+ public File mStorageDir;
+
+ public LockSettingsStorageTestable(Context context, File storageDir) {
+ super(context);
+ mStorageDir = storageDir;
+ }
+
+ @Override
+ String getLockPatternFilename(int userId) {
+ return new File(mStorageDir,
+ super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath();
+ }
+
+ @Override
+ String getLockPasswordFilename(int userId) {
+ return new File(mStorageDir,
+ super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath();
+ }
+
+ @Override
+ String getChildProfileLockFile(int userId) {
+ return new File(mStorageDir,
+ super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
index 9d52153..d110fea 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.UserInfo;
@@ -60,46 +61,22 @@
assertTrue(FileUtils.deleteContents(mStorageDir));
assertTrue(!mDb.exists() || mDb.delete());
- final Context ctx = getContext();
final UserManager mockUserManager = mock(UserManager.class);
// User 2 is a profile of user 1.
when(mockUserManager.getProfileParent(eq(2))).thenReturn(new UserInfo(1, "name", 0));
// User 3 is a profile of user 0.
when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
- setContext(new ContextWrapper(ctx) {
- @Override
- public Object getSystemService(String name) {
- if (USER_SERVICE.equals(name)) {
- return mockUserManager;
- }
- return super.getSystemService(name);
- }
- });
- mStorage = new LockSettingsStorage(getContext(), new LockSettingsStorage.Callback() {
- @Override
- public void initialize(SQLiteDatabase db) {
- mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0);
- }
- }) {
- @Override
- String getLockPatternFilename(int userId) {
- return new File(mStorageDir,
- super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath();
- }
-
- @Override
- String getLockPasswordFilename(int userId) {
- return new File(mStorageDir,
- super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath();
- }
-
- @Override
- String getChildProfileLockFile(int userId) {
- return new File(mStorageDir,
- super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath();
- }
- };
+ MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
+ mock(NotificationManager.class));
+ mStorage = new LockSettingsStorageTestable(context,
+ new File(getContext().getFilesDir(), "locksettings"));
+ mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
+ @Override
+ public void initialize(SQLiteDatabase db) {
+ mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0);
+ }
+ });
}
@Override
@@ -323,7 +300,7 @@
}
public void testFileLocation_Owner() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
assertEquals("/data/system/gesture.key", storage.getLegacyLockPatternFilename(0));
assertEquals("/data/system/password.key", storage.getLegacyLockPasswordFilename(0));
@@ -332,21 +309,21 @@
}
public void testFileLocation_SecondaryUser() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1));
assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1));
}
public void testFileLocation_ProfileToSecondary() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2));
assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2));
}
public void testFileLocation_ProfileToOwner() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3));
assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
diff --git a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
new file mode 100644
index 0000000..15983ca
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
@@ -0,0 +1,185 @@
+/*
+ * 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.os.IBinder;
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.util.ArrayMap;
+
+import junit.framework.AssertionFailedError;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+public class MockGateKeeperService implements IGateKeeperService {
+ static class VerifyHandle {
+ public byte[] password;
+ public long sid;
+
+ public VerifyHandle(byte[] password, long sid) {
+ this.password = password;
+ this.sid = sid;
+ }
+
+ public VerifyHandle(byte[] handle) {
+ ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+ buffer.put(handle, 0, handle.length);
+ buffer.flip();
+ int version = buffer.get();
+ sid = buffer.getLong();
+ password = new byte[buffer.remaining()];
+ buffer.get(password);
+ }
+
+ public byte[] toBytes() {
+ ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + password.length);
+ buffer.put((byte)0);
+ buffer.putLong(sid);
+ buffer.put(password);
+ return buffer.array();
+ }
+ }
+
+ static class AuthToken {
+ public long challenge;
+ public long sid;
+
+ public AuthToken(long challenge, long sid) {
+ this.challenge = challenge;
+ this.sid = sid;
+ }
+
+ public AuthToken(byte[] handle) {
+ ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+ buffer.put(handle, 0, handle.length);
+ buffer.flip();
+ int version = buffer.get();
+ challenge = buffer.getLong();
+ sid = buffer.getLong();
+ }
+
+ public byte[] toBytes() {
+ ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + Long.BYTES);
+ buffer.put((byte)0);
+ buffer.putLong(challenge);
+ buffer.putLong(sid);
+ return buffer.array();
+ }
+ }
+
+ private ArrayMap<Integer, Long> sidMap = new ArrayMap<>();
+ private ArrayMap<Integer, AuthToken> authTokenMap = new ArrayMap<>();
+
+ private ArrayMap<Integer, byte[]> handleMap = new ArrayMap<>();
+
+ @Override
+ public GateKeeperResponse enroll(int uid, byte[] currentPasswordHandle, byte[] currentPassword,
+ byte[] desiredPassword) throws android.os.RemoteException {
+
+ if (currentPasswordHandle != null) {
+ VerifyHandle handle = new VerifyHandle(currentPasswordHandle);
+ if (Arrays.equals(currentPassword, handle.password)) {
+ // Trusted enroll
+ VerifyHandle newHandle = new VerifyHandle(desiredPassword, handle.sid);
+ refreshSid(uid, handle.sid, false);
+ handleMap.put(uid, newHandle.toBytes());
+ return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+ } else {
+ return null;
+ }
+ } else {
+ // Untrusted enroll
+ long newSid = new Random().nextLong();
+ VerifyHandle newHandle = new VerifyHandle(desiredPassword, newSid);
+ refreshSid(uid, newSid, true);
+ handleMap.put(uid, newHandle.toBytes());
+ return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+ }
+ }
+
+ @Override
+ public GateKeeperResponse verify(int uid, byte[] enrolledPasswordHandle,
+ byte[] providedPassword) throws android.os.RemoteException {
+ return verifyChallenge(uid, 0, enrolledPasswordHandle, providedPassword);
+ }
+
+ @Override
+ public GateKeeperResponse verifyChallenge(int uid, long challenge,
+ byte[] enrolledPasswordHandle, byte[] providedPassword) throws RemoteException {
+
+ VerifyHandle handle = new VerifyHandle(enrolledPasswordHandle);
+ if (Arrays.equals(handle.password, providedPassword)) {
+ byte[] knownHandle = handleMap.get(uid);
+ if (knownHandle != null) {
+ if (!Arrays.equals(knownHandle, enrolledPasswordHandle)) {
+ throw new AssertionFailedError("Got correct but obsolete handle");
+ }
+ }
+ refreshSid(uid, handle.sid, false);
+ AuthToken token = new AuthToken(challenge, handle.sid);
+ refreshAuthToken(uid, token);
+ return GateKeeperResponse.createOkResponse(token.toBytes(), false);
+ } else {
+ return GateKeeperResponse.createGenericResponse(GateKeeperResponse.RESPONSE_ERROR);
+ }
+ }
+
+ private void refreshAuthToken(int uid, AuthToken token) {
+ authTokenMap.put(uid, token);
+ }
+
+ public AuthToken getAuthToken(int uid) {
+ return authTokenMap.get(uid);
+ }
+
+ public void clearAuthToken(int uid) {
+ authTokenMap.remove(uid);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearSecureUserId(int userId) throws RemoteException {
+ sidMap.remove(userId);
+ }
+
+ @Override
+ public long getSecureUserId(int userId) throws RemoteException {
+ if (sidMap.containsKey(userId)) {
+ return sidMap.get(userId);
+ } else {
+ return 0L;
+ }
+ }
+
+ private void refreshSid(int uid, long sid, boolean force) {
+ if (!sidMap.containsKey(uid) || force) {
+ sidMap.put(uid, sid);
+ } else{
+ if (sidMap.get(uid) != sid) {
+ throw new AssertionFailedError("Inconsistent SID");
+ }
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java
new file mode 100644
index 0000000..b63936f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java
@@ -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
+ */
+
+package com.android.server;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.UserManager;
+
+public class MockLockSettingsContext extends ContextWrapper {
+
+ private UserManager mUserManager;
+ private NotificationManager mNotificationManager;
+
+ public MockLockSettingsContext(Context base, UserManager userManager,
+ NotificationManager notificationManager) {
+ super(base);
+ mUserManager = userManager;
+ mNotificationManager = notificationManager;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (USER_SERVICE.equals(name)) {
+ return mUserManager;
+ } else if (NOTIFICATION_SERVICE.equals(name)) {
+ return mNotificationManager;
+ } else {
+ throw new RuntimeException("System service not mocked: " + name);
+ }
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ // Skip permission checks for unit tests.
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/MockStorageManager.java b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
new file mode 100644
index 0000000..031a3b3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
@@ -0,0 +1,493 @@
+/*
+ * 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.pm.IPackageMoveObserver;
+import android.os.IBinder;
+import android.os.IProgressListener;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.storage.DiskInfo;
+import android.os.storage.IObbActionListener;
+import android.os.storage.IStorageEventListener;
+import android.os.storage.IStorageManager;
+import android.os.storage.IStorageShutdownObserver;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+import android.os.storage.VolumeRecord;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import com.android.internal.os.AppFuseMount;
+
+import junit.framework.AssertionFailedError;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockStorageManager implements IStorageManager {
+
+ private ArrayMap<Integer, ArrayList<Pair<byte[], byte[]>>> mAuth = new ArrayMap<>();
+ private boolean mIgnoreBadUnlock;
+
+ @Override
+ public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret)
+ throws RemoteException {
+ getUserAuth(userId).add(new Pair<>(token, secret));
+ }
+
+ @Override
+ public void fixateNewestUserKeyAuth(int userId) throws RemoteException {
+ ArrayList<Pair<byte[], byte[]>> auths = mAuth.get(userId);
+ Pair<byte[], byte[]> latest = auths.get(auths.size() - 1);
+ auths.clear();
+ auths.add(latest);
+ }
+
+ private ArrayList<Pair<byte[], byte[]>> getUserAuth(int userId) {
+ if (!mAuth.containsKey(userId)) {
+ ArrayList<Pair<byte[], byte[]>> auths = new ArrayList<Pair<byte[], byte[]>>();
+ auths.add(new Pair(null, null));
+ mAuth.put(userId, auths);
+ }
+ return mAuth.get(userId);
+ }
+
+ public byte[] getUserUnlockToken(int userId) {
+ ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+ if (auths.size() != 1) {
+ throw new AssertionFailedError("More than one secret exists");
+ }
+ return auths.get(0).second;
+ }
+
+ public void unlockUser(int userId, byte[] secret, IProgressListener listener)
+ throws RemoteException {
+ listener.onStarted(userId, null);
+ listener.onFinished(userId, null);
+ ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+ if (secret != null) {
+ if (auths.size() > 1) {
+ throw new AssertionFailedError("More than one secret exists");
+ }
+ Pair<byte[], byte[]> auth = auths.get(0);
+ if ((!mIgnoreBadUnlock) && auth.second != null && !Arrays.equals(secret, auth.second)) {
+ throw new AssertionFailedError("Invalid secret to unlock user");
+ }
+ } else {
+ if (auths != null && auths.size() > 0) {
+ throw new AssertionFailedError("Cannot unlock encrypted user with empty token");
+ }
+ }
+ }
+
+ public void setIgnoreBadUnlock(boolean ignore) {
+ mIgnoreBadUnlock = ignore;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void registerListener(IStorageEventListener listener) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterListener(IStorageEventListener listener) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUsbMassStorageConnected() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setUsbMassStorageEnabled(boolean enable) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUsbMassStorageEnabled() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mountVolume(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmountVolume(String mountPoint, boolean force, boolean removeEncryption)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public int formatVolume(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int[] getStorageUsers(String path) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getVolumeState(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid,
+ boolean external) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int finalizeSecureContainer(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int destroySecureContainer(String id, boolean force) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int unmountSecureContainer(String id, boolean force) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isSecureContainerMounted(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int renameSecureContainer(String oldId, String newId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSecureContainerPath(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String[] getSecureContainerList() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdown(IStorageShutdownObserver observer) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void finishMediaUpdate() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void mountObb(String rawPath, String canonicalPath, String key, IObbActionListener token,
+ int nonce) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isObbMounted(String rawPath) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getMountedObbPath(String rawPath) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isExternalStorageEmulated() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int decryptStorage(String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int encryptStorage(int type, String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int changeEncryptionPassword(int type, String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public StorageVolume[] getVolumeList(int uid, String packageName, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSecureContainerFilesystemPath(String cid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getEncryptionState() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int verifyEncryptionPassword(String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int fixPermissionsSecureContainer(String id, int gid, String filename)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mkdirs(String callingPkg, String path) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getPasswordType() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPassword() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearPassword() throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void setField(String field, String contents) throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public String getField(String field) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int resizeSecureContainer(String id, int sizeMb, String key) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long lastMaintenance() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void runMaintenance() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void waitForAsecScan() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DiskInfo[] getDisks() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public VolumeInfo[] getVolumes(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public VolumeRecord[] getVolumeRecords(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void mount(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmount(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void format(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionPublic(String diskId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionPrivate(String diskId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionMixed(String diskId, int ratio) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void forgetVolume(String fsUuid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void forgetAllVolumes() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPrimaryStorageUuid() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long benchmark(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDebugFlags(int flags, int mask) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void createUserKey(int userId, int serialNumber, boolean ephemeral)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void destroyUserKey(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void lockUserKey(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUserKeyUnlocked(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void destroyUserStorage(String volumeUuid, int userId, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isConvertibleToFBE() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void fstrim(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getCacheQuotaBytes(String volumeUuid, int uid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getCacheSizeBytes(String volumeUuid, int uid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 43c8957..152b9c9 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.UnaryOperator;
/**
* 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 UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
+ @Mock private UnaryOperator<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..213fb27 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);
}
@@ -364,8 +380,7 @@
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));
+ // IllegalArgumentException is expected.
}
}
@@ -382,8 +397,7 @@
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));
+ // IllegalArgumentException is expected.
}
}
@@ -500,7 +514,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserSuccessWithoutPasswordForwarding() throws Exception {
+ public void testStartAddAccountSessionSuccessWithoutPasswordForwarding() throws Exception {
unlockSystemUser();
when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
PackageManager.PERMISSION_DENIED);
@@ -531,7 +545,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserSuccessWithPasswordForwarding() throws Exception {
+ public void testStartAddAccountSessionSuccessWithPasswordForwarding() throws Exception {
unlockSystemUser();
when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
PackageManager.PERMISSION_GRANTED);
@@ -564,7 +578,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserReturnWithInvalidIntent() throws Exception {
+ public void testStartAddAccountSessionReturnWithInvalidIntent() throws Exception {
unlockSystemUser();
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = new ActivityInfo();
@@ -593,7 +607,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserReturnWithValidIntent() throws Exception {
+ public void testStartAddAccountSessionReturnWithValidIntent() throws Exception {
unlockSystemUser();
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = new ActivityInfo();
@@ -626,7 +640,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserError() throws Exception {
+ public void testStartAddAccountSessionError() throws Exception {
unlockSystemUser();
Bundle options = createOptionsWithAccountName(
AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR);
@@ -650,14 +664,1858 @@
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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // SecurityException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // SecurityException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @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) {
+ // SecurityException is expected.
+ }
+ }
+
+ @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));
+ }
+
+ @SmallTest
+ public void testGetAccountsByFeaturesWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.getAccountsByFeatures(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+ "testpackage"); // opPackageName
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @SmallTest
+ public void testGetAccountsByFeaturesWithNullAccountType() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.getAccountsByFeatures(
+ mMockAccountManagerResponse, // response
+ null, // accountType
+ AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+ "testpackage"); // opPackageName
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ @SmallTest
+ public void testGetAccountsByFeaturesAccountNotVisible() throws Exception {
+ unlockSystemUser();
+
+ when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+ .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAccountsByFeatures(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+ "testpackage"); // opPackageName
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Account[] accounts = (Account[]) result.getParcelableArray(AccountManager.KEY_ACCOUNTS);
+ assertTrue(accounts.length == 0);
+ }
+
+ @SmallTest
+ public void testGetAccountsByFeaturesNullFeatureReturnsAllAccounts() throws Exception {
+ unlockSystemUser();
+
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, "p12", null);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAccountsByFeatures(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ null, // features
+ "testpackage"); // opPackageName
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Account[] accounts = (Account[]) result.getParcelableArray(AccountManager.KEY_ACCOUNTS);
+ Arrays.sort(accounts, new AccountSorter());
+ assertEquals(2, accounts.length);
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, accounts[0]);
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, accounts[1]);
+ }
+
+ @SmallTest
+ public void testGetAccountsByFeaturesReturnsAccountsWithFeaturesOnly() throws Exception {
+ unlockSystemUser();
+
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, "p12", null);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAccountsByFeatures(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+ "testpackage"); // opPackageName
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Account[] accounts = (Account[]) result.getParcelableArray(AccountManager.KEY_ACCOUNTS);
+ assertEquals(1, accounts.length);
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, accounts[0]);
+ }
+
+ @SmallTest
+ public void testGetAccountsByFeaturesError() throws Exception {
+ unlockSystemUser();
+
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_ERROR, "p12", null);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.getAccountsByFeatures(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+ "testpackage"); // opPackageName
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), anyString());
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
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 +2627,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 +2648,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 +2688,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/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index 8591dae..2e045ff 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -374,7 +374,7 @@
mAccountsDb.setAccountVisibility(accId, uid2, 3);
assertEquals(mAccountsDb.findAccountVisibility(accId, uid2), Integer.valueOf(3));
- Map<Integer, Integer> vis = mAccountsDb.findAccountVisibilityForAccountId(accId);
+ Map<Integer, Integer> vis = mAccountsDb.findAllVisibilityValuesForAccount(account);
assertEquals(vis.size(), 2);
assertEquals(vis.get(uid1), Integer.valueOf(1));
assertEquals(vis.get(uid2), Integer.valueOf(3));
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..eb839a2 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,20 @@
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 true.
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+ } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+ // fill bundle with false.
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ } else {
+ // return null for error
+ result = null;
+ }
+
+ response.onResult(result);
+ return null;
}
@Override
@@ -197,7 +336,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 +383,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 +430,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/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 677e468..d7bfc44 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -36,6 +36,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutServiceInternal;
import android.os.Handler;
import android.os.UserHandle;
@@ -121,6 +122,18 @@
assertEquals(provider, providerCaptor.getValue().provider);
}
+ public void testIsRequestPinAppWidgetSupported() {
+ ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
+ // Set up users.
+ when(mMockShortcutService.isRequestPinItemSupported(anyInt(), anyInt()))
+ .thenReturn(true, false);
+ assertTrue(mManager.isRequestPinAppWidgetSupported());
+ assertFalse(mManager.isRequestPinAppWidgetSupported());
+
+ verify(mMockShortcutService, times(2)).isRequestPinItemSupported(anyInt(),
+ eq(LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET));
+ }
+
public void testProviderUpdatesReceived() throws Exception {
int widgetId = setupHostAndWidget();
RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 4927f0c..3b92a34 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -37,6 +37,7 @@
import com.android.internal.widget.LockPatternUtils;
import java.io.File;
+import java.io.IOException;
import java.util.Map;
/**
@@ -264,6 +265,12 @@
}
@Override
+ void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
+ throws IOException {
+ context.recoverySystem.rebootWipeUserData(shutdown, reason, force);
+ }
+
+ @Override
boolean systemPropertiesGetBoolean(String key, boolean def) {
return context.systemProperties.getBoolean(key, def);
}
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 8da47c8..f4e4e08 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;
@@ -82,6 +85,7 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
/**
@@ -1128,6 +1132,7 @@
public void testSetGetApplicationRestriction() {
setAsProfileOwner(admin1);
+ mContext.packageName = admin1.getPackageName();
{
Bundle rest = new Bundle();
@@ -1159,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");
@@ -1190,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
@@ -1221,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");
@@ -1236,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());
@@ -1250,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());
}
}
@@ -1835,6 +1950,81 @@
}
}
+ public void testCreateAdminSupportIntent() throws Exception {
+ // Setup device owner.
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+
+ // Nonexisting permission returns null
+ Intent intent = dpm.createAdminSupportIntent("disallow_nothing");
+ assertNull(intent);
+
+ // Existing permission that is not set returns null
+ intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+ assertNull(intent);
+
+ // Existing permission that is not set by device/profile owner returns null
+ when(mContext.userManager.hasUserRestriction(
+ eq(UserManager.DISALLOW_ADJUST_VOLUME),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(true);
+ intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+ assertNull(intent);
+
+ // Permission that is set by device owner returns correct intent
+ when(mContext.userManager.getUserRestrictionSource(
+ eq(UserManager.DISALLOW_ADJUST_VOLUME),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+ intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+ assertNotNull(intent);
+ assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, intent.getAction());
+ assertEquals(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID),
+ intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
+ assertEquals(admin1,
+ (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN));
+ assertEquals(UserManager.DISALLOW_ADJUST_VOLUME,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+ // Try with POLICY_DISABLE_CAMERA and POLICY_DISABLE_SCREEN_CAPTURE, which are not
+ // user restrictions
+
+ // Camera is not disabled
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_CAMERA);
+ assertNull(intent);
+
+ // Camera is disabled
+ dpm.setCameraDisabled(admin1, true);
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_CAMERA);
+ assertNotNull(intent);
+ assertEquals(DevicePolicyManager.POLICY_DISABLE_CAMERA,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+ // Screen capture is not disabled
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ assertNull(intent);
+
+ // Screen capture is disabled
+ dpm.setScreenCaptureDisabled(admin1, true);
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ assertNotNull(intent);
+ assertEquals(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+ // Same checks for different user
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ // Camera should be disabled by device owner
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_CAMERA);
+ assertNotNull(intent);
+ assertEquals(DevicePolicyManager.POLICY_DISABLE_CAMERA,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+ assertEquals(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID),
+ intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
+ // ScreenCapture should not be disabled by device owner
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ assertNull(intent);
+ }
+
/**
* Test for:
* {@link DevicePolicyManager#setAffiliationIds}
@@ -2358,6 +2548,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();
@@ -2387,144 +2594,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 {
@@ -3233,6 +3420,140 @@
}
}
+ public void testWipeDataDeviceOwner() throws Exception {
+ setDeviceOwner();
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+ dpm.wipeData(0);
+ verify(mContext.recoverySystem).rebootWipeUserData(
+ /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
+ }
+
+ public void testWipeDataDeviceOwnerDisallowed() throws Exception {
+ setDeviceOwner();
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+ try {
+ // The DO is not allowed to wipe the device if the user restriction was set
+ // by the system
+ dpm.wipeData(0);
+ fail("SecurityException not thrown");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ public void testMaximumFailedPasswordAttemptsReachedManagedProfile() throws Exception {
+ final int MANAGED_PROFILE_USER_ID = 15;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+ // Even if the caller is the managed profile, the current user is the user 0
+ when(mContext.iactivityManager.getCurrentUser())
+ .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ UserHandle.of(MANAGED_PROFILE_USER_ID)))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_PROFILE_OWNER);
+
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+ // Failed password attempts on the parent user are taken into account, as there isn't a
+ // separate work challenge.
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+ // The profile should be wiped even if DISALLOW_REMOVE_MANAGED_PROFILE is enabled, because
+ // both the user restriction and the policy were set by the PO.
+ verify(mContext.userManagerInternal).removeUserEvenWhenDisallowed(
+ MANAGED_PROFILE_USER_ID);
+ verifyZeroInteractions(mContext.recoverySystem);
+ }
+
+ public void testMaximumFailedPasswordAttemptsReachedManagedProfileDisallowed()
+ throws Exception {
+ final int MANAGED_PROFILE_USER_ID = 15;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+ // Even if the caller is the managed profile, the current user is the user 0
+ when(mContext.iactivityManager.getCurrentUser())
+ .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ UserHandle.of(MANAGED_PROFILE_USER_ID)))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+ // Failed password attempts on the parent user are taken into account, as there isn't a
+ // separate work challenge.
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+ // DISALLOW_REMOVE_MANAGED_PROFILE was set by the system, not the PO, so the profile is
+ // not wiped.
+ verify(mContext.userManagerInternal, never())
+ .removeUserEvenWhenDisallowed(anyInt());
+ verifyZeroInteractions(mContext.recoverySystem);
+ }
+
+ public void testMaximumFailedPasswordAttemptsReachedDeviceOwner() throws Exception {
+ setDeviceOwner();
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+ dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+ // The device should be wiped even if DISALLOW_FACTORY_RESET is enabled, because both the
+ // user restriction and the policy were set by the DO.
+ verify(mContext.recoverySystem).rebootWipeUserData(
+ /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
+ }
+
+ public void testMaximumFailedPasswordAttemptsReachedDeviceOwnerDisallowed() throws Exception {
+ setDeviceOwner();
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+
+ dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+ // DISALLOW_FACTORY_RESET was set by the system, not the DO, so the device is not wiped.
+ verifyZeroInteractions(mContext.recoverySystem);
+ verify(mContext.userManagerInternal, never())
+ .removeUserEvenWhenDisallowed(anyInt());
+ }
+
public void testGetPermissionGrantState() throws Exception {
final String permission = "some.permission";
final String app1 = "com.example.app1";
@@ -3254,20 +3575,24 @@
// System can retrieve permission grant state.
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.packageName = "com.example.system";
assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
dpm.getPermissionGrantState(null, app1, permission));
assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT,
dpm.getPermissionGrantState(null, app2, permission));
// A regular app cannot retrieve permission grant state.
- mMockContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.binder.callingUid = setupPackageInPackageManager(app1, 1);
+ mContext.packageName = app1;
try {
dpm.getPermissionGrantState(null, app1, permission);
- fail("Didn't throw IllegalStateException");
- } catch (IllegalStateException expected) {
+ fail("Didn't throw SecurityException");
+ } catch (SecurityException expected) {
}
// Profile owner can retrieve permission grant state.
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
setAsProfileOwner(admin1);
assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
dpm.getPermissionGrantState(admin1, app1, permission));
@@ -3287,6 +3612,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 44bf547..22cd135 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -55,6 +55,7 @@
import org.mockito.stubbing.Answer;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -99,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;
@@ -154,6 +159,12 @@
}
}
+ public static class RecoverySystemForMock {
+ public void rebootWipeUserData(
+ boolean shutdown, String reason, boolean force) throws IOException {
+ }
+ }
+
public static class SystemPropertiesForMock {
public boolean getBoolean(String key, boolean def) {
return false;
@@ -263,6 +274,7 @@
public final UserManagerForMock userManagerForMock;
public final PowerManagerForMock powerManager;
public final PowerManagerInternal powerManagerInternal;
+ public final RecoverySystemForMock recoverySystem;
public final NotificationManager notificationManager;
public final IIpConnectivityMetrics iipConnectivityMetrics;
public final IWindowManager iwindowManager;
@@ -308,6 +320,7 @@
packageManagerInternal = mock(PackageManagerInternal.class);
powerManager = mock(PowerManagerForMock.class);
powerManagerInternal = mock(PowerManagerInternal.class);
+ recoverySystem = mock(RecoverySystemForMock.class);
notificationManager = mock(NotificationManager.class);
iipConnectivityMetrics = mock(IIpConnectivityMetrics.class);
iwindowManager = mock(IWindowManager.class);
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 228b8e0..8c23a91 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -398,7 +398,7 @@
@Override
ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName,
- int launcherUserId) {
+ int launcherUserId, int requestType) {
return mPinConfirmActivityFetcher.apply(launcherPackageName, launcherUserId);
}
@@ -566,6 +566,7 @@
protected Map<String, PackageInfo> mInjectedPackages;
protected Set<PackageWithUser> mUninstalledPackages;
+ protected Set<PackageWithUser> mEphemeralPackages;
protected Set<String> mSystemPackages;
protected PackageManager mMockPackageManager;
@@ -618,9 +619,16 @@
protected static final UserInfo USER_INFO_11 =
new UserInfo(USER_11, "user11", UserInfo.FLAG_INITIALIZED);
+ /*
+ * Cheat: USER_P0 is a sub profile of USER_0, but it doesn't have the MANAGED_PROFILE flag set.
+ * Due to a change made to LauncherApps (b/34340531), work profile apps a no longer able
+ * to see the main profile, which would break tons of unit tests. We avoid it by not setting
+ * MANAGED_PROFILE for P0.
+ * We cover this negative case in CTS. (i.e. CTS has tests to make sure maanged profile
+ * can't access main profile's shortcuts.)
+ */
protected static final UserInfo USER_INFO_P0 = withProfileGroupId(
- new UserInfo(USER_P0, "userP0",
- UserInfo.FLAG_MANAGED_PROFILE), 0);
+ new UserInfo(USER_P0, "userP0", UserInfo.FLAG_INITIALIZED), 0);
protected BiPredicate<String, Integer> mDefaultLauncherChecker =
(callingPackage, userId) ->
@@ -724,6 +732,7 @@
mUninstalledPackages = new HashSet<>();
mSystemPackages = new HashSet<>();
+ mEphemeralPackages = new HashSet<>();
mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
@@ -1027,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/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
index 64c5622..fec3267 100644
--- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
@@ -39,7 +39,8 @@
public PackageSetting generateFakePackageSetting(String name) {
return new PackageSetting(name, name, new File(mContext.getCacheDir(), "fakeCodePath"),
new File(mContext.getCacheDir(), "fakeResPath"), "", "", "",
- "", 1, 0, 0, null, null, 0 /*sharedUserId*/);
+ "", 1, 0, 0, null, null, 0 /*sharedUserId*/, null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
}
@Override
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/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index bd2bb6c..baf60c5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -216,7 +216,9 @@
ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
PARENT_PACKAGE_NAME,
childPackageNames,
- 0);
+ 0,
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
}
@@ -241,7 +243,9 @@
ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
PARENT_PACKAGE_NAME,
childPackageNames,
- 0);
+ 0,
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
final PackageSetting testPkgSetting01 = new PackageSetting(
PACKAGE_NAME /*pkgName*/,
REAL_PACKAGE_NAME /*realPkgName*/,
@@ -256,7 +260,9 @@
0 /*pkgPrivateFlags*/,
null /*parentPkgName*/,
null /*childPkgNames*/,
- 0);
+ 0,
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
testPkgSetting01.copyFrom(origPkgSetting01);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
}
@@ -281,7 +287,9 @@
0 /*pkgFlags*/,
0 /*pkgPrivateFlags*/,
null /*childPkgNames*/,
- UserManagerService.getInstance());
+ UserManagerService.getInstance(),
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
assertThat(testPkgSetting01.origPackage, is(nullValue()));
@@ -313,7 +321,9 @@
ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
null /*childPkgNames*/,
- UserManagerService.getInstance());
+ UserManagerService.getInstance(),
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
assertThat(testPkgSetting01.origPackage, is(nullValue()));
@@ -348,7 +358,9 @@
0 /*pkgFlags*/,
0 /*pkgPrivateFlags*/,
null /*childPkgNames*/,
- UserManagerService.getInstance());
+ UserManagerService.getInstance(),
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
fail("Expected a PackageManagerException");
} catch (PackageManagerException expected) {
}
@@ -378,7 +390,9 @@
false /*allowInstall*/,
null /*parentPkgName*/,
null /*childPkgNames*/,
- UserManagerService.getInstance());
+ UserManagerService.getInstance(),
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
assertThat(testPkgSetting01.codePath, is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
@@ -416,7 +430,9 @@
true /*allowInstall*/,
null /*parentPkgName*/,
null /*childPkgNames*/,
- UserManagerService.getInstance());
+ UserManagerService.getInstance(),
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
assertThat(testPkgSetting01.appId, is(0));
assertThat(testPkgSetting01.codePath, is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -457,7 +473,9 @@
false /*allowInstall*/,
null /*parentPkgName*/,
null /*childPkgNames*/,
- UserManagerService.getInstance());
+ UserManagerService.getInstance(),
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
assertThat(testPkgSetting01.appId, is(10064));
assertThat(testPkgSetting01.codePath, is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -498,7 +516,9 @@
false /*allowInstall*/,
null /*parentPkgName*/,
null /*childPkgNames*/,
- UserManagerService.getInstance());
+ UserManagerService.getInstance(),
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
assertThat(testPkgSetting01.appId, is(10064));
assertThat(testPkgSetting01.codePath, is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -624,7 +644,9 @@
0 /*privateFlags*/,
null /*parentPackageName*/,
null /*childPackageNames*/,
- sharedUserId);
+ sharedUserId,
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/);
}
private @NonNull List<UserInfo> createFakeUsers() {
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/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
index df275d2..0310e16 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
@@ -22,6 +22,9 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Matchers.notNull;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -90,21 +93,24 @@
Pair<ComponentName, Integer> actual;
// User 0
- actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_0);
+ actual = mProcessor.getRequestPinConfirmationActivity(USER_0,
+ PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_1, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_0, (int) actual.second);
// User 10
- actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10);
+ actual = mProcessor.getRequestPinConfirmationActivity(USER_10,
+ PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_2, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_10, (int) actual.second);
// User P0 -> managed profile, return user-0's launcher.
- actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0);
+ actual = mProcessor.getRequestPinConfirmationActivity(USER_P0,
+ PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_1, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
@@ -130,15 +136,18 @@
? null : new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS);
// User 10 -- still has confirm activity.
- actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10);
+ actual = mProcessor.getRequestPinConfirmationActivity(USER_10,
+ PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_2, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_10, (int) actual.second);
// But user-0 and user p0 no longer has a confirmation activity.
- assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_0));
- assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0));
+ assertNull(mProcessor.getRequestPinConfirmationActivity(USER_0,
+ PinItemRequest.REQUEST_TYPE_SHORTCUT));
+ assertNull(mProcessor.getRequestPinConfirmationActivity(USER_P0,
+ PinItemRequest.REQUEST_TYPE_SHORTCUT));
// Check from the public API.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -201,7 +210,7 @@
}
private void assertPinItemRequestIntent(Intent actualIntent, String expectedPackage) {
- assertEquals(LauncherApps.ACTION_CONFIRM_PIN_ITEM, actualIntent.getAction());
+ assertEquals(LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT, actualIntent.getAction());
assertEquals(expectedPackage, actualIntent.getComponent().getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS,
actualIntent.getComponent().getClassName());
@@ -238,7 +247,7 @@
* - Launcher supports the feature.
* - Shortcut doesn't pre-exist.
*/
- private void checkRequestPinShortcut(@Nullable PendingIntent resultIntent) {
+ private void checkRequestPinShortcut(@Nullable IntentSender resultIntent) {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
@@ -254,8 +263,7 @@
assertNull(s.getActivity());
- assertTrue(mManager.requestPinShortcut(s,
- resultIntent == null ? null : resultIntent.getIntentSender()));
+ assertTrue(mManager.requestPinShortcut(s, resultIntent));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
@@ -294,9 +302,9 @@
// This method is always called, even with PI == null.
if (resultIntent == null) {
- verify(mServiceContext, times(1)).sendIntentSender(eq(null));
+ verify(mServiceContext, times(1)).sendIntentSender(isNull(IntentSender.class));
} else {
- verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
+ verify(mServiceContext, times(1)).sendIntentSender(notNull(IntentSender.class));
}
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
@@ -314,11 +322,12 @@
checkRequestPinShortcut(/* resultIntent=*/ null);
}
- public void testRequestPinShortcut_withCallback() {
- final PendingIntent resultIntent =
- PendingIntent.getActivity(getTestContext(), 0, new Intent(), 0);
+ private IntentSender makeResultIntent() {
+ return PendingIntent.getActivity(getTestContext(), 0, new Intent(), 0).getIntentSender();
+ }
- checkRequestPinShortcut(resultIntent);
+ public void testRequestPinShortcut_withCallback() {
+ checkRequestPinShortcut(makeResultIntent());
}
public void testRequestPinShortcut_explicitTargetActivity() {
@@ -578,8 +587,15 @@
public void testRequestPinShortcut_dynamicExists_alreadyPinned() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+ final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
+
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
+ final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, "s1")
+ .setShortLabel("Title-" + "s1")
+ .setIcon(res32x32)
+ .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class));
+ final ShortcutInfo s = b.build();
+ assertTrue(mManager.setDynamicShortcuts(list(s)));
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
@@ -590,14 +606,66 @@
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
- .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
+ .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "MainActivity"))
.areAllPinned();
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
- /* resultIntent=*/ null));
+ makeResultIntent()));
// The intent should be sent right away.
- verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
+ verify(mServiceContext, times(1)).sendIntentSender(notNull(IntentSender.class));
+ });
+
+ // Already pinned.
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("s1")
+ .areAllDynamic()
+ .areAllEnabled()
+ .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "MainActivity"))
+ .areAllPinned();
+ });
+
+ // ... But the launcher will still receive the request.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ // Check the intent passed to startActivityAsUser().
+ final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+ verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+ assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+ // Check the request object.
+ final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+ assertPinItemRequest(request);
+
+ assertWith(request.getShortcutInfo())
+ .haveIds("s1")
+ .areAllDynamic()
+ .areAllPinned() // Note it's pinned already.
+ .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "MainActivity"))
+ .areAllWithNoIntent();
+
+ assertAllHaveIcon(list(request.getShortcutInfo()));
+
+ reset(mServiceContext);
+
+ // Accept the request.
+ assertTrue(request.accept());
+
+ // The intent is only sent once, so times(1).
+ verify(mServiceContext, times(1)).sendIntentSender(isNull(IntentSender.class));
+ });
+
+ // Still pinned.
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("s1")
+ .areAllDynamic()
+ .areAllEnabled()
+ .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "MainActivity"))
+ .areAllPinned();
});
}
@@ -621,10 +689,65 @@
.areAllPinned();
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
- /* resultIntent=*/ null));
+ makeResultIntent()));
// The intent should be sent right away.
- verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
+ verify(mServiceContext, times(1)).sendIntentSender(notNull(IntentSender.class));
+ });
+
+ // Already pinned.
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1")
+ .areAllManifest()
+ .areAllEnabled()
+ .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
+ ShortcutActivity.class.getName()))
+ .areAllPinned();
+ });
+
+ // ... But the launcher will still receive the request.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ // Check the intent passed to startActivityAsUser().
+ final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+ verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+ assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+ // Check the request object.
+ final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+ assertPinItemRequest(request);
+
+ assertWith(request.getShortcutInfo())
+ .haveIds("ms1")
+ .areAllManifest()
+ .areAllPinned() // Note it's pinned already.
+ .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
+ ShortcutActivity.class.getName()))
+ .areAllWithNoIntent();
+
+ assertAllHaveIcon(list(request.getShortcutInfo()));
+
+ reset(mServiceContext);
+
+ // Accept the request.
+ assertTrue(request.accept());
+
+ // The intent is only sent once, so times(1).
+ verify(mServiceContext, times(1)).sendIntentSender(isNull(IntentSender.class));
+ });
+
+ // Still pinned.
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1")
+ .areAllManifest()
+ .areAllEnabled()
+ .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
+ ShortcutActivity.class.getName()))
+ .areAllPinned();
});
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
index 84a7c19..26033a3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
@@ -72,7 +72,7 @@
}
private void assertPinItemRequestIntent(Intent actualIntent, String expectedPackage) {
- assertEquals(LauncherApps.ACTION_CONFIRM_PIN_ITEM, actualIntent.getAction());
+ assertEquals(LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET, actualIntent.getAction());
assertEquals(expectedPackage, actualIntent.getComponent().getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS,
actualIntent.getComponent().getClassName());
@@ -83,7 +83,8 @@
private void assertPinItemRequest(PinItemRequest actualRequest, String className) {
assertNotNull(actualRequest);
assertEquals(PinItemRequest.REQUEST_TYPE_APPWIDGET, actualRequest.getRequestType());
- assertEquals(className, actualRequest.getAppWidgetProviderInfo().provider.getClassName());
+ assertEquals(className, actualRequest.getAppWidgetProviderInfo(getTestContext())
+ .provider.getClassName());
}
public void testNotForeground() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 9b2c94e..1964cad 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -177,10 +177,12 @@
UserInfo userInfo = createProfileForUser("Profile",
UserInfo.FLAG_MANAGED_PROFILE, primaryUserId);
assertNotNull(userInfo);
-
+ assertNull(mUserManager.getProfileParent(primaryUserId));
UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
assertNotNull(parentProfileInfo);
assertEquals(parentProfileInfo.id, primaryUserId);
+ removeUser(userInfo.id);
+ assertNull(mUserManager.getProfileParent(primaryUserId));
}
// Make sure only one managed profile can be created
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index b655f3a..2d07e65 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -204,6 +204,46 @@
assertNull(getPackageUseInfo(mBarUser1));
}
+ @Test
+ public void testNotifyPackageInstallUsedByOther() {
+ TestData newPackage = new TestData("newPackage",
+ VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]), mUser0);
+
+ List<String> newSecondaries = newPackage.getSecondaryDexPaths();
+ // Before we notify about the installation of the newPackage if mFoo
+ // is trying to load something from it we should not find it.
+ notifyDexLoad(mFooUser0, newSecondaries, mUser0);
+ assertNull(getPackageUseInfo(newPackage));
+
+ // Notify about newPackage install and let mFoo load its dexes.
+ mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0);
+ notifyDexLoad(mFooUser0, newSecondaries, mUser0);
+
+ // We should get back the right info.
+ PackageUseInfo pui = getPackageUseInfo(newPackage);
+ assertNotNull(pui);
+ assertFalse(pui.isUsedByOtherApps());
+ assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
+ assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0);
+ }
+
+ @Test
+ public void testNotifyPackageInstallSelfUse() {
+ TestData newPackage = new TestData("newPackage",
+ VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]), mUser0);
+
+ List<String> newSecondaries = newPackage.getSecondaryDexPaths();
+ // Packages should be able to find their own dex files even if the notification about
+ // their installation is delayed.
+ notifyDexLoad(newPackage, newSecondaries, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(newPackage);
+ assertNotNull(pui);
+ assertFalse(pui.isUsedByOtherApps());
+ assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
+ assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0);
+ }
+
private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) {
for (String dex : secondaries) {
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
index d53fdf6..e1dda51 100644
--- a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
@@ -449,6 +449,11 @@
}
@Override
+ File getDataPreloadsFileCacheDirectory() {
+ return new File(mTestPreloadsDir, "file_cache");
+ }
+
+ @Override
void publishLocalService(RetailDemoModeService service,
RetailDemoModeServiceInternal localService) {
}
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..cd3ae1a 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,41 @@
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;
+ private final boolean mMultiProcessDefault;
+
+ public static final int PRIMARY_USER_ID = 0;
public TestSystemImpl(WebViewProviderInfo[] packageConfigs, boolean fallbackLogicEnabled,
- int numRelros, boolean isDebuggable) {
+ int numRelros, boolean isDebuggable, boolean multiProcessDefault) {
mPackageConfigs = packageConfigs;
mFallbackLogicEnabled = fallbackLogicEnabled;
mNumRelros = numRelros;
mIsDebuggable = isDebuggable;
+ mUsers.add(PRIMARY_USER_ID);
+ mMultiProcessDefault = multiProcessDefault;
+ }
+
+ public void addUser(int userId) {
+ mUsers.add(userId);
}
@Override
@@ -78,17 +94,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 +116,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;
}
@@ -132,4 +189,9 @@
@Override
public void notifyZygote(boolean enableMultiProcess) {}
+
+ @Override
+ public boolean isMultiProcessDefaultEnabled() {
+ return mMultiProcessDefault;
+ }
}
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..4c0f042 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -84,17 +85,30 @@
private void setupWithPackages(WebViewProviderInfo[] packages,
boolean fallbackLogicEnabled, int numRelros, boolean isDebuggable) {
+ setupWithPackages(packages, fallbackLogicEnabled, numRelros, isDebuggable,
+ false /* multiProcessDefault */);
+ }
+
+ private void setupWithPackages(WebViewProviderInfo[] packages,
+ boolean fallbackLogicEnabled, int numRelros, boolean isDebuggable,
+ boolean multiProcessDefault) {
TestSystemImpl testing = new TestSystemImpl(packages, fallbackLogicEnabled, numRelros,
- isDebuggable);
+ isDebuggable, multiProcessDefault);
mTestSystemImpl = Mockito.spy(testing);
mWebViewUpdateServiceImpl =
new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl);
}
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 +349,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 +358,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 +388,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 +443,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 +463,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 +480,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 +548,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 +593,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 +603,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 +636,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 +717,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 +736,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 +817,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 +846,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 +924,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 +935,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 +967,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 +1202,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 +1219,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 +1254,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 +1291,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 +1305,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 +1360,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 +1379,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 +1402,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
@@ -1369,4 +1529,89 @@
assertEquals(firstPackage.versionName,
mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionName);
}
+
+ @Test
+ public void testMultiProcessEnabledByDefault() {
+ String primaryPackage = "primary";
+ WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
+ new WebViewProviderInfo(
+ primaryPackage, "", true /* default available */, false /* fallback */, null)};
+ setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */,
+ true /* debuggable */, true /* multiprocess by default */);
+ mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+ true /* valid */, true /* installed */, null /* signatures */,
+ 10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */,
+ false /* isSystemApp */));
+
+ runWebViewBootPreparationOnMainSync();
+ checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
+
+ // Check it's on by default
+ assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+ // Test toggling it
+ mWebViewUpdateServiceImpl.enableMultiProcess(false);
+ assertFalse(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+ mWebViewUpdateServiceImpl.enableMultiProcess(true);
+ assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+ // Disable, then upgrade provider, which should re-enable it
+ mWebViewUpdateServiceImpl.enableMultiProcess(false);
+ mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+ true /* valid */, true /* installed */, null /* signatures */,
+ 20 /* lastUpdateTime*/, false /* not hidden */, 2000 /* versionCode */,
+ false /* isSystemApp */));
+ mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ checkPreparationPhasesForPackage(primaryPackage, 2);
+ assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+ }
+
+ @Test
+ public void testMultiProcessDisabledByDefault() {
+ String primaryPackage = "primary";
+ WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
+ new WebViewProviderInfo(
+ primaryPackage, "", true /* default available */, false /* fallback */, null)};
+ setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */,
+ true /* debuggable */, false /* not multiprocess by default */);
+ mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+ true /* valid */, true /* installed */, null /* signatures */,
+ 10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */,
+ false /* isSystemApp */));
+
+ runWebViewBootPreparationOnMainSync();
+ checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
+
+ // Check it's off by default
+ assertFalse(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+ // Test toggling it
+ mWebViewUpdateServiceImpl.enableMultiProcess(true);
+ assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+ mWebViewUpdateServiceImpl.enableMultiProcess(false);
+ assertFalse(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+ // Disable, then upgrade provider, which should not re-enable it
+ mWebViewUpdateServiceImpl.enableMultiProcess(false);
+ mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+ true /* valid */, true /* installed */, null /* signatures */,
+ 20 /* lastUpdateTime*/, false /* not hidden */, 2000 /* versionCode */,
+ false /* isSystemApp */));
+ mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ checkPreparationPhasesForPackage(primaryPackage, 2);
+ assertFalse(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+ // Enable, then upgrade provider, which should leave it on
+ mWebViewUpdateServiceImpl.enableMultiProcess(true);
+ mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+ true /* valid */, true /* installed */, null /* signatures */,
+ 30 /* lastUpdateTime*/, false /* not hidden */, 3000 /* versionCode */,
+ false /* isSystemApp */));
+ mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ checkPreparationPhasesForPackage(primaryPackage, 3);
+ assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+ }
}
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/DimLayerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java
new file mode 100644
index 0000000..c3a471a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.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.server.wm;
+
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+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;
+
+/**
+ * Tests for the {@link DimLayerController} class.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.wm.DimLayerControllerTests
+ */
+@SmallTest
+@Presubmit
+@org.junit.runner.RunWith(AndroidJUnit4.class)
+public class DimLayerControllerTests extends WindowTestsBase {
+
+ /**
+ * This tests if shared fullscreen dim layer is added when stack is added to display
+ * and is removed when the only stack on the display is removed.
+ */
+ @Test
+ public void testSharedFullScreenDimLayer() throws Exception {
+ // Create a display.
+ final DisplayContent dc = createNewDisplay();
+ assertFalse(dc.mDimLayerController.hasSharedFullScreenDimLayer());
+
+ // Add stack with activity.
+ final TaskStack stack = createTaskStackOnDisplay(dc);
+ assertTrue(dc.mDimLayerController.hasDimLayerUser(stack));
+ assertTrue(dc.mDimLayerController.hasSharedFullScreenDimLayer());
+
+ // Remove the only stack on the display and check if the shared dim layer clears.
+ stack.removeImmediately();
+ assertFalse(dc.mDimLayerController.hasDimLayerUser(stack));
+ assertFalse(dc.mDimLayerController.hasSharedFullScreenDimLayer());
+ }
+}
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..30f99e5 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -29,6 +29,7 @@
import java.util.ArrayList;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
@@ -215,13 +216,8 @@
*/
@Test
public void testMoveStackBetweenDisplays() throws Exception {
- // Create second display.
- 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);
+ // Create a second display.
+ final DisplayContent dc = createNewDisplay();
// Add stack with activity.
final TaskStack stack = createTaskStackOnDisplay(dc);
@@ -236,7 +232,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());
@@ -261,10 +257,31 @@
// Check that override config is applied.
assertEquals(newOverrideConfig, sDisplayContent.getOverrideConfiguration());
+ }
+
+ /**
+ * This tests global configuration updates when default display config is updated.
+ */
+ @Test
+ public void testDefaultDisplayOverrideConfigUpdate() throws Exception {
+ final Configuration currentOverrideConfig = sDisplayContent.getOverrideConfiguration();
+
+ // Create new, slightly changed override configuration and apply it to the display.
+ final Configuration newOverrideConfig = new Configuration(currentOverrideConfig);
+ newOverrideConfig.densityDpi += 120;
+ newOverrideConfig.fontScale += 0.3;
+
+ sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, DEFAULT_DISPLAY);
// Check that global configuration is updated, as we've updated default display's config.
- final Configuration globalConfig = sWm.mRoot.getConfiguration();
+ Configuration globalConfig = sWm.mRoot.getConfiguration();
assertEquals(newOverrideConfig.densityDpi, globalConfig.densityDpi);
assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
+
+ // Return back to original values.
+ sWm.setNewDisplayOverrideConfiguration(currentOverrideConfig, DEFAULT_DISPLAY);
+ globalConfig = sWm.mRoot.getConfiguration();
+ assertEquals(currentOverrideConfig.densityDpi, globalConfig.densityDpi);
+ assertEquals(currentOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
}
}
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..b0eba0b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
@@ -0,0 +1,118 @@
+/*
+ * 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 DisplayContent dc = createNewDisplay();
+ 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/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index 48d4770..e1a22d9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -18,6 +18,7 @@
import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
import static android.graphics.GraphicBuffer.USAGE_SW_READ_NEVER;
+import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY;
import static android.graphics.GraphicBuffer.USAGE_SW_WRITE_NEVER;
import static android.graphics.GraphicBuffer.create;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -26,13 +27,18 @@
import android.app.ActivityManager.TaskSnapshot;
import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.os.Debug;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,21 +50,75 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class TaskSnapshotCacheTest extends WindowTestsBase {
+public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
- @Test
- public void testCleanCache() throws Exception {
- TaskSnapshotCache snapshotCache = new TaskSnapshotCache();
- final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
- snapshotCache.putSnapshot(window.getTask(), createSnapshot());
- assertNotNull(snapshotCache.getSnapshot(window.getTask()));
- snapshotCache.cleanCache(window.mAppToken);
- assertNull(snapshotCache.getSnapshot(window.getTask()));
+ private TaskSnapshotCache mCache;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mCache = new TaskSnapshotCache(sWm, mLoader);
}
- private TaskSnapshot createSnapshot() {
- GraphicBuffer buffer = create(1, 1, PixelFormat.RGBA_8888,
- USAGE_HW_TEXTURE | USAGE_SW_WRITE_NEVER | USAGE_SW_READ_NEVER);
- return new TaskSnapshot(buffer, Configuration.ORIENTATION_PORTRAIT, new Rect());
+ @Test
+ public void testAppRemoved() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mCache.putSnapshot(window.getTask(), createSnapshot());
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ mCache.onAppRemoved(window.mAppToken);
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ }
+
+ @Test
+ public void testAppDied() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mCache.putSnapshot(window.getTask(), createSnapshot());
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ mCache.onAppDied(window.mAppToken);
+
+ // Should still be in the retrieval cache.
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+
+ // Trash retrieval cache.
+ for (int i = 0; i < 20; i++) {
+ mCache.putSnapshot(createWindow(null, FIRST_APPLICATION_WINDOW, "window").getTask(),
+ createSnapshot());
+ }
+
+ // Should not be in cache anymore
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ }
+
+ @Test
+ public void testTaskRemoved() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mCache.putSnapshot(window.getTask(), createSnapshot());
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ mCache.onTaskRemoved(window.getTask().mTaskId);
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ }
+
+ @Test
+ public void testRestoreFromDisk() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+ mPersister.waitForQueueEmpty();
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */));
+
+ // Load it from disk
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ true /* restoreFromDisk */));
+
+ // Make sure it's in the cache now.
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
new file mode 100644
index 0000000..dc008b5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+
+import com.android.internal.util.Predicate;
+import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskSnapshotPersisterLoaderTest
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
+
+ private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
+ private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
+
+ @Test
+ public void testPersistAndLoadSnapshot() {
+ mPersister.persistSnapshot(1 , sTestUserId, createSnapshot());
+ mPersister.waitForQueueEmpty();
+ final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
+ new File(sFilesDir.getPath() + "/snapshots/1.png") };
+ assertTrueForFiles(files, File::exists, " must exist");
+ final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId);
+ assertNotNull(snapshot);
+ assertEquals(TEST_INSETS, snapshot.getContentInsets());
+ assertNotNull(snapshot.getSnapshot());
+ assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation());
+ }
+
+ private void assertTrueForFiles(File[] files, Predicate<File> predicate, String message) {
+ for (File file : files) {
+ assertTrue(file.getName() + message, predicate.apply(file));
+ }
+ }
+
+ @Test
+ public void testTaskRemovedFromRecents() {
+ mPersister.persistSnapshot(1, sTestUserId, createSnapshot());
+ mPersister.onTaskRemovedFromRecents(1, sTestUserId);
+ mPersister.waitForQueueEmpty();
+ assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists());
+ assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.png").exists());
+ }
+
+ /**
+ * Tests that persisting a couple of snapshots is being throttled.
+ */
+ @Test
+ public void testThrottling() {
+ long ms = SystemClock.elapsedRealtime();
+ mPersister.persistSnapshot(1, sTestUserId, createSnapshot());
+ mPersister.persistSnapshot(2, sTestUserId, createSnapshot());
+ mPersister.persistSnapshot(3, sTestUserId, createSnapshot());
+ mPersister.persistSnapshot(4, sTestUserId, createSnapshot());
+ mPersister.persistSnapshot(5, sTestUserId, createSnapshot());
+ mPersister.persistSnapshot(6, sTestUserId, createSnapshot());
+ mPersister.waitForQueueEmpty();
+ assertTrue(SystemClock.elapsedRealtime() - ms > 500);
+ }
+
+ @Test
+ public void testGetTaskId() {
+ RemoveObsoleteFilesQueueItem removeObsoleteFilesQueueItem =
+ mPersister.new RemoveObsoleteFilesQueueItem(new ArraySet<>(), new int[] {});
+ assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
+ assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
+ assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
+ assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.png"));
+ assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto"));
+ assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.png"));
+ }
+
+ @Test
+ public void testRemoveObsoleteFiles() {
+ mPersister.persistSnapshot(1, sTestUserId, createSnapshot());
+ mPersister.persistSnapshot(2, sTestUserId, createSnapshot());
+ final ArraySet<Integer> taskIds = new ArraySet<>();
+ taskIds.add(1);
+ mPersister.removeObsoleteFiles(taskIds, new int[] { sTestUserId });
+ mPersister.waitForQueueEmpty();
+ final File[] existsFiles = new File[] {
+ new File(sFilesDir.getPath() + "/snapshots/1.proto"),
+ new File(sFilesDir.getPath() + "/snapshots/1.png") };
+ final File[] nonExistsFiles = new File[] {
+ new File(sFilesDir.getPath() + "/snapshots/2.proto"),
+ new File(sFilesDir.getPath() + "/snapshots/2.png") };
+ assertTrueForFiles(existsFiles, File::exists, " must exist");
+ assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
+ }
+
+ @Test
+ public void testRemoveObsoleteFiles_addedOneInTheMeantime() {
+ mPersister.persistSnapshot(1, sTestUserId, createSnapshot());
+ final ArraySet<Integer> taskIds = new ArraySet<>();
+ taskIds.add(1);
+ mPersister.removeObsoleteFiles(taskIds, new int[] { sTestUserId });
+ mPersister.persistSnapshot(2, sTestUserId, createSnapshot());
+ mPersister.waitForQueueEmpty();
+ final File[] existsFiles = new File[] {
+ new File(sFilesDir.getPath() + "/snapshots/1.proto"),
+ new File(sFilesDir.getPath() + "/snapshots/1.png"),
+ new File(sFilesDir.getPath() + "/snapshots/2.proto"),
+ new File(sFilesDir.getPath() + "/snapshots/2.png") };
+ assertTrueForFiles(existsFiles, File::exists, " must exist");
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
new file mode 100644
index 0000000..6fc6edb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
+import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.pm.UserInfo;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import java.io.File;
+
+/**
+ * Base class for tests that use a {@link TaskSnapshotPersister}.
+ */
+class TaskSnapshotPersisterTestBase extends WindowTestsBase {
+
+ private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
+ private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
+
+ TaskSnapshotPersister mPersister;
+ TaskSnapshotLoader mLoader;
+ static int sTestUserId;
+ static File sFilesDir;
+ private static UserManager sUserManager;
+
+ @BeforeClass
+ public static void setUpUser() {
+ sUserManager = UserManager.get(InstrumentationRegistry.getContext());
+ sTestUserId = createUser(TEST_USER_NAME, 0);
+ sFilesDir = InstrumentationRegistry.getContext().getFilesDir();
+ }
+
+ @AfterClass
+ public static void tearDownUser() {
+ removeUser(sTestUserId);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mPersister = new TaskSnapshotPersister(
+ userId -> sFilesDir);
+ mLoader = new TaskSnapshotLoader(mPersister);
+ mPersister.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ cleanDirectory();
+ }
+
+ private static int createUser(String name, int flags) {
+ UserInfo user = sUserManager.createUser(name, flags);
+ if (user == null) {
+ Assert.fail("Error while creating the test user: " + TEST_USER_NAME);
+ }
+ return user.id;
+ }
+
+ private static void removeUser(int userId) {
+ if (!sUserManager.removeUser(userId)) {
+ Assert.fail("Error while removing the test user: " + TEST_USER_NAME);
+ }
+ }
+
+ private void cleanDirectory() {
+ for (File file : new File(sFilesDir, "snapshots").listFiles()) {
+ if (!file.isDirectory()) {
+ file.delete();
+ }
+ }
+ }
+
+ TaskSnapshot createSnapshot() {
+ GraphicBuffer buffer = GraphicBuffer.create(100, 100, PixelFormat.RGBA_8888,
+ USAGE_HW_TEXTURE | USAGE_SW_READ_RARELY | USAGE_SW_READ_RARELY);
+ Canvas c = buffer.lockCanvas();
+ c.drawColor(Color.RED);
+ buffer.unlockCanvasAndPost(c);
+ return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS);
+ }
+}
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..f79908e 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,63 @@
@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;
+ assertEquals(sDisplayContent, stack1.getDisplayContent());
// 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);
+ final DisplayContent dc = createNewDisplay();
+ 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/TestIWindow.java b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
index 1514e69..c029a9f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
@@ -107,4 +107,8 @@
throws RemoteException {
}
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ }
}
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 c0c8fb0..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) {
- return null;
+ int logo, int windowFlags, Configuration overrideConfig, int displayId) {
+ 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..ca9dd9d 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,21 @@
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.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
+import android.view.Display;
+import android.view.DisplayInfo;
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;
@@ -36,6 +43,7 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.EMPTY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -50,18 +58,24 @@
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);
+ private static int sNextDisplayId = Display.DEFAULT_DISPLAY + 1;
static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
private static int sNextTaskId = 0;
private static boolean sOneTimeSetupDone = false;
static DisplayContent sDisplayContent;
+ static DisplayInfo sDisplayInfo = new DisplayInfo();
static WindowLayersController sLayersController;
static WindowState sWallpaperWindow;
static WindowState sImeWindow;
@@ -72,19 +86,37 @@
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 = new DisplayContent(context.getDisplay(), sWm, sLayersController,
- new WallpaperController(sWm));
- sWm.mRoot.addChild(sDisplayContent, 0);
+ sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId());
+ if (sDisplayContent != null) {
+ sDisplayContent.removeImmediately();
+ }
+ // Make sure that display ids don't overlap, so there won't be several displays with same
+ // ids among RootWindowContainer children.
+ for (DisplayContent dc : sWm.mRoot.mChildren) {
+ if (dc.getDisplayId() >= sNextDisplayId) {
+ sNextDisplayId = dc.getDisplayId() + 1;
+ }
+ }
+ context.getDisplay().getDisplayInfo(sDisplayInfo);
+ sDisplayContent = createNewDisplay();
+ sWm.mDisplayEnabled = true;
+ sWm.mDisplayReady = true;
// Set-up some common windows.
sWallpaperWindow = createWindow(null, TYPE_WALLPAPER, sDisplayContent, "wallpaperWindow");
@@ -108,7 +140,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 +159,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 +171,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 +189,34 @@
}
/** 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;
}
- /**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
- Task createTaskInStack(TaskStack stack, int userId) {
+ 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}. */
+ 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;
}
+ /** Creates a {@link DisplayContent} and adds it to the system. */
+ DisplayContent createNewDisplay() {
+ final int displayId = sNextDisplayId++;
+ final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
+ sDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
+ return new DisplayContent(display, sWm, sLayersController, new WallpaperController(sWm));
+ }
+
/* 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 +236,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 +264,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 +289,16 @@
super.onDisplayChanged(dc);
mOnDisplayChangedCalled = true;
}
+
+ @Override
+ boolean isAnimating() {
+ return mUseLocalIsAnimating ? mIsAnimating : super.isAnimating();
+ }
+
+ void setLocalIsAnimating(boolean isAnimating) {
+ mUseLocalIsAnimating = true;
+ mIsAnimating = isAnimating;
+ }
}
/**
@@ -243,21 +308,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 +356,10 @@
0 /* inputDispatchingTimeoutNanos */, sWm);
mToken = token;
}
+
+ AppWindowToken getAppWindowToken() {
+ return (AppWindowToken) sDisplayContent.getWindowToken(mToken.asBinder());
+ }
}
class TestIApplicationToken implements IApplicationToken {
@@ -295,7 +376,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 d243bf2..7a69803 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -106,6 +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 = true;
private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
long mAppIdleScreenThresholdMillis;
@@ -241,7 +242,7 @@
postOneTimeCheckIdleStates();
}
- if (KERNEL_COUNTER_FILE.exists()) {
+ if (ENABLE_KERNEL_UPDATES && KERNEL_COUNTER_FILE.exists()) {
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE
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 7f182a4..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;
@@ -556,7 +658,9 @@
private void sendPortChangedBroadcastLocked(PortInfo portInfo) {
final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.addFlags(
+ Intent.FLAG_RECEIVER_FOREGROUND |
+ Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
intent.putExtra(UsbManager.EXTRA_PORT, portInfo.mUsbPort);
intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus);
@@ -570,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) {
@@ -696,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;
}
@@ -705,13 +697,6 @@
}
};
- private final UEventObserver mUEventObserver = new UEventObserver() {
- @Override
- public void onUEvent(UEvent event) {
- scheduleUpdatePorts();
- }
- };
-
/**
* Describes a USB port.
*/
@@ -762,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;
@@ -775,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/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index e03a14f..2e99b6e 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -971,7 +971,9 @@
public void accessoryAttached(UsbAccessory accessory) {
Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
ArrayList<ResolveInfo> matches;
String defaultPackage = null;
@@ -1364,7 +1366,9 @@
private static Intent createDeviceAttachedIntent(UsbDevice device) {
Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
return intent;
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index 24d5f09..7a55be4 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -182,6 +182,7 @@
}
Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
if (DEBUG) {
@@ -204,6 +205,7 @@
}
Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 7d258a0..b7391b4 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -377,8 +377,16 @@
*/
public static final int PROPERTY_IS_DOWNGRADED_CONFERENCE = 1<<6;
+ /**
+ * Set by the framework to indicate that the {@link Connection} originated from a self-managed
+ * {@link ConnectionService}.
+ * <p>
+ * See {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.
+ */
+ public static final int PROPERTY_SELF_MANAGED = 1<<7;
+
//**********************************************************************************************
- // Next PROPERTY value: 1<<7
+ // Next PROPERTY value: 1<<8
//**********************************************************************************************
/**
@@ -681,6 +689,10 @@
builder.append("Properties:");
}
+ if (can(properties, PROPERTY_SELF_MANAGED)) {
+ builder.append(isLong ? " PROPERTY_SELF_MANAGED" : " self_mng");
+ }
+
if (can(properties, PROPERTY_EMERGENCY_CALLBACK_MODE)) {
builder.append(isLong ? " PROPERTY_EMERGENCY_CALLBACK_MODE" : " ecbm");
}
@@ -741,6 +753,7 @@
public void onConnectionEvent(Connection c, String event, Bundle extras) {}
/** @hide */
public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
+ public void onAudioRouteChanged(Connection c, int audioRoute) {}
}
/**
@@ -2325,6 +2338,25 @@
}
/**
+ * Sets the audio route (speaker, bluetooth, etc...). When this request is honored, there will
+ * be change to the {@link #getCallAudioState()}.
+ * <p>
+ * Used by self-managed {@link ConnectionService}s which wish to change the audio route for a
+ * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+ * <p>
+ * See also {@link InCallService#setAudioRoute(int)}.
+ *
+ * @param route The audio route to use (one of {@link CallAudioState#ROUTE_BLUETOOTH},
+ * {@link CallAudioState#ROUTE_EARPIECE}, {@link CallAudioState#ROUTE_SPEAKER}, or
+ * {@link CallAudioState#ROUTE_WIRED_HEADSET}).
+ */
+ public final void setAudioRoute(int route) {
+ for (Listener l : mListeners) {
+ l.onAudioRouteChanged(this, route);
+ }
+ }
+
+ /**
* Notifies this Connection that the {@link #getAudioState()} property has a new value.
*
* @param state The new connection audio state.
@@ -2479,6 +2511,21 @@
*/
public void onExtrasChanged(Bundle extras) {}
+ /**
+ * Notifies this {@link Connection} that its {@link ConnectionService} is responsible for
+ * displaying its incoming call user interface for the {@link Connection}.
+ * <p>
+ * Will only be called for incoming calls added via a self-managed {@link ConnectionService}
+ * (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}), where the {@link ConnectionService}
+ * should show its own incoming call user interface.
+ * <p>
+ * Where there are ongoing calls in other self-managed {@link ConnectionService}s, or in a
+ * regular {@link ConnectionService}, the Telecom framework will display its own incoming call
+ * user interface to allow the user to choose whether to answer the new incoming call and
+ * disconnect other ongoing calls, or to reject the new incoming call.
+ */
+ public void onShowIncomingCallUi() {}
+
static String toLogSafePhoneNumber(String number) {
// For unknown number, log empty string.
if (number == null) {
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index aba38fe..2343462 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -33,6 +33,7 @@
private final Bundle mExtras;
private final int mVideoState;
private final String mTelecomCallId;
+ private final boolean mShouldShowIncomingCallUi;
/**
* @param accountHandle The accountHandle which should be used to place the call.
@@ -43,7 +44,7 @@
PhoneAccountHandle accountHandle,
Uri handle,
Bundle extras) {
- this(accountHandle, handle, extras, VideoProfile.STATE_AUDIO_ONLY, null);
+ this(accountHandle, handle, extras, VideoProfile.STATE_AUDIO_ONLY, null, false);
}
/**
@@ -57,7 +58,7 @@
Uri handle,
Bundle extras,
int videoState) {
- this(accountHandle, handle, extras, videoState, null);
+ this(accountHandle, handle, extras, videoState, null, false);
}
/**
@@ -66,6 +67,10 @@
* @param extras Application-specific extra data.
* @param videoState Determines the video state for the connection.
* @param telecomCallId The telecom call ID.
+ * @param shouldShowIncomingCallUi For a self-managed {@link ConnectionService}, will be
+ * {@code true} if the {@link ConnectionService} should show its
+ * own incoming call UI for an incoming call. When
+ * {@code false}, Telecom shows the incoming call UI.
* @hide
*/
public ConnectionRequest(
@@ -73,12 +78,14 @@
Uri handle,
Bundle extras,
int videoState,
- String telecomCallId) {
+ String telecomCallId,
+ boolean shouldShowIncomingCallUi) {
mAccountHandle = accountHandle;
mAddress = handle;
mExtras = extras;
mVideoState = videoState;
mTelecomCallId = telecomCallId;
+ mShouldShowIncomingCallUi = shouldShowIncomingCallUi;
}
private ConnectionRequest(Parcel in) {
@@ -87,6 +94,7 @@
mExtras = in.readParcelable(getClass().getClassLoader());
mVideoState = in.readInt();
mTelecomCallId = in.readString();
+ mShouldShowIncomingCallUi = in.readInt() == 1;
}
/**
@@ -129,6 +137,18 @@
return mTelecomCallId;
}
+ /**
+ * For a self-managed {@link ConnectionService}, indicates for an incoming call whether the
+ * {@link ConnectionService} should show its own incoming call UI for an incoming call.
+ *
+ * @return {@code true} if the {@link ConnectionService} should show its own incoming call UI.
+ * When {@code false}, Telecom shows the incoming call UI for the call.
+ * @hide
+ */
+ public boolean shouldShowIncomingCallUi() {
+ return mShouldShowIncomingCallUi;
+ }
+
@Override
public String toString() {
return String.format("ConnectionRequest %s %s",
@@ -165,5 +185,6 @@
destination.writeParcelable(mExtras, 0);
destination.writeInt(mVideoState);
destination.writeString(mTelecomCallId);
+ destination.writeInt(mShouldShowIncomingCallUi ? 1 : 0);
}
}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index b119e16..d0ccd55 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -42,10 +42,15 @@
import java.util.concurrent.ConcurrentHashMap;
/**
- * An abstract service that should be implemented by any apps which can make phone calls (VoIP or
- * otherwise) and want those calls to be integrated into the built-in phone app.
- * Once implemented, the {@code ConnectionService} needs two additional steps before it will be
- * integrated into the phone app:
+ * An abstract service that should be implemented by any apps which either:
+ * <ol>
+ * <li>Can make phone calls (VoIP or otherwise) and want those calls to be integrated into the
+ * built-in phone app. Referred to as a <b>system managed</b> {@link ConnectionService}.</li>
+ * <li>Are a standalone calling app and don't want their calls to be integrated into the
+ * built-in phone app. Referred to as a <b>self managed</b> {@link ConnectionService}.</li>
+ * </ol>
+ * Once implemented, the {@link ConnectionService} needs to take the following steps so that Telecom
+ * will bind to it:
* <p>
* 1. <i>Registration in AndroidManifest.xml</i>
* <br/>
@@ -63,16 +68,20 @@
* <br/>
* See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information.
* <p>
- * Once registered and enabled by the user in the phone app settings, telecom will bind to a
- * {@code ConnectionService} implementation when it wants that {@code ConnectionService} to place
- * a call or the service has indicated that is has an incoming call through
- * {@link TelecomManager#addNewIncomingCall}. The {@code ConnectionService} can then expect a call
- * to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection} wherein it
- * should provide a new instance of a {@link Connection} object. It is through this
- * {@link Connection} object that telecom receives state updates and the {@code ConnectionService}
+ * System managed {@link ConnectionService}s must be enabled by the user in the phone app settings
+ * before Telecom will bind to them. Self-manged {@link ConnectionService}s must be granted the
+ * appropriate permission before Telecom will bind to them.
+ * <p>
+ * Once registered and enabled by the user in the phone app settings or granted permission, telecom
+ * will bind to a {@link ConnectionService} implementation when it wants that
+ * {@link ConnectionService} to place a call or the service has indicated that is has an incoming
+ * call through {@link TelecomManager#addNewIncomingCall}. The {@link ConnectionService} can then
+ * expect a call to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection}
+ * wherein it should provide a new instance of a {@link Connection} object. It is through this
+ * {@link Connection} object that telecom receives state updates and the {@link ConnectionService}
* receives call-commands such as answer, reject, hold and disconnect.
* <p>
- * When there are no more live calls, telecom will unbind from the {@code ConnectionService}.
+ * When there are no more live calls, telecom will unbind from the {@link ConnectionService}.
*/
public abstract class ConnectionService extends Service {
/**
@@ -1054,6 +1063,7 @@
}
}
+ @Override
public void onExtrasRemoved(Connection c, List<String> keys) {
String id = mIdByConnection.get(c);
if (id != null) {
@@ -1061,7 +1071,6 @@
}
}
-
@Override
public void onConnectionEvent(Connection connection, String event, Bundle extras) {
String id = mIdByConnection.get(connection);
@@ -1069,6 +1078,14 @@
mAdapter.onConnectionEvent(id, event, extras);
}
}
+
+ @Override
+ public void onAudioRouteChanged(Connection c, int audioRoute) {
+ String id = mIdByConnection.get(c);
+ if (id != null) {
+ mAdapter.setAudioRoute(id, audioRoute);
+ }
+ }
};
/** {@inheritDoc} */
@@ -1146,6 +1163,13 @@
connection.getDisconnectCause(),
createIdList(connection.getConferenceables()),
connection.getExtras()));
+
+ if (isIncoming && request.shouldShowIncomingCallUi() &&
+ (connection.getConnectionProperties() & Connection.PROPERTY_SELF_MANAGED) ==
+ Connection.PROPERTY_SELF_MANAGED) {
+ // Tell ConnectionService to show its incoming call UX.
+ connection.onShowIncomingCallUi();
+ }
if (isUnknown) {
triggerConferenceRecalculate();
}
@@ -1587,6 +1611,38 @@
}
/**
+ * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
+ * incoming {@link Connection} was denied.
+ * <p>
+ * Used when a self-managed {@link ConnectionService} attempts to create a new incoming
+ * {@link Connection}, but Telecom has determined that the call cannot be allowed at this time.
+ * The {@link ConnectionService} is responsible for silently rejecting the new incoming
+ * {@link Connection}.
+ * <p>
+ * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information.
+ *
+ * @param request The incoming connection request.
+ */
+ public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
+ }
+
+ /**
+ * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
+ * outgoing {@link Connection} was denied.
+ * <p>
+ * Used when a self-managed {@link ConnectionService} attempts to create a new outgoing
+ * {@link Connection}, but Telecom has determined that the call cannot be placed at this time.
+ * The {@link ConnectionService} is responisible for informing the user that the
+ * {@link Connection} cannot be made at this time.
+ * <p>
+ * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information.
+ *
+ * @param request The outgoing connection request.
+ */
+ public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+ }
+
+ /**
* Trigger recalculate functinality for conference calls. This is used when a Telephony
* Connection is part of a conference controller but is not yet added to Connection
* Service and hence cannot be added to the conference call.
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index f3fada9..9542b73 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -515,6 +515,23 @@
}
/**
+ * Sets the audio route associated with a {@link Connection}.
+ *
+ * @param callId The unique ID of the call.
+ * @param audioRoute The new audio route (see {@code CallAudioState#ROUTE_*}).
+ */
+ void setAudioRoute(String callId, int audioRoute) {
+ Log.v(this, "setAudioRoute: %s %s", callId, CallAudioState.audioRouteToString(audioRoute));
+ for (IConnectionServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setAudioRoute(callId, audioRoute, Log.getExternalSession());
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+
+ /**
* Informs Telecom of a connection level event.
*
* @param callId The unique ID of the call.
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index afe5e33..cc437f9 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -67,6 +67,7 @@
private static final int MSG_ON_CONNECTION_EVENT = 26;
private static final int MSG_SET_CONNECTION_PROPERTIES = 27;
private static final int MSG_SET_PULLING = 28;
+ private static final int MSG_SET_AUDIO_ROUTE = 29;
private final IConnectionServiceAdapter mDelegate;
@@ -289,6 +290,16 @@
}
break;
}
+ case MSG_SET_AUDIO_ROUTE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ mDelegate.setAudioRoute((String) args.arg1, args.argi1,
+ (Session.Info) args.arg2);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
}
}
};
@@ -507,6 +518,17 @@
}
@Override
+ public final void setAudioRoute(String connectionId, int audioRoute,
+ Session.Info sessionInfo) {
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = connectionId;
+ args.argi1 = audioRoute;
+ args.arg2 = sessionInfo;
+ mHandler.obtainMessage(MSG_SET_AUDIO_ROUTE, args).sendToTarget();
+ }
+
+ @Override
public final void onConnectionEvent(String connectionId, String event, Bundle extras,
Session.Info sessionInfo) {
SomeArgs args = SomeArgs.obtain();
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index ca54486..845a103 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -189,6 +189,21 @@
public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 0x400;
/**
+ * Flag indicating that this {@link PhoneAccount} is responsible for managing its own
+ * {@link Connection}s. This type of {@link PhoneAccount} is ideal for use with standalone
+ * calling apps which do not wish to use the default phone app for {@link Connection} UX,
+ * but which want to leverage the call and audio routing capabilities of the Telecom framework.
+ * <p>
+ * When set, {@link Connection}s created by the self-managed {@link ConnectionService} will not
+ * be surfaced to implementations of the {@link InCallService} API. Thus it is the
+ * responsibility of a self-managed {@link ConnectionService} to provide a user interface for
+ * its {@link Connection}s.
+ * <p>
+ * Self-managed {@link Connection}s will, however, be displayed on connected Bluetooth devices.
+ */
+ public static final int CAPABILITY_SELF_MANAGED = 0x800;
+
+ /**
* URI scheme for telephone number URIs.
*/
public static final String SCHEME_TEL = "tel";
@@ -692,6 +707,14 @@
mIsEnabled = isEnabled;
}
+ /**
+ * @return {@code true} if the {@link PhoneAccount} is self-managed, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isSelfManaged() {
+ return (mCapabilities & CAPABILITY_SELF_MANAGED) == CAPABILITY_SELF_MANAGED;
+ }
+
//
// Parcelable implementation
//
@@ -815,6 +838,9 @@
*/
private String capabilitiesToString() {
StringBuilder sb = new StringBuilder();
+ if (hasCapabilities(CAPABILITY_SELF_MANAGED)) {
+ sb.append("SelfManaged ");
+ }
if (hasCapabilities(CAPABILITY_SUPPORTS_VIDEO_CALLING)) {
sb.append("SuppVideo ");
}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index d8a226a..0c7404a 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -389,6 +389,15 @@
}
@Override
+ public void setAudioRoute(String callId, int audioRoute, Session.Info sessionInfo) {
+ if (hasConnection(callId)) {
+ // TODO(3pcalls): handle this for remote connections.
+ // Likely we don't want to do anything since it doesn't make sense for self-managed
+ // connections to go through a connection mgr.
+ }
+ }
+
+ @Override
public void onConnectionEvent(String callId, String event, Bundle extras,
Session.Info sessionInfo) {
if (mConnectionById.containsKey(callId)) {
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index f12886a..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";
@@ -1202,17 +1205,25 @@
/**
* Registers a new incoming call. A {@link ConnectionService} should invoke this method when it
- * has an incoming call. The specified {@link PhoneAccountHandle} must have been registered
- * with {@link #registerPhoneAccount} and the user must have enabled the corresponding
- * {@link PhoneAccount}. This can be checked using {@link #getPhoneAccount}. Once invoked, this
- * method will cause the system to bind to the {@link ConnectionService} associated with the
- * {@link PhoneAccountHandle} and request additional information about the call
- * (See {@link ConnectionService#onCreateIncomingConnection}) before starting the incoming
+ * has an incoming call. For managed {@link ConnectionService}s, the specified
+ * {@link PhoneAccountHandle} must have been registered with {@link #registerPhoneAccount} and
+ * the user must have enabled the corresponding {@link PhoneAccount}. This can be checked using
+ * {@link #getPhoneAccount}. Self-managed {@link ConnectionService}s must have
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS} to add a new incoming call.
+ * <p>
+ * Once invoked, this method will cause the system to bind to the {@link ConnectionService}
+ * associated with the {@link PhoneAccountHandle} and request additional information about the
+ * call (See {@link ConnectionService#onCreateIncomingConnection}) before starting the incoming
* call UI.
* <p>
- * A {@link SecurityException} will be thrown if either the {@link PhoneAccountHandle} does not
- * correspond to a registered {@link PhoneAccount} or the associated {@link PhoneAccount} is not
- * currently enabled by the user.
+ * For a managed {@link ConnectionService}, a {@link SecurityException} will be thrown if either
+ * the {@link PhoneAccountHandle} does not correspond to a registered {@link PhoneAccount} or
+ * the associated {@link PhoneAccount} is not currently enabled by the user.
+ * <p>
+ * For a self-managed {@link ConnectionService}, a {@link SecurityException} will be thrown if
+ * the {@link PhoneAccount} has {@link PhoneAccount#CAPABILITY_SELF_MANAGED} and the calling app
+ * does not have {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+ *
* @param phoneAccount A {@link PhoneAccountHandle} registered with
* {@link #registerPhoneAccount}.
* @param extras A bundle that will be passed through to
@@ -1379,7 +1390,8 @@
* method-caller is either the user selected default dialer app or preloaded system dialer
* app, then emergency calls will also be allowed.
*
- * Requires permission: {@link android.Manifest.permission#CALL_PHONE}
+ * Placing a call via a managed {@link ConnectionService} requires permission:
+ * {@link android.Manifest.permission#CALL_PHONE}
*
* Usage example:
* <pre>
@@ -1396,11 +1408,20 @@
* <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>
* <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>
* </ul>
+ * <p>
+ * An app which implements the self-managed {@link ConnectionService} API uses
+ * {@link #placeCall(Uri, Bundle)} to inform Telecom of a new outgoing call. A self-managed
+ * {@link ConnectionService} must include {@link #EXTRA_PHONE_ACCOUNT_HANDLE} to specify its
+ * associated {@link android.telecom.PhoneAccountHandle}.
+ *
+ * Self-managed {@link ConnectionService}s require permission
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
*
* @param address The address to make the call to.
* @param extras Bundle of extras to use with the call.
*/
- @RequiresPermission(android.Manifest.permission.CALL_PHONE)
+ @RequiresPermission(anyOf = {android.Manifest.permission.CALL_PHONE,
+ android.Manifest.permission.MANAGE_OWN_CALLS})
public void placeCall(Uri address, Bundle extras) {
ITelecomService service = getTelecomService();
if (service != null) {
@@ -1476,6 +1497,71 @@
return result;
}
+ /**
+ * Determines whether Telecom would permit an incoming call to be added via the
+ * {@link #addNewIncomingCall(PhoneAccountHandle, Bundle)} API for the specified
+ * {@link PhoneAccountHandle}.
+ * <p>
+ * A {@link ConnectionService} may not add a call for the specified {@link PhoneAccountHandle}
+ * in the following situations:
+ * <ul>
+ * <li>{@link PhoneAccount} does not have property
+ * {@link PhoneAccount#CAPABILITY_SELF_MANAGED} set (i.e. it is a managed
+ * {@link ConnectionService}), and the active or held call limit has
+ * been reached.</li>
+ * <li>There is an ongoing emergency call.</li>
+ * </ul>
+ *
+ * @param phoneAccountHandle The {@link PhoneAccountHandle} the call will be added for.
+ * @return {@code true} if telecom will permit an incoming call to be added, {@code false}
+ * otherwise.
+ */
+ public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ return service.isIncomingCallPermitted(phoneAccountHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error isIncomingCallPermitted", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether Telecom would permit an outgoing call to be placed via the
+ * {@link #placeCall(Uri, Bundle)} API for the specified {@link PhoneAccountHandle}.
+ * <p>
+ * A {@link ConnectionService} may not place a call for the specified {@link PhoneAccountHandle}
+ * in the following situations:
+ * <ul>
+ * <li>{@link PhoneAccount} does not have property
+ * {@link PhoneAccount#CAPABILITY_SELF_MANAGED} set (i.e. it is a managed
+ * {@link ConnectionService}), and the active, held or ringing call limit has
+ * been reached.</li>
+ * <li>{@link PhoneAccount} has property {@link PhoneAccount#CAPABILITY_SELF_MANAGED} set
+ * (i.e. it is a self-managed {@link ConnectionService} and there is an ongoing call in
+ * another {@link ConnectionService}.</li>
+ * <li>There is an ongoing emergency call.</li>
+ * </ul>
+ *
+ * @param phoneAccountHandle The {@link PhoneAccountHandle} the call will be added for.
+ * @return {@code true} if telecom will permit an outgoing call to be placed, {@code false}
+ * otherwise.
+ */
+ public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ return service.isOutgoingCallPermitted(phoneAccountHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error isOutgoingCallPermitted", e);
+ }
+ }
+ return false;
+ }
+
+
private ITelecomService getTelecomService() {
if (mTelecomServiceOverride != null) {
return mTelecomServiceOverride;
diff --git a/telecomm/java/android/telecom/package-info.java b/telecomm/java/android/telecom/package-info.java
new file mode 100644
index 0000000..a4140e5
--- /dev/null
+++ b/telecomm/java/android/telecom/package-info.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ */
+
+/**
+ * The Android Telecom framework is responsible for managing calls on an Android device. This can
+ * include SIM-based calls using the {@code Telephony} framework, VOIP calls using SIP (e.g. the
+ * {@code SipConnectionService}), or via a third-party VOIP
+ * {@link android.telecom.ConnectionService}. Telecom acts as a switchboard, routing calls and
+ * audio focus between {@link android.telecom.Connection}s provided by
+ * {@link android.telecom.ConnectionService} implementations, and
+ * {@link android.telecom.InCallService} implementations which provide a user interface for calls.
+ * <p>
+ * Android supports the following calling use cases (with increasing level of complexity):
+ * <ul>
+ * <li>Implement the self-managed {@link android.telecom.ConnectionService} API - this is ideal
+ * for developers of standalone calling apps which do not wish to show their calls within the
+ * default phone app, and do not wish to have other calls shown in their user interface. Using
+ * a self-managed {@link android.telecom.ConnectionService} implementation within your
+ * standalone calling app helps you ensure that your app will interoperate not only with native
+ * telephony calling on the device, but also other standalone calling apps implementing this
+ * API. It also manages audio routing and focus for you.</li>
+ * <li>Implement the managed {@link android.telecom.ConnectionService} API - facilitates
+ * development of a calling solution that relies on the existing device phone application (see
+ * {@link android.telecom.TelecomManager#getDefaultDialerPackage()}) to provide the user
+ * interface for calls. An example might be a third party implementation of SIP calling, or a
+ * VOIP calling service. A {@link android.telecom.ConnectionService} alone provides only the
+ * means of connecting calls, but has no associated user interface.</li>
+ * <li>Implement the {@link android.telecom.InCallService} API - facilitates development of a
+ * replacement for the device's default Phone/Dialer app. The
+ * {@link android.telecom.InCallService} alone does not have any calling capability and consists
+ * of the user-interface side of calling only. An {@link android.telecom.InCallService} must
+ * handle all Calls the Telecom framework is aware of. It must not make assumptions about the
+ * nature of the calls (e.g. assuming calls are SIM-based telephony calls), and should not
+ * implement calling restrictions based on any one {@link android.telecom.ConnectionService}
+ * (e.g. it should not enforce Telephony restrictions for video calls).</li>
+ * <li>Implement both the {@link android.telecom.InCallService} and
+ * {@link android.telecom.ConnectionService} API - ideal if you wish to create your own
+ * {@link android.telecom.ConnectionService} based calling solution, complete with its own
+ * full user interface, while showing all other Android calls in the same user interface. Using
+ * this approach, you must still ensure that your {@link android.telecom.InCallService} makes
+ * no assumption about the source of the calls it displays. You must also ensure that your
+ * {@link android.telecom.ConnectionService} implementation can still function without the
+ * default phone app being set to your custom {@link android.telecom.InCallService}.</li>
+ * </ul>
+ */
+package android.telecom;
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 002c3bb..b58f8bc 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -102,6 +102,8 @@
void removeExtras(String callId, in List<String> keys, in Session.Info sessionInfo);
+ void setAudioRoute(String callId, int audioRoute, in Session.Info sessionInfo);
+
void onConnectionEvent(String callId, String event, in Bundle extras,
in Session.Info sessionInfo);
}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 5c412e7..6ca0bc5 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -249,4 +249,14 @@
* @see TelecomServiceImpl#createManageBlockedNumbersIntent
**/
Intent createManageBlockedNumbersIntent();
+
+ /**
+ * @see TelecomServiceImpl#isIncomingCallPermitted
+ */
+ boolean isIncomingCallPermitted(in PhoneAccountHandle phoneAccountHandle);
+
+ /**
+ * @see TelecomServiceImpl#isOutgoingCallPermitted
+ */
+ boolean isOutgoingCallPermitted(in PhoneAccountHandle phoneAccountHandle);
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 288c9ab..6c45233 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -259,6 +259,12 @@
KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
/**
+ * Override the device's configuration for the ImsService to use for this SIM card.
+ */
+ public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING =
+ "config_ims_package_override_string";
+
+ /**
* Override the platform's notion of a network operator being considered roaming.
* Value is string array of SIDs to be considered roaming for 3GPP2 RATs.
*/
@@ -598,6 +604,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 +643,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";
@@ -878,39 +926,6 @@
public static final String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
/**
- * A list of component name of carrier signalling receivers which are interested in intent
- * android.intent.action.CARRIER_SIGNAL_REDIRECTED.
- * Example:
- * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameA</item>
- * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameB</item>
- * @hide
- */
- public static final String KEY_SIGNAL_REDIRECTION_RECEIVER_STRING_ARRAY =
- "signal_redirection_receiver_string_array";
-
- /**
- * A list of component name of carrier signalling receivers which are interested in intent
- * android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED.
- * Example:
- * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameA</item>
- * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameB</item>
- * @hide
- */
- public static final String KEY_SIGNAL_DCFAILURE_RECEIVER_STRING_ARRAY =
- "signal_dcfailure_receiver_string_array";
-
- /**
- * A list of component name of carrier signalling receivers which are interested in intent
- * android.intent.action.CARRIER_SIGNAL_PCO_VALUE.
- * Example:
- * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameA</item>
- * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameB</item>
- * @hide
- */
- public static final String KEY_SIGNAL_PCO_RECEIVER_STRING_ARRAY =
- "signal_pco_receiver_string_array";
-
- /**
* Defines carrier-specific actions which act upon
* android.intent.action.CARRIER_SIGNAL_REDIRECTED, used for customization of the
* default carrier app
@@ -963,6 +978,42 @@
"carrier_default_redirection_url_string_array";
/**
+ * Each config includes the componentName of the carrier app, followed by a list of interesting
+ * signals(declared in the manifest) which could wake up the app.
+ * @see com.android.internal.telephony.TelephonyIntents
+ * Example:
+ * <item>com.google.android.carrierAPK/.CarrierSignalReceiverA:
+ * android.intent.action.CARRIER_SIGNAL_REDIRECTED,
+ * android.intent.action.CARRIER_SIGNAL_PCO_VALUE
+ * </item>
+ * <item>com.google.android.carrierAPK/.CarrierSignalReceiverB:
+ * android.intent.action.CARRIER_SIGNAL_PCO_VALUE
+ * </item>
+ * @hide
+ */
+ public static final String KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY =
+ "carrier_app_wake_signal_config";
+
+ /**
+ * Each config includes the componentName of the carrier app, followed by a list of interesting
+ * signals for the app during run-time. The list of signals(intents) are targeting on run-time
+ * broadcast receivers only, aiming to avoid unnecessary wake-ups and should not be declared in
+ * the app's manifest.
+ * @see com.android.internal.telephony.TelephonyIntents
+ * Example:
+ * <item>com.google.android.carrierAPK/.CarrierSignalReceiverA:
+ * android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
+ * android.intent.action.CARRIER_SIGNAL_PCO_VALUE
+ * </item>
+ * <item>com.google.android.carrierAPK/.CarrierSignalReceiverB:
+ * android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+ * </item>
+ * @hide
+ */
+ public static final String KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY =
+ "carrier_app_no_wake_signal_config";
+
+ /**
* Determines whether the carrier supports making non-emergency phone calls while the phone is
* in emergency callback mode. Default value is {@code true}, meaning that non-emergency calls
* are allowed in emergency callback mode.
@@ -1305,8 +1356,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, "");
@@ -1346,6 +1402,7 @@
});
sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
+ sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
@@ -1413,10 +1470,14 @@
sDefaults.putString(KEY_RCS_CONFIG_SERVER_URL_STRING, "");
// Carrier Signalling Receivers
- sDefaults.putStringArray(KEY_SIGNAL_REDIRECTION_RECEIVER_STRING_ARRAY, null);
- sDefaults.putStringArray(KEY_SIGNAL_DCFAILURE_RECEIVER_STRING_ARRAY, null);
- sDefaults.putStringArray(KEY_SIGNAL_PCO_RECEIVER_STRING_ARRAY, null);
sDefaults.putString(KEY_CARRIER_SETUP_APP_STRING, "");
+ sDefaults.putStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
+ new String[]{
+ "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:" +
+ "android.intent.action.CARRIER_SIGNAL_REDIRECTED"
+ });
+ sDefaults.putStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null);
+
// Default carrier app configurations
sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY,
@@ -1504,12 +1565,12 @@
* moment.
* </p>
* <p>Requires that the calling app has carrier privileges.
- * @see #hasCarrierPrivileges
* <p>
* This method returns before the reload has completed, and
* {@link android.service.carrier.CarrierService#onLoadConfig} will be called from an
* arbitrary thread.
* </p>
+ * @see #hasCarrierPrivileges
*/
public void notifyConfigChangedForSubId(int subId) {
try {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index eb838fa..f53c576 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -373,13 +373,46 @@
"android.telephony.action.EMERGENCY_ASSISTANCE";
/**
+ * A boolean meta-data value indicating whether the voicemail settings should be hidden in the
+ * call settings page launched by
+ * {@link android.telecom.TelecomManager#ACTION_SHOW_CALL_SETTINGS}.
+ * Dialer implementations (see {@link android.telecom.TelecomManager#getDefaultDialerPackage()})
+ * which would also like to manage voicemail settings should set this meta-data to {@code true}
+ * in the manifest registration of their application.
+ *
+ * @see android.telecom.TelecomManager#ACTION_SHOW_CALL_SETTINGS
+ * @see #ACTION_CONFIGURE_VOICEMAIL
+ * @see #EXTRA_HIDE_PUBLIC_SETTINGS
+ */
+ public static final String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU =
+ "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
+
+ /**
* Open the voicemail settings activity to make changes to voicemail configuration.
+ *
+ * <p>
+ * The {@link #EXTRA_HIDE_PUBLIC_SETTINGS} hides settings the dialer will modify through public
+ * API if set.
+ *
+ * @see #EXTRA_HIDE_PUBLIC_SETTINGS
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CONFIGURE_VOICEMAIL =
"android.telephony.action.CONFIGURE_VOICEMAIL";
/**
+ * The boolean value indicating whether the voicemail settings activity launched by {@link
+ * #ACTION_CONFIGURE_VOICEMAIL} should hide settings accessible through public API. This is
+ * used by dialer implementations which provides their own voicemail settings UI, but still
+ * needs to expose device specific voicemail settings to the user.
+ *
+ * @see #ACTION_CONFIGURE_VOICEMAIL
+ * @see #METADATA_HIDE_VOICEMAIL_SETTINGS_MENU
+ */
+ public static final String EXTRA_HIDE_PUBLIC_SETTINGS =
+ "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
+
+ /**
* @hide
*/
public static final boolean EMERGENCY_ASSISTANCE_ENABLED = true;
@@ -388,13 +421,13 @@
* The lookup key used with the {@link #ACTION_PHONE_STATE_CHANGED} broadcast
* for a String containing the new call state.
*
- * @see #EXTRA_STATE_IDLE
- * @see #EXTRA_STATE_RINGING
- * @see #EXTRA_STATE_OFFHOOK
- *
* <p class="note">
* Retrieve with
* {@link android.content.Intent#getStringExtra(String)}.
+ *
+ * @see #EXTRA_STATE_IDLE
+ * @see #EXTRA_STATE_RINGING
+ * @see #EXTRA_STATE_OFFHOOK
*/
public static final String EXTRA_STATE = PhoneConstants.STATE_KEY;
@@ -1104,12 +1137,12 @@
/**
* Returns the neighboring cell information of the device.
*
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+ *
* @return List of NeighboringCellInfo or null if info unavailable.
*
- * <p>Requires Permission:
- * (@link android.Manifest.permission#ACCESS_COARSE_UPDATES}
- *
- * @deprecated Use (@link getAllCellInfo} which returns a superset of the information
+ * @deprecated Use {@link #getAllCellInfo} which returns a superset of the information
* from NeighboringCellInfo.
*/
@Deprecated
@@ -1655,6 +1688,11 @@
/**
* Returns a constant indicating the radio technology (network type)
* currently in use on the device for data transmission.
+ *
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ *
* @return the network type
*
* @see #NETWORK_TYPE_UNKNOWN
@@ -1673,10 +1711,6 @@
* @see #NETWORK_TYPE_LTE
* @see #NETWORK_TYPE_EHRPD
* @see #NETWORK_TYPE_HSPAP
- *
- * <p>
- * Requires Permission:
- * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
public int getDataNetworkType() {
return getDataNetworkType(getSubId());
@@ -2657,6 +2691,28 @@
return false;
}
+
+ /**
+ * Returns the package responsible of processing visual voicemail for the phone account.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
+ * READ_PHONE_STATE}
+ */
+ @Nullable
+ public String getVisualVoicemailPackageName(PhoneAccountHandle phoneAccountHandle) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony
+ .getVisualVoicemailPackageName(mContext.getOpPackageName(),
+ phoneAccountHandle);
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ return null;
+ }
+
/**
* Enables the visual voicemail SMS filter for a phone account. When the filter is
* enabled, Incoming SMS messages matching the OMTP VVM SMS interface will be redirected to the
@@ -4791,6 +4847,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) {
@@ -5809,10 +5879,17 @@
* Set the allowed carrier list for slotId
* Require system privileges. In the future we may add this to carrier APIs.
*
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+ *
+ * <p>This method works only on devices with {@link
+ * android.content.pm.PackageManager#FEATURE_TELEPHONY_CARRIERLOCK} enabled.
+ *
* @return The number of carriers set successfully. Should be length of
* carrierList on success; -1 on error.
* @hide
*/
+ @SystemApi
public int setAllowedCarriers(int slotId, List<CarrierIdentifier> carriers) {
try {
ITelephony service = getITelephony();
@@ -5821,6 +5898,8 @@
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
}
return -1;
}
@@ -5829,10 +5908,17 @@
* Get the allowed carrier list for slotId.
* Require system privileges. In the future we may add this to carrier APIs.
*
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
+ *
+ * <p>This method returns valid data on devices with {@link
+ * android.content.pm.PackageManager#FEATURE_TELEPHONY_CARRIERLOCK} enabled.
+ *
* @return List of {@link android.telephony.CarrierIdentifier}; empty list
* means all carriers are allowed.
* @hide
*/
+ @SystemApi
public List<CarrierIdentifier> getAllowedCarriers(int slotId) {
try {
ITelephony service = getITelephony();
@@ -5841,6 +5927,8 @@
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
}
return new ArrayList<CarrierIdentifier>(0);
}
diff --git a/telephony/java/android/telephony/ims/ImsServiceBase.java b/telephony/java/android/telephony/ims/ImsServiceBase.java
new file mode 100644
index 0000000..0b50eca
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsServiceBase.java
@@ -0,0 +1,35 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Base ImsService Implementation, which is used by the ImsResolver to bind.
+ * @hide
+ */
+@SystemApi
+public class ImsServiceBase extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
new file mode 100644
index 0000000..0509d60
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.ims.feature;
+
+/**
+ * Base class for all IMS features that are supported by the framework.
+ * @hide
+ */
+public class ImsFeature {
+
+ // Invalid feature value
+ public static final int INVALID = -1;
+ // ImsFeatures that are defined in the Manifests
+ public static final int EMERGENCY_MMTEL = 0;
+ public static final int MMTEL = 1;
+ public static final int RCS = 2;
+ // Total number of features defined
+ public static final int MAX = 3;
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
new file mode 100644
index 0000000..fa86a43
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -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.
+ */
+
+package com.android.ims.internal;
+
+/**
+ * {@hide}
+ */
+interface IImsServiceController {
+ void createImsFeature(int slotId, int feature);
+ void removeImsFeature(int slotId, int feature);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
new file mode 100644
index 0000000..0a36b6b
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
@@ -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.
+ */
+
+package com.android.ims.internal;
+
+/**
+ * {@hide}
+ */
+oneway interface IImsServiceFeatureListener {
+ void imsFeatureCreated(int slotId, int feature);
+ void imsFeatureRemoved(int slotId, int feature);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index c0d6768ae..ec419d7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -466,12 +466,21 @@
*/
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);
boolean isVisualVoicemailEnabled(String callingPackage,
in PhoneAccountHandle accountHandle);
+ String getVisualVoicemailPackageName(String callingPackage,
+ in PhoneAccountHandle phoneAccountHandle);
+
// Not oneway, caller needs to make sure the vaule is set before receiving a SMS
void enableVisualVoicemailSmsFilter(String callingPackage, int subId,
in VisualVoicemailSmsFilterSettings settings);
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index bcc68b3..9ffd92d 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -19,6 +19,7 @@
import android.annotation.SystemApi;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
+import android.app.Notification;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -513,6 +514,12 @@
}
@Override
+ public ComponentName startServiceInForeground(Intent service,
+ int id, Notification notification) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public boolean stopService(Intent service) {
throw new UnsupportedOperationException();
}
@@ -525,6 +532,13 @@
/** @hide */
@Override
+ public ComponentName startServiceInForegroundAsUser(Intent service,
+ int id, Notification notification, UserHandle user) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @hide */
+ @Override
public boolean stopServiceAsUser(Intent service, UserHandle user) {
throw new UnsupportedOperationException();
}
@@ -678,6 +692,13 @@
return null;
}
+ /** @hide */
+ @Override
+ public Context createContextForSplit(String splitName)
+ throws PackageManager.NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
/** {@hide} */
@Override
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index c46d4a5..fe4e330 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -16,6 +16,7 @@
package android.test.mock;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PackageInstallObserver;
@@ -43,7 +44,9 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
@@ -72,6 +75,12 @@
throw new UnsupportedOperationException();
}
+ @Override
+ public PackageInfo getPackageInfo(VersionedPackage versionedPackage,
+ int flags) throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
/** @hide */
@Override
public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
@@ -211,6 +220,11 @@
}
@Override
+ public boolean canRequestPackageInstalls() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public boolean isPermissionRevokedByPolicy(String permName, String pkgName) {
throw new UnsupportedOperationException();
}
@@ -715,8 +729,7 @@
* @hide - to match hiding in superclass
*/
@Override
- public void deletePackage(
- String packageName, IPackageDeleteObserver observer, int flags) {
+ public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
throw new UnsupportedOperationException();
}
@@ -724,8 +737,8 @@
* @hide - to match hiding in superclass
*/
@Override
- public void deletePackageAsUser(
- String packageName, IPackageDeleteObserver observer, int flags, int userId) {
+ public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer,
+ int flags, int userId) {
throw new UnsupportedOperationException();
}
@@ -818,6 +831,17 @@
throw new UnsupportedOperationException();
}
+ @Override
+ public @NonNull List<SharedLibraryInfo> getSharedLibraries(int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @hide */
+ @Override
+ public @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) {
+ throw new UnsupportedOperationException();
+ }
+
/** @hide */
@Override
public @NonNull String getServicesSystemSharedLibraryPackageName() {
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/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index d62c30d..2d7a68f 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -68,7 +68,6 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.test.AndroidTestCase;
-import android.test.FlakyTest;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
@@ -154,49 +153,32 @@
}
/**
- * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
- * will return immediately if the handler is already idle.
+ * Block until the given handler becomes idle, or until timeoutMs has passed.
*/
- private class IdleableHandlerThread extends HandlerThread {
- private IdleHandler mIdleHandler;
-
- public IdleableHandlerThread(String name) {
- super(name);
- }
-
- public void waitForIdle(int timeoutMs) {
- final ConditionVariable cv = new ConditionVariable();
- final MessageQueue queue = getLooper().getQueue();
-
+ private static void waitForIdleHandler(HandlerThread handler, int timeoutMs) {
+ final ConditionVariable cv = new ConditionVariable();
+ final MessageQueue queue = handler.getLooper().getQueue();
+ final IdleHandler idleHandler = () -> {
synchronized (queue) {
- if (queue.isIdle()) {
- return;
- }
-
- assertNull("BUG: only one idle handler allowed", mIdleHandler);
- mIdleHandler = new IdleHandler() {
- public boolean queueIdle() {
- synchronized (queue) {
- cv.open();
- mIdleHandler = null;
- return false; // Remove the handler.
- }
- }
- };
- queue.addIdleHandler(mIdleHandler);
+ cv.open();
+ return false; // Remove the idleHandler.
}
-
- if (!cv.block(timeoutMs)) {
- fail("HandlerThread " + getName() +
- " did not become idle after " + timeoutMs + " ms");
- queue.removeIdleHandler(mIdleHandler);
+ };
+ synchronized (queue) {
+ if (queue.isIdle()) {
+ return;
}
+ queue.addIdleHandler(idleHandler);
+ }
+ if (!cv.block(timeoutMs)) {
+ fail("HandlerThread " + handler.getName() +
+ " did not become idle after " + timeoutMs + " ms");
+ queue.removeIdleHandler(idleHandler);
}
}
- // Tests that IdleableHandlerThread works as expected.
@SmallTest
- public void testIdleableHandlerThread() {
+ public void testWaitForIdle() {
final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng.
// Tests that waitForIdle returns immediately if the service is already idle.
@@ -220,9 +202,9 @@
}
}
- @SmallTest
- @FlakyTest(tolerance = 3)
- public void testNotWaitingForIdleCausesRaceConditions() {
+ // This test has an inherent race condition in it, and cannot be enabled for continuous testing
+ // or presubmit tests. It is kept for manual runs and documentation purposes.
+ public void verifyThatNotWaitingForIdleCausesRaceConditions() {
// Bring up a network that we can use to send messages to ConnectivityService.
ConditionVariable cv = waitForConnectivityBroadcasts(1);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -249,7 +231,7 @@
private final WrappedNetworkMonitor mWrappedNetworkMonitor;
private final NetworkInfo mNetworkInfo;
private final NetworkCapabilities mNetworkCapabilities;
- private final IdleableHandlerThread mHandlerThread;
+ private final HandlerThread mHandlerThread;
private final ConditionVariable mDisconnected = new ConditionVariable();
private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
@@ -281,7 +263,7 @@
default:
throw new UnsupportedOperationException("unimplemented network type");
}
- mHandlerThread = new IdleableHandlerThread("Mock-" + typeName);
+ mHandlerThread = new HandlerThread("Mock-" + typeName);
mHandlerThread.start();
mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
"Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
@@ -321,7 +303,7 @@
}
public void waitForIdle(int timeoutMs) {
- mHandlerThread.waitForIdle(timeoutMs);
+ waitForIdleHandler(mHandlerThread, timeoutMs);
}
public void waitForIdle() {
@@ -648,11 +630,6 @@
}
@Override
- protected HandlerThread createHandlerThread() {
- return new IdleableHandlerThread("WrappedConnectivityService");
- }
-
- @Override
protected int getDefaultTcpRwnd() {
// Prevent wrapped ConnectivityService from trying to write to SystemProperties.
return 0;
@@ -710,7 +687,7 @@
}
public void waitForIdle(int timeoutMs) {
- ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs);
+ waitForIdleHandler(mHandlerThread, timeoutMs);
}
public void waitForIdle() {
@@ -1103,6 +1080,29 @@
UNAVAILABLE
}
+ private static class CallbackInfo {
+ public final CallbackState state;
+ public final Network network;
+ public final Object arg;
+ public CallbackInfo(CallbackState s, Network n, Object o) {
+ state = s; network = n; arg = o;
+ }
+ public String toString() {
+ return String.format("%s (%s)", state, network);
+ }
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof CallbackInfo)) return false;
+ // Ignore timeMs, since it's unpredictable.
+ CallbackInfo other = (CallbackInfo) o;
+ return (state == other.state) && Objects.equals(network, other.network);
+ }
+ @Override
+ public int hashCode() {
+ return Objects.hash(state, network);
+ }
+ }
+
/**
* Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks
* this class receives, by calling expectCallback() exactly once each time a callback is
@@ -1112,23 +1112,8 @@
// Chosen to be much less than the linger timeout. This ensures that we can distinguish
// between a LOST callback that arrives immediately and a LOST callback that arrives after
// the linger timeout.
- private final static int TIMEOUT_MS = 50;
+ private final static int TIMEOUT_MS = 100;
- private class CallbackInfo {
- public final CallbackState state;
- public final Network network;
- public Object arg;
- public CallbackInfo(CallbackState s, Network n, Object o) {
- state = s; network = n; arg = o;
- }
- public String toString() { return String.format("%s (%s)", state, network); }
- public boolean equals(Object o) {
- if (!(o instanceof CallbackInfo)) return false;
- // Ignore timeMs, since it's unpredictable.
- CallbackInfo other = (CallbackInfo) o;
- return state == other.state && Objects.equals(network, other.network);
- }
- }
private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
protected void setLastCallback(CallbackState state, Network network, Object o) {
@@ -1155,17 +1140,24 @@
setLastCallback(CallbackState.LOST, network, null);
}
+ CallbackInfo nextCallback(int timeoutMs) {
+ CallbackInfo cb = null;
+ try {
+ cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ }
+ if (cb == null) {
+ // LinkedBlockingQueue.poll() returns null if it timeouts.
+ fail("Did not receive callback after " + timeoutMs + "ms");
+ }
+ return cb;
+ }
+
void expectCallback(CallbackState state, MockNetworkAgent mockAgent, int timeoutMs) {
CallbackInfo expected = new CallbackInfo(
state, (mockAgent != null) ? mockAgent.getNetwork() : null, 0);
- CallbackInfo actual;
- try {
- actual = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
- assertEquals("Unexpected callback:", expected, actual);
- } catch (InterruptedException e) {
- fail("Did not receive expected " + expected + " after " + TIMEOUT_MS + "ms");
- actual = null; // Or the compiler can't tell it's never used uninitialized.
- }
+ CallbackInfo actual = nextCallback(timeoutMs);
+ assertEquals("Unexpected callback:", expected, actual);
if (state == CallbackState.LOSING) {
String msg = String.format(
"Invalid linger time value %d, must be between %d and %d",
@@ -1472,8 +1464,8 @@
// Let linger run its course.
callback.assertNoCallback();
- callback.expectCallback(CallbackState.LOST, mCellNetworkAgent,
- TEST_LINGER_DELAY_MS /* timeoutMs */);
+ final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+ callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
// Clean up.
mWiFiNetworkAgent.disconnect();
@@ -1962,7 +1954,9 @@
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
// When lingering is complete, cell is still there but is now in the background.
- fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, TEST_LINGER_DELAY_MS);
+ mService.waitForIdle();
+ int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+ fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
callback.assertNoCallback();
assertFalse(isForegroundNetwork(mCellNetworkAgent));
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -1988,6 +1982,7 @@
// Disconnect wifi and check that cell is foreground again.
mWiFiNetworkAgent.disconnect();
+ mService.waitForIdle();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
@@ -2324,14 +2319,14 @@
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- mCm.requestNetwork(nr, networkCallback, 10);
+ final int timeoutMs = 150;
+ mCm.requestNetwork(nr, networkCallback, timeoutMs);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, timeoutMs);
// pass timeout and validate that UNAVAILABLE is not called
- sleepFor(15);
networkCallback.assertNoCallback();
}
@@ -2344,17 +2339,19 @@
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- mCm.requestNetwork(nr, networkCallback, 500);
+ final int requestTimeoutMs = 100;
+ mCm.requestNetwork(nr, networkCallback, requestTimeoutMs);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ final int assertTimeoutMs = 150;
+ networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, assertTimeoutMs);
sleepFor(20);
mWiFiNetworkAgent.disconnect();
networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// pass timeout and validate that UNAVAILABLE is not called
- sleepFor(600);
+ sleepFor(100);
networkCallback.assertNoCallback();
}
@@ -2368,7 +2365,8 @@
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- mCm.requestNetwork(nr, networkCallback, 10);
+ final int timeoutMs = 10;
+ mCm.requestNetwork(nr, networkCallback, timeoutMs);
// pass timeout and validate that UNAVAILABLE is called
networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
@@ -2388,7 +2386,8 @@
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- mCm.requestNetwork(nr, networkCallback, 10);
+ final int timeoutMs = 10;
+ mCm.requestNetwork(nr, networkCallback, timeoutMs);
// remove request
mCm.unregisterNetworkCallback(networkCallback);
@@ -2405,13 +2404,13 @@
networkCallback.assertNoCallback();
}
- public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) throws Exception {
+ public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) {
long start = SystemClock.elapsedRealtime();
while (SystemClock.elapsedRealtime() <= start + maxWaitingTimeMs) {
if (fn.getAsBoolean()) {
return;
}
- Thread.sleep(10);
+ sleepFor(15);
}
assertTrue(fn.getAsBoolean());
}
@@ -2579,7 +2578,9 @@
callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
// ... and that stopping it after that has no adverse effects.
- assertNull(mCm.getNetworkCapabilities(myNet));
+ // TODO: investigate assertEventuallyTrue is needed and waitForIdle() is not enough
+ final Network myNetAlias = myNet;
+ assertEventuallyTrue(() -> mCm.getNetworkCapabilities(myNetAlias) == null, 100);
ka.stop();
// Reconnect.
@@ -2823,11 +2824,11 @@
}
/* test utilities */
+ // TODO: eliminate all usages of sleepFor and replace by proper timeouts/waitForIdle.
static private void sleepFor(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
-
}
}
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 00420e9..1e67769 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.reset;
import android.content.Context;
@@ -69,12 +70,13 @@
@Test
public void testDoesNothingBeforeStarted() {
- UpstreamNetworkMonitor unm = new UpstreamNetworkMonitor(null, null, EVENT_UNM_UPDATE);
- assertFalse(unm.mobileNetworkRequested());
- // Given a null Context, and therefore a null ConnectivityManager,
- // these would cause an exception, if they actually attempted anything.
- unm.mobileUpstreamRequiresDun(true);
- unm.mobileUpstreamRequiresDun(false);
+ assertTrue(mCM.hasNoCallbacks());
+ assertFalse(mUNM.mobileNetworkRequested());
+
+ mUNM.updateMobileRequiresDun(true);
+ assertTrue(mCM.hasNoCallbacks());
+ mUNM.updateMobileRequiresDun(false);
+ assertTrue(mCM.hasNoCallbacks());
}
@Test
@@ -85,7 +87,7 @@
assertEquals(1, mCM.trackingDefault.size());
mUNM.stop();
- assertTrue(mCM.isEmpty());
+ assertTrue(mCM.hasNoCallbacks());
}
@Test
@@ -97,11 +99,11 @@
assertTrue(mCM.isListeningForDun());
mUNM.stop();
- assertTrue(mCM.isEmpty());
+ assertTrue(mCM.hasNoCallbacks());
}
@Test
- public void testCanRequestMobileNetwork() throws Exception {
+ public void testRequestsMobileNetwork() throws Exception {
assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size());
@@ -109,25 +111,22 @@
assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size());
- mUNM.mobileUpstreamRequiresDun(false);
+ mUNM.updateMobileRequiresDun(false);
assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size());
mUNM.registerMobileNetworkRequest();
assertTrue(mUNM.mobileNetworkRequested());
- assertEquals(1, mCM.requested.size());
- assertEquals(1, mCM.legacyTypeMap.size());
- assertEquals(Integer.valueOf(TYPE_MOBILE_HIPRI),
- mCM.legacyTypeMap.values().iterator().next());
+ assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
assertFalse(mCM.isDunRequested());
mUNM.stop();
assertFalse(mUNM.mobileNetworkRequested());
- assertTrue(mCM.isEmpty());
+ assertTrue(mCM.hasNoCallbacks());
}
@Test
- public void testCanRequestDunNetwork() throws Exception {
+ public void testRequestsDunNetwork() throws Exception {
assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size());
@@ -135,21 +134,50 @@
assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size());
- mUNM.mobileUpstreamRequiresDun(true);
+ mUNM.updateMobileRequiresDun(true);
assertFalse(mUNM.mobileNetworkRequested());
assertEquals(0, mCM.requested.size());
mUNM.registerMobileNetworkRequest();
assertTrue(mUNM.mobileNetworkRequested());
- assertEquals(1, mCM.requested.size());
- assertEquals(1, mCM.legacyTypeMap.size());
- assertEquals(Integer.valueOf(TYPE_MOBILE_DUN),
- mCM.legacyTypeMap.values().iterator().next());
+ assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
assertTrue(mCM.isDunRequested());
mUNM.stop();
assertFalse(mUNM.mobileNetworkRequested());
- assertTrue(mCM.isEmpty());
+ assertTrue(mCM.hasNoCallbacks());
+ }
+
+ @Test
+ public void testUpdateMobileRequiredDun() throws Exception {
+ mUNM.start();
+
+ // Test going from no-DUN to DUN correctly re-registers callbacks.
+ mUNM.updateMobileRequiresDun(false);
+ mUNM.registerMobileNetworkRequest();
+ assertTrue(mUNM.mobileNetworkRequested());
+ assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
+ assertFalse(mCM.isDunRequested());
+ mUNM.updateMobileRequiresDun(true);
+ assertTrue(mUNM.mobileNetworkRequested());
+ assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
+ assertTrue(mCM.isDunRequested());
+
+ // Test going from DUN to no-DUN correctly re-registers callbacks.
+ mUNM.updateMobileRequiresDun(false);
+ assertTrue(mUNM.mobileNetworkRequested());
+ assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
+ assertFalse(mCM.isDunRequested());
+
+ mUNM.stop();
+ assertFalse(mUNM.mobileNetworkRequested());
+ }
+
+ private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
+ assertEquals(1, mCM.requested.size());
+ assertEquals(1, mCM.legacyTypeMap.size());
+ assertEquals(Integer.valueOf(upstreamType),
+ mCM.legacyTypeMap.values().iterator().next());
}
private static class TestConnectivityManager extends ConnectivityManager {
@@ -162,7 +190,7 @@
super(ctx, svc);
}
- boolean isEmpty() {
+ boolean hasNoCallbacks() {
return trackingDefault.isEmpty() &&
listening.isEmpty() &&
requested.isEmpty() &&
@@ -225,6 +253,8 @@
} else if (requested.containsKey(cb)) {
requested.remove(cb);
legacyTypeMap.remove(cb);
+ } else {
+ fail("Unexpected callback removed");
}
assertFalse(trackingDefault.contains(cb));
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
index 565d2f0..0aca45e 100644
--- a/tools/aapt/AaptConfig.cpp
+++ b/tools/aapt/AaptConfig.cpp
@@ -131,6 +131,22 @@
part = parts[index].string();
}
+ if (parseWideColorGamut(part, &config)) {
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index].string();
+ }
+
+ if (parseHdr(part, &config)) {
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index].string();
+ }
+
if (parseOrientation(part, &config)) {
index++;
if (index == N) {
@@ -250,7 +266,9 @@
uint16_t minSdk = 0;
if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
- == ResTable_config::UI_MODE_TYPE_VR_HEADSET) {
+ == ResTable_config::UI_MODE_TYPE_VR_HEADSET
+ || 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;
@@ -431,6 +449,46 @@
return false;
}
+bool parseWideColorGamut(const char* name, ResTable_config* out) {
+ if (strcmp(name, kWildcardName) == 0) {
+ 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->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->colorMode =
+ (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
+ | ResTable_config::WIDE_COLOR_GAMUT_NO;
+ return true;
+ }
+ return false;
+}
+
+bool parseHdr(const char* name, ResTable_config* out) {
+ if (strcmp(name, kWildcardName) == 0) {
+ 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->colorMode =
+ (out->colorMode&~ResTable_config::MASK_HDR)
+ | ResTable_config::HDR_YES;
+ return true;
+ } else if (strcmp(name, "lowdr") == 0) {
+ if (out) out->colorMode =
+ (out->colorMode&~ResTable_config::MASK_HDR)
+ | ResTable_config::HDR_NO;
+ return true;
+ }
+ return false;
+}
+
bool parseOrientation(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out) out->orientation = out->ORIENTATION_ANY;
diff --git a/tools/aapt/AaptConfig.h b/tools/aapt/AaptConfig.h
index 04c763f..a6dd252 100644
--- a/tools/aapt/AaptConfig.h
+++ b/tools/aapt/AaptConfig.h
@@ -61,6 +61,8 @@
bool parseScreenLayoutSize(const char* str, android::ResTable_config* out = NULL);
bool parseScreenLayoutLong(const char* str, android::ResTable_config* out = NULL);
bool parseScreenRound(const char* name, android::ResTable_config* out = NULL);
+bool parseWideColorGamut(const char* name, android::ResTable_config* out = NULL);
+bool parseHdr(const char* name, android::ResTable_config* out = NULL);
bool parseOrientation(const char* str, android::ResTable_config* out = NULL);
bool parseUiModeType(const char* str, android::ResTable_config* out = NULL);
bool parseUiModeNight(const char* str, android::ResTable_config* out = NULL);
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 75a3160..cf5badc 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -211,7 +211,7 @@
bool isValidResourceType(const String8& type)
{
return type == "anim" || type == "animator" || type == "interpolator"
- || type == "transition"
+ || type == "transition" || type == "font"
|| type == "drawable" || type == "layout"
|| type == "values" || type == "xml" || type == "raw"
|| type == "color" || type == "menu" || type == "mipmap";
@@ -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 8bb38ba..4f22fa5 100644
--- a/tools/aapt/tests/AaptConfig_test.cpp
+++ b/tools/aapt/tests/AaptConfig_test.cpp
@@ -98,3 +98,33 @@
EXPECT_EQ(SDK_MNC, config.sdkVersion);
EXPECT_EQ(String8("notround-v23"), config.toString());
}
+
+TEST(AaptConfigTest, WideColorGamutQualifier) {
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("widecg", &config));
+ EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES,
+ 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.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+ EXPECT_EQ(SDK_O, config.sdkVersion);
+ EXPECT_EQ(String8("nowidecg-v26"), config.toString());
+}
+
+TEST(AaptConfigTest, HdrQualifier) {
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("highdr", &config));
+ EXPECT_EQ(android::ResTable_config::HDR_YES,
+ 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.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 b1bd401..46098cb 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -20,15 +20,16 @@
#include <vector>
#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
#include "Locale.h"
#include "SdkConstants.h"
-#include "util/StringPiece.h"
#include "util/Util.h"
-namespace aapt {
-
using android::ResTable_config;
+using android::StringPiece;
+
+namespace aapt {
static const char* kWildcardName = "any";
@@ -205,6 +206,52 @@
return false;
}
+static bool parseWideColorGamut(const char* name, ResTable_config* out) {
+ if (strcmp(name, kWildcardName) == 0) {
+ 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->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->colorMode =
+ (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+ ResTable_config::WIDE_COLOR_GAMUT_NO;
+ return true;
+ }
+ return false;
+}
+
+static bool parseHdr(const char* name, ResTable_config* out) {
+ if (strcmp(name, kWildcardName) == 0) {
+ 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->colorMode =
+ (out->colorMode & ~ResTable_config::MASK_HDR) |
+ ResTable_config::HDR_YES;
+ return true;
+ } else if (strcmp(name, "lowdr") == 0) {
+ if (out)
+ out->colorMode =
+ (out->colorMode & ~ResTable_config::MASK_HDR) |
+ ResTable_config::HDR_NO;
+ return true;
+ }
+ return false;
+}
+
static bool parseOrientation(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out) out->orientation = out->ORIENTATION_ANY;
@@ -686,6 +733,20 @@
}
}
+ if (parseWideColorGamut(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
+ if (parseHdr(part_iter->c_str(), &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
if (parseOrientation(part_iter->c_str(), &config)) {
++part_iter;
if (part_iter == parts_end) {
@@ -778,7 +839,9 @@
ConfigDescription* config) {
uint16_t min_sdk = 0;
if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
- == ResTable_config::UI_MODE_TYPE_VR_HEADSET) {
+ == ResTable_config::UI_MODE_TYPE_VR_HEADSET ||
+ 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;
@@ -849,6 +912,12 @@
if ((screenLayout2 | o.screenLayout2) & MASK_SCREENROUND) {
return !(o.screenLayout2 & MASK_SCREENROUND);
}
+ if ((colorMode | o.colorMode) & MASK_HDR) {
+ return !(o.colorMode & MASK_HDR);
+ }
+ 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) {
return !(o.uiMode & MASK_UI_MODE_TYPE);
@@ -895,6 +964,9 @@
!pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) ||
!pred(screenLayout2 & MASK_SCREENROUND,
o.screenLayout2 & MASK_SCREENROUND) ||
+ !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.h b/tools/aapt2/ConfigDescription.h
index 97d0f38..65c9617 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -20,8 +20,7 @@
#include <ostream>
#include "androidfw/ResourceTypes.h"
-
-#include "util/StringPiece.h"
+#include "androidfw/StringPiece.h"
namespace aapt {
@@ -42,7 +41,7 @@
* The resulting configuration has the appropriate sdkVersion defined
* for backwards compatibility.
*/
- static bool Parse(const StringPiece& str, ConfigDescription* out = nullptr);
+ static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr);
/**
* If the configuration uses an axis that was added after
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index 1d22ce0..14a5656 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -18,9 +18,12 @@
#include <string>
+#include "androidfw/StringPiece.h"
+
#include "SdkConstants.h"
#include "test/Test.h"
-#include "util/StringPiece.h"
+
+using android::StringPiece;
namespace aapt {
@@ -99,6 +102,36 @@
EXPECT_EQ(std::string("notround-v23"), config.toString().string());
}
+TEST(ConfigDescriptionTest, TestWideColorGamutQualifier) {
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("widecg", &config));
+ EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES,
+ 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.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+ EXPECT_EQ(SDK_O, config.sdkVersion);
+ EXPECT_EQ(std::string("nowidecg-v26"), config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, TestHdrQualifier) {
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("highdr", &config));
+ EXPECT_EQ(android::ResTable_config::HDR_YES,
+ 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.colorMode & android::ResTable_config::MASK_HDR);
+ EXPECT_EQ(SDK_O, config.sdkVersion);
+ EXPECT_EQ(std::string("lowdr-v26"), config.toString().string());
+}
+
TEST(ConfigDescriptionTest, ParseVrAttribute) {
ConfigDescription config;
EXPECT_TRUE(TestParse("vrheadset", &config));
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
index 5bc86a9..50e8b33 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -22,9 +22,9 @@
#include <string>
#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
#include "Source.h"
-#include "util/StringPiece.h"
#include "util/Util.h"
namespace aapt {
@@ -38,7 +38,7 @@
public:
DiagMessage() = default;
- explicit DiagMessage(const StringPiece& src) : source_(src) {}
+ explicit DiagMessage(const android::StringPiece& src) : source_(src) {}
explicit DiagMessage(const Source& src) : source_(src) {}
@@ -59,6 +59,12 @@
std::stringstream message_;
};
+template <>
+inline DiagMessage& DiagMessage::operator<<(const ::std::u16string& value) {
+ message_ << android::StringPiece16(value);
+ return *this;
+}
+
struct IDiagnostics {
virtual ~IDiagnostics() = default;
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
index c98cd37..84977ab 100644
--- a/tools/aapt2/Flags.cpp
+++ b/tools/aapt2/Flags.cpp
@@ -21,20 +21,22 @@
#include <string>
#include <vector>
-#include "util/StringPiece.h"
+#include "androidfw/StringPiece.h"
+
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
Flags& Flags::RequiredFlag(const StringPiece& name,
const StringPiece& description, std::string* value) {
auto func = [value](const StringPiece& arg) -> bool {
- *value = arg.ToString();
+ *value = arg.to_string();
return true;
};
- flags_.push_back(
- Flag{name.ToString(), description.ToString(), func, true, 1, false});
+ flags_.push_back(Flag{name.to_string(), description.to_string(), func, true, 1, false});
return *this;
}
@@ -42,12 +44,11 @@
const StringPiece& description,
std::vector<std::string>* value) {
auto func = [value](const StringPiece& arg) -> bool {
- value->push_back(arg.ToString());
+ value->push_back(arg.to_string());
return true;
};
- flags_.push_back(
- Flag{name.ToString(), description.ToString(), func, true, 1, false});
+ flags_.push_back(Flag{name.to_string(), description.to_string(), func, true, 1, false});
return *this;
}
@@ -55,12 +56,11 @@
const StringPiece& description,
Maybe<std::string>* value) {
auto func = [value](const StringPiece& arg) -> bool {
- *value = arg.ToString();
+ *value = arg.to_string();
return true;
};
- flags_.push_back(
- Flag{name.ToString(), description.ToString(), func, false, 1, false});
+ flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false});
return *this;
}
@@ -68,12 +68,11 @@
const StringPiece& description,
std::vector<std::string>* value) {
auto func = [value](const StringPiece& arg) -> bool {
- value->push_back(arg.ToString());
+ value->push_back(arg.to_string());
return true;
};
- flags_.push_back(
- Flag{name.ToString(), description.ToString(), func, false, 1, false});
+ flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false});
return *this;
}
@@ -81,12 +80,11 @@
const StringPiece& description,
std::unordered_set<std::string>* value) {
auto func = [value](const StringPiece& arg) -> bool {
- value->insert(arg.ToString());
+ value->insert(arg.to_string());
return true;
};
- flags_.push_back(
- Flag{name.ToString(), description.ToString(), func, false, 1, false});
+ flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false});
return *this;
}
@@ -97,8 +95,7 @@
return true;
};
- flags_.push_back(
- Flag{name.ToString(), description.ToString(), func, false, 0, false});
+ flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 0, false});
return *this;
}
@@ -141,7 +138,7 @@
for (size_t i = 0; i < args.size(); i++) {
StringPiece arg = args[i];
if (*(arg.data()) != '-') {
- args_.push_back(arg.ToString());
+ args_.push_back(arg.to_string());
continue;
}
diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h
index 9feff6b..3b3ae71 100644
--- a/tools/aapt2/Flags.h
+++ b/tools/aapt2/Flags.h
@@ -23,32 +23,30 @@
#include <unordered_set>
#include <vector>
+#include "androidfw/StringPiece.h"
+
#include "util/Maybe.h"
-#include "util/StringPiece.h"
namespace aapt {
class Flags {
public:
- Flags& RequiredFlag(const StringPiece& name, const StringPiece& description,
+ Flags& RequiredFlag(const android::StringPiece& name, const android::StringPiece& description,
std::string* value);
- Flags& RequiredFlagList(const StringPiece& name,
- const StringPiece& description,
+ Flags& RequiredFlagList(const android::StringPiece& name, const android::StringPiece& description,
std::vector<std::string>* value);
- Flags& OptionalFlag(const StringPiece& name, const StringPiece& description,
+ Flags& OptionalFlag(const android::StringPiece& name, const android::StringPiece& description,
Maybe<std::string>* value);
- Flags& OptionalFlagList(const StringPiece& name,
- const StringPiece& description,
+ Flags& OptionalFlagList(const android::StringPiece& name, const android::StringPiece& description,
std::vector<std::string>* value);
- Flags& OptionalFlagList(const StringPiece& name,
- const StringPiece& description,
+ Flags& OptionalFlagList(const android::StringPiece& name, const android::StringPiece& description,
std::unordered_set<std::string>* value);
- Flags& OptionalSwitch(const StringPiece& name, const StringPiece& description,
+ Flags& OptionalSwitch(const android::StringPiece& name, const android::StringPiece& description,
bool* value);
- void Usage(const StringPiece& command, std::ostream* out);
+ void Usage(const android::StringPiece& command, std::ostream* out);
- bool Parse(const StringPiece& command, const std::vector<StringPiece>& args,
+ bool Parse(const android::StringPiece& command, const std::vector<android::StringPiece>& args,
std::ostream* outError);
const std::vector<std::string>& GetArgs();
@@ -57,7 +55,7 @@
struct Flag {
std::string name;
std::string description;
- std::function<bool(const StringPiece& value)> action;
+ std::function<bool(const android::StringPiece& value)> action;
bool required;
size_t num_args;
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index 78f56c7a..7664fac 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -72,7 +72,7 @@
return std::all_of(std::begin(str), std::end(str), ::isdigit);
}
-bool LocaleValue::InitFromFilterString(const StringPiece& str) {
+bool LocaleValue::InitFromFilterString(const android::StringPiece& str) {
// A locale (as specified in the filter) is an underscore separated name such
// as "en_US", "en_Latn_US", or "en_US_POSIX".
std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h
index fc6c448..3d73b2e 100644
--- a/tools/aapt2/Locale.h
+++ b/tools/aapt2/Locale.h
@@ -21,8 +21,7 @@
#include <vector>
#include "androidfw/ResourceTypes.h"
-
-#include "util/StringPiece.h"
+#include "androidfw/StringPiece.h"
namespace aapt {
@@ -40,7 +39,7 @@
/**
* Initialize this LocaleValue from a config string.
*/
- bool InitFromFilterString(const StringPiece& config);
+ bool InitFromFilterString(const android::StringPiece& config);
/**
* Initialize this LocaleValue from parts of a vector.
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index a2b216d..15d7e2e 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -17,7 +17,7 @@
#include <iostream>
#include <vector>
-#include "util/StringPiece.h"
+#include "androidfw/StringPiece.h"
namespace aapt {
@@ -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 << "."
@@ -33,10 +33,10 @@
return 0;
}
-extern int Compile(const std::vector<StringPiece>& args);
-extern int Link(const std::vector<StringPiece>& args);
-extern int Dump(const std::vector<StringPiece>& args);
-extern int Diff(const std::vector<StringPiece>& args);
+extern int Compile(const std::vector<android::StringPiece>& args);
+extern int Link(const std::vector<android::StringPiece>& args);
+extern int Dump(const std::vector<android::StringPiece>& args);
+extern int Diff(const std::vector<android::StringPiece>& args);
} // namespace aapt
@@ -45,12 +45,12 @@
argv += 1;
argc -= 1;
- std::vector<aapt::StringPiece> args;
+ std::vector<android::StringPiece> args;
for (int i = 1; i < argc; i++) {
args.push_back(argv[i]);
}
- aapt::StringPiece command(argv[0]);
+ android::StringPiece command(argv[0]);
if (command == "compile" || command == "c") {
return aapt::Compile(args);
} else if (command == "link" || command == "l") {
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index 3eef7aa7..fdabce1 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -19,6 +19,8 @@
#include <map>
#include <string>
+using android::StringPiece;
+
namespace aapt {
StringPiece ToString(ResourceType type) {
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 13330b5..1950ea3 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -24,11 +24,11 @@
#include <tuple>
#include <vector>
+#include "androidfw/StringPiece.h"
#include "utils/JenkinsHash.h"
#include "ConfigDescription.h"
#include "Source.h"
-#include "util/StringPiece.h"
namespace aapt {
@@ -63,13 +63,13 @@
kXml,
};
-StringPiece ToString(ResourceType type);
+android::StringPiece ToString(ResourceType type);
/**
* Returns a pointer to a valid ResourceType, or nullptr if
* the string was invalid.
*/
-const ResourceType* ParseResourceType(const StringPiece& str);
+const ResourceType* ParseResourceType(const android::StringPiece& str);
/**
* A resource's name. This can uniquely identify
@@ -81,7 +81,7 @@
std::string entry;
ResourceName() = default;
- ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e);
+ ResourceName(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
int compare(const ResourceName& other) const;
@@ -96,15 +96,15 @@
* of the original string.
*/
struct ResourceNameRef {
- StringPiece package;
+ android::StringPiece package;
ResourceType type = ResourceType::kRaw;
- StringPiece entry;
+ android::StringPiece entry;
ResourceNameRef() = default;
ResourceNameRef(const ResourceNameRef&) = default;
ResourceNameRef(ResourceNameRef&&) = default;
ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit)
- ResourceNameRef(const StringPiece& p, ResourceType t, const StringPiece& e);
+ ResourceNameRef(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
ResourceNameRef& operator=(const ResourceName& rhs);
@@ -258,9 +258,9 @@
// ResourceName implementation.
//
-inline ResourceName::ResourceName(const StringPiece& p, ResourceType t,
- const StringPiece& e)
- : package(p.ToString()), type(t), entry(e.ToString()) {}
+inline ResourceName::ResourceName(const android::StringPiece& p, ResourceType t,
+ const android::StringPiece& e)
+ : package(p.to_string()), type(t), entry(e.to_string()) {}
inline int ResourceName::compare(const ResourceName& other) const {
int cmp = package.compare(other.package);
@@ -311,8 +311,8 @@
inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs)
: package(rhs.package), type(rhs.type), entry(rhs.entry) {}
-inline ResourceNameRef::ResourceNameRef(const StringPiece& p, ResourceType t,
- const StringPiece& e)
+inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, ResourceType t,
+ const android::StringPiece& e)
: package(p), type(t), entry(e) {}
inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index b16def4..79379fe 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -29,6 +29,8 @@
#include "util/Util.h"
#include "xml/XmlPullParser.h"
+using android::StringPiece;
+
namespace aapt {
constexpr const char* sXliffNamespaceUri =
@@ -101,7 +103,7 @@
StringPiece trimmed_comment = util::TrimWhitespace(res->comment);
if (trimmed_comment.size() != res->comment.size()) {
// Only if there was a change do we re-assign.
- res->comment = trimmed_comment.ToString();
+ res->comment = trimmed_comment.to_string();
}
if (res->symbol_state) {
@@ -297,7 +299,7 @@
// Extract the product name if it exists.
if (Maybe<StringPiece> maybe_product =
xml::FindNonEmptyAttribute(parser, "product")) {
- parsed_resource.product = maybe_product.value().ToString();
+ parsed_resource.product = maybe_product.value().to_string();
}
// Parse the resource regardless of product.
@@ -383,7 +385,7 @@
// Items have their type encoded in the type attribute.
if (Maybe<StringPiece> maybe_type =
xml::FindNonEmptyAttribute(parser, "type")) {
- resource_type = maybe_type.value().ToString();
+ resource_type = maybe_type.value().to_string();
} else {
diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
<< "<item> must have a 'type' attribute");
@@ -419,7 +421,7 @@
}
out_resource->name.type = ResourceType::kId;
- out_resource->name.entry = maybe_name.value().ToString();
+ out_resource->name.entry = maybe_name.value().to_string();
out_resource->value = util::make_unique<Id>();
return true;
}
@@ -436,7 +438,7 @@
}
out_resource->name.type = item_iter->second.type;
- out_resource->name.entry = maybe_name.value().ToString();
+ out_resource->name.entry = maybe_name.value().to_string();
// Only use the implicit format for this type if it wasn't overridden.
if (!resource_format) {
@@ -461,7 +463,7 @@
return false;
}
- out_resource->name.entry = maybe_name.value().ToString();
+ out_resource->name.entry = maybe_name.value().to_string();
}
// Call the associated parse method. The type will be filled in by the
@@ -484,7 +486,7 @@
}
out_resource->name.type = *parsed_type;
- out_resource->name.entry = maybe_name.value().ToString();
+ out_resource->name.entry = maybe_name.value().to_string();
out_resource->value =
ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
if (!out_resource->value) {
@@ -718,7 +720,7 @@
const size_t depth = parser->depth();
while (xml::XmlPullParser::NextChildNode(parser, depth)) {
if (parser->event() == xml::XmlPullParser::Event::kComment) {
- comment = util::TrimWhitespace(parser->comment()).ToString();
+ comment = util::TrimWhitespace(parser->comment()).to_string();
continue;
} else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
// Skip text.
@@ -754,7 +756,7 @@
ParsedResource child_resource;
child_resource.name.type = *parsed_type;
- child_resource.name.entry = maybe_name.value().ToString();
+ child_resource.name.entry = maybe_name.value().to_string();
child_resource.id = next_id;
child_resource.comment = std::move(comment);
child_resource.source = item_source;
@@ -899,7 +901,7 @@
const size_t depth = parser->depth();
while (xml::XmlPullParser::NextChildNode(parser, depth)) {
if (parser->event() == xml::XmlPullParser::Event::kComment) {
- comment = util::TrimWhitespace(parser->comment()).ToString();
+ comment = util::TrimWhitespace(parser->comment()).to_string();
continue;
} else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
// Skip text.
@@ -1288,7 +1290,7 @@
const size_t depth = parser->depth();
while (xml::XmlPullParser::NextChildNode(parser, depth)) {
if (parser->event() == xml::XmlPullParser::Event::kComment) {
- comment = util::TrimWhitespace(parser->comment()).ToString();
+ comment = util::TrimWhitespace(parser->comment()).to_string();
continue;
} else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
// Ignore text.
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 11b1e5b..c12dacf 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -20,6 +20,7 @@
#include <memory>
#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
#include "ConfigDescription.h"
#include "Diagnostics.h"
@@ -27,7 +28,6 @@
#include "ResourceValues.h"
#include "StringPool.h"
#include "util/Maybe.h"
-#include "util/StringPiece.h"
#include "xml/XmlPullParser.h"
namespace aapt {
@@ -101,7 +101,7 @@
bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
bool weak);
Maybe<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
- const StringPiece& tag);
+ const android::StringPiece& tag);
bool ParseStyle(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
bool ParseDeclareStyleable(xml::XmlPullParser* parser,
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 2463911..5762fb0 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -25,6 +25,8 @@
#include "test/Test.h"
#include "xml/XmlPullParser.h"
+using android::StringPiece;
+
namespace aapt {
constexpr const char* kXmlPreamble =
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 4e6a50a..dd78750 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -28,6 +28,8 @@
#include <string>
#include <tuple>
+using android::StringPiece;
+
namespace aapt {
static bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs,
@@ -87,7 +89,7 @@
std::unique_ptr<ResourceTablePackage> new_package =
util::make_unique<ResourceTablePackage>();
- new_package->name = name.ToString();
+ new_package->name = name.to_string();
return packages.emplace(iter, std::move(new_package))->get();
}
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index a0c3217..0fe966c 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -25,7 +25,9 @@
#include "StringPool.h"
#include "io/File.h"
-#include <android-base/macros.h>
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
#include <functional>
#include <map>
#include <memory>
@@ -68,9 +70,8 @@
*/
std::unique_ptr<Value> value;
- ResourceConfigValue(const ConfigDescription& config,
- const StringPiece& product)
- : config(config), product(product.ToString()) {}
+ ResourceConfigValue(const ConfigDescription& config, const android::StringPiece& product)
+ : config(config), product(product.to_string()) {}
private:
DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
@@ -105,13 +106,13 @@
*/
std::vector<std::unique_ptr<ResourceConfigValue>> values;
- explicit ResourceEntry(const StringPiece& name) : name(name.ToString()) {}
+ explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
ResourceConfigValue* FindValue(const ConfigDescription& config);
ResourceConfigValue* FindValue(const ConfigDescription& config,
- const StringPiece& product);
+ const android::StringPiece& product);
ResourceConfigValue* FindOrCreateValue(const ConfigDescription& config,
- const StringPiece& product);
+ const android::StringPiece& product);
std::vector<ResourceConfigValue*> findAllValues(
const ConfigDescription& config);
std::vector<ResourceConfigValue*> FindValuesIf(
@@ -150,8 +151,8 @@
explicit ResourceTableType(const ResourceType type) : type(type) {}
- ResourceEntry* FindEntry(const StringPiece& name);
- ResourceEntry* FindOrCreateEntry(const StringPiece& name);
+ ResourceEntry* FindEntry(const android::StringPiece& name);
+ ResourceEntry* FindOrCreateEntry(const android::StringPiece& name);
private:
DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
@@ -195,22 +196,19 @@
Value* incoming);
bool AddResource(const ResourceNameRef& name, const ConfigDescription& config,
- const StringPiece& product, std::unique_ptr<Value> value,
+ const android::StringPiece& product, std::unique_ptr<Value> value,
IDiagnostics* diag);
bool AddResource(const ResourceNameRef& name, const ResourceId& res_id,
- const ConfigDescription& config, const StringPiece& product,
+ const ConfigDescription& config, const android::StringPiece& product,
std::unique_ptr<Value> value, IDiagnostics* diag);
- bool AddFileReference(const ResourceNameRef& name,
- const ConfigDescription& config, const Source& source,
- const StringPiece& path, IDiagnostics* diag);
+ bool AddFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+ const Source& source, const android::StringPiece& path, IDiagnostics* diag);
- bool AddFileReferenceAllowMangled(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source,
- const StringPiece& path, io::IFile* file,
- IDiagnostics* diag);
+ bool AddFileReferenceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
+ const Source& source, const android::StringPiece& path,
+ io::IFile* file, IDiagnostics* diag);
/**
* Same as AddResource, but doesn't verify the validity of the name. This is
@@ -219,18 +217,13 @@
* mangled
* names.
*/
- bool AddResourceAllowMangled(const ResourceNameRef& name,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
+ bool AddResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::StringPiece& product, std::unique_ptr<Value> value,
IDiagnostics* diag);
- bool AddResourceAllowMangled(const ResourceNameRef& name,
- const ResourceId& id,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ bool AddResourceAllowMangled(const ResourceNameRef& name, const ResourceId& id,
+ const ConfigDescription& config, const android::StringPiece& product,
+ std::unique_ptr<Value> value, IDiagnostics* diag);
bool SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
const Symbol& symbol, IDiagnostics* diag);
@@ -273,28 +266,23 @@
* represent the
* 'current' package before it is known to the ResourceTable.
*/
- ResourceTablePackage* FindPackage(const StringPiece& name);
+ ResourceTablePackage* FindPackage(const android::StringPiece& name);
ResourceTablePackage* FindPackageById(uint8_t id);
- ResourceTablePackage* CreatePackage(const StringPiece& name,
- Maybe<uint8_t> id = {});
+ ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
private:
- ResourceTablePackage* FindOrCreatePackage(const StringPiece& name);
+ ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
- const ConfigDescription& config,
- const StringPiece& product, std::unique_ptr<Value> value,
- const char* valid_chars,
- const CollisionResolverFunc& conflict_resolver,
- IDiagnostics* diag);
+ const ConfigDescription& config, const android::StringPiece& product,
+ std::unique_ptr<Value> value, const char* valid_chars,
+ const CollisionResolverFunc& conflict_resolver, IDiagnostics* diag);
- bool AddFileReferenceImpl(const ResourceNameRef& name,
- const ConfigDescription& config,
- const Source& source, const StringPiece& path,
- io::IFile* file, const char* valid_chars,
- IDiagnostics* diag);
+ bool AddFileReferenceImpl(const ResourceNameRef& name, const ConfigDescription& config,
+ const Source& source, const android::StringPiece& path, io::IFile* file,
+ const char* valid_chars, IDiagnostics* diag);
bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
const Symbol& symbol, const char* valid_chars,
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index fce9b33..1123967 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -26,6 +26,9 @@
#include "util/Files.h"
#include "util/Util.h"
+using android::StringPiece;
+using android::StringPiece16;
+
namespace aapt {
namespace ResourceUtils {
@@ -59,7 +62,7 @@
name_out.entry =
util::Utf16ToUtf8(StringPiece16(name_in.name, name_in.nameLen));
} else if (name_in.name8) {
- name_out.entry = StringPiece(name_in.name8, name_in.nameLen).ToString();
+ name_out.entry.assign(name_in.name8, name_in.nameLen);
} else {
return {};
}
@@ -303,9 +306,7 @@
p++;
}
- ref.name =
- ResourceName(package.ToString(), ResourceType::kAttr,
- name.empty() ? trimmed_str.ToString() : name.ToString());
+ ref.name = ResourceName(package, ResourceType::kAttr, name.empty() ? trimmed_str : name);
return Maybe<Reference>(std::move(ref));
}
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 9766f6a..bd3a8e3 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -20,10 +20,11 @@
#include <functional>
#include <memory>
+#include "androidfw/StringPiece.h"
+
#include "NameMangler.h"
#include "Resource.h"
#include "ResourceValues.h"
-#include "util/StringPiece.h"
namespace aapt {
namespace ResourceUtils {
@@ -37,8 +38,8 @@
* individual extracted piece to verify that the pieces are valid.
* Returns false if there was no package but a ':' was present.
*/
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package,
- StringPiece* out_type, StringPiece* out_entry);
+bool ExtractResourceName(const android::StringPiece& str, android::StringPiece* out_package,
+ android::StringPiece* out_type, android::StringPiece* out_entry);
/**
* Returns true if the string was parsed as a resource name
@@ -46,7 +47,7 @@
* `out_resource` set to the parsed resource name and `out_private` set to true
* if a '*' prefix was present.
*/
-bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_resource,
+bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_resource,
bool* out_private = nullptr);
/*
@@ -57,28 +58,27 @@
* If '+' was present in the reference, `out_create` is set to true.
* If '*' was present in the reference, `out_private` is set to true.
*/
-bool ParseReference(const StringPiece& str, ResourceNameRef* out_reference,
+bool ParseReference(const android::StringPiece& str, ResourceNameRef* out_reference,
bool* out_create = nullptr, bool* out_private = nullptr);
/*
* Returns true if the string is in the form of a resource reference
* (@[+][package:]type/name).
*/
-bool IsReference(const StringPiece& str);
+bool IsReference(const android::StringPiece& str);
/*
* Returns true if the string was parsed as an attribute reference
* (?[package:][type/]name),
* with `out_reference` set to the parsed reference.
*/
-bool ParseAttributeReference(const StringPiece& str,
- ResourceNameRef* out_reference);
+bool ParseAttributeReference(const android::StringPiece& str, ResourceNameRef* out_reference);
/**
* Returns true if the string is in the form of an attribute
* reference(?[package:][type/]name).
*/
-bool IsAttributeReference(const StringPiece& str);
+bool IsAttributeReference(const android::StringPiece& str);
/**
* Convert an android::ResTable::resource_name to an aapt::ResourceName struct.
@@ -90,22 +90,22 @@
* Returns a boolean value if the string is equal to TRUE, true, True, FALSE,
* false, or False.
*/
-Maybe<bool> ParseBool(const StringPiece& str);
+Maybe<bool> ParseBool(const android::StringPiece& str);
/**
* Returns a uint32_t if the string is an integer.
*/
-Maybe<uint32_t> ParseInt(const StringPiece& str);
+Maybe<uint32_t> ParseInt(const android::StringPiece& str);
/**
* Returns an ID if it the string represented a valid ID.
*/
-Maybe<ResourceId> ParseResourceId(const StringPiece& str);
+Maybe<ResourceId> ParseResourceId(const android::StringPiece& str);
/**
* Parses an SDK version, which can be an integer, or a letter from A-Z.
*/
-Maybe<int> ParseSdkVersion(const StringPiece& str);
+Maybe<int> ParseSdkVersion(const android::StringPiece& str);
/*
* Returns a Reference, or None Maybe instance if the string `str` was parsed as
@@ -118,8 +118,7 @@
* ?[package:]style/<entry> or
* <package>:[style/]<entry>
*/
-Maybe<Reference> ParseStyleParentReference(const StringPiece& str,
- std::string* out_error);
+Maybe<Reference> ParseStyleParentReference(const android::StringPiece& str, std::string* out_error);
/*
* Returns a Reference if the string `str` was parsed as a valid XML attribute
@@ -128,7 +127,7 @@
*
* package:entry
*/
-Maybe<Reference> ParseXmlAttributeName(const StringPiece& str);
+Maybe<Reference> ParseXmlAttributeName(const android::StringPiece& str);
/*
* Returns a Reference object if the string was parsed as a resource or
@@ -137,52 +136,52 @@
* if
* the '+' was present in the string.
*/
-std::unique_ptr<Reference> TryParseReference(const StringPiece& str,
+std::unique_ptr<Reference> TryParseReference(const android::StringPiece& str,
bool* out_create = nullptr);
/*
* Returns a BinaryPrimitve object representing @null or @empty if the string
* was parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseNullOrEmpty(const android::StringPiece& str);
/*
* Returns a BinaryPrimitve object representing a color if the string was parsed
* as one.
*/
-std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseColor(const android::StringPiece& str);
/*
* Returns a BinaryPrimitve object representing a boolean if the string was
* parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseBool(const android::StringPiece& str);
/*
* Returns a BinaryPrimitve object representing an integer if the string was
* parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseInt(const android::StringPiece& str);
/*
* Returns a BinaryPrimitve object representing a floating point number
* (float, dimension, etc) if the string was parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseFloat(const android::StringPiece& str);
/*
* Returns a BinaryPrimitve object representing an enum symbol if the string was
* parsed as one.
*/
std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
- const StringPiece& str);
+ const android::StringPiece& str);
/*
* Returns a BinaryPrimitve object representing a flag symbol if the string was
* parsed as one.
*/
std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
- const StringPiece& str);
+ const android::StringPiece& str);
/*
* Try to convert a string to an Item for the given attribute. The attribute
* will
@@ -191,11 +190,11 @@
* reference to an ID that must be created (@+id/foo).
*/
std::unique_ptr<Item> TryParseItemForAttribute(
- const StringPiece& value, const Attribute* attr,
+ const android::StringPiece& value, const Attribute* attr,
const std::function<void(const ResourceName&)>& on_create_reference = {});
std::unique_ptr<Item> TryParseItemForAttribute(
- const StringPiece& value, uint32_t type_mask,
+ const android::StringPiece& value, uint32_t type_mask,
const std::function<void(const ResourceName&)>& on_create_reference = {});
uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index f9c500b..048c692 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -48,8 +48,7 @@
EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual);
EXPECT_TRUE(actual_priv);
- EXPECT_FALSE(
- ResourceUtils::ParseResourceName(StringPiece(), &actual, &actual_priv));
+ EXPECT_FALSE(ResourceUtils::ParseResourceName(android::StringPiece(), &actual, &actual_priv));
}
TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index ea73615..d380f8d 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -22,6 +22,7 @@
#include <vector>
#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
#include "Diagnostics.h"
#include "Resource.h"
@@ -73,7 +74,7 @@
*/
const std::string& GetComment() const { return comment_; }
- void SetComment(const StringPiece& str) { comment_ = str.ToString(); }
+ void SetComment(const android::StringPiece& str) { comment_ = str.to_string(); }
void SetComment(std::string&& str) { comment_ = std::move(str); }
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index c7f920a..e806714 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -21,6 +21,8 @@
#include <unordered_map>
#include <vector>
+using android::StringPiece;
+
namespace aapt {
static const char* sDevelopmentSdkCodeName = "O";
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 5352b53..98ba94b 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -19,6 +19,8 @@
#include <utility>
+#include "androidfw/StringPiece.h"
+
#include "Resource.h"
namespace aapt {
@@ -52,7 +54,7 @@
size_t FindAttributeSdkLevel(const ResourceId& id);
size_t FindAttributeSdkLevel(const ResourceName& name);
-std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion();
+std::pair<android::StringPiece, int> GetDevelopmentSdkCodeNameAndVersion();
} // namespace aapt
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 459a8e6..d7f2a66 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -20,8 +20,9 @@
#include <ostream>
#include <string>
+#include "androidfw/StringPiece.h"
+
#include "util/Maybe.h"
-#include "util/StringPiece.h"
namespace aapt {
@@ -35,12 +36,11 @@
Source() = default;
- inline Source(const StringPiece& path)
- : path(path.ToString()) { // NOLINT(implicit)
+ inline Source(const android::StringPiece& path) : path(path.to_string()) { // NOLINT(implicit)
}
- inline Source(const StringPiece& path, size_t line)
- : path(path.ToString()), line(line) {}
+ inline Source(const android::StringPiece& path, size_t line)
+ : path(path.to_string()), line(line) {}
inline Source WithLine(size_t line) const { return Source(path, line); }
};
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index 3032829..d968d73 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -22,11 +22,13 @@
#include "android-base/logging.h"
#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
#include "util/BigBuffer.h"
-#include "util/StringPiece.h"
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
StringPool::Ref::Ref() : entry_(nullptr) {}
@@ -140,7 +142,7 @@
}
Entry* entry = new Entry();
- entry->value = str.ToString();
+ entry->value = str.to_string();
entry->context = context;
entry->index = strings_.size();
entry->ref_ = 0;
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index a4f556c..d0ce489 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -23,9 +23,10 @@
#include <unordered_map>
#include <vector>
+#include "androidfw/StringPiece.h"
+
#include "ConfigDescription.h"
#include "util/BigBuffer.h"
-#include "util/StringPiece.h"
namespace aapt {
@@ -149,14 +150,14 @@
* Adds a string to the pool, unless it already exists. Returns
* a reference to the string in the pool.
*/
- Ref MakeRef(const StringPiece& str);
+ Ref MakeRef(const android::StringPiece& str);
/**
* Adds a string to the pool, unless it already exists, with a context
* object that can be used when sorting the string pool. Returns
* a reference to the string in the pool.
*/
- Ref MakeRef(const StringPiece& str, const Context& context);
+ Ref MakeRef(const android::StringPiece& str, const Context& context);
/**
* Adds a style to the string pool and returns a reference to it.
@@ -208,11 +209,11 @@
static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8);
- Ref MakeRefImpl(const StringPiece& str, const Context& context, bool unique);
+ Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique);
std::vector<std::unique_ptr<Entry>> strings_;
std::vector<std::unique_ptr<StyleEntry>> styles_;
- std::unordered_multimap<StringPiece, Entry*> indexed_strings_;
+ std::unordered_multimap<android::StringPiece, Entry*> indexed_strings_;
};
//
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index e1394fc..f64a8cf 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -18,9 +18,14 @@
#include <string>
+#include "androidfw/StringPiece.h"
+
#include "test/Test.h"
#include "util/Util.h"
+using android::StringPiece;
+using android::StringPiece16;
+
namespace aapt {
TEST(StringPoolTest, InsertOneString) {
@@ -236,8 +241,7 @@
EXPECT_EQ(StringPiece16(u"goodbye"), util::GetString16(test, 1));
EXPECT_EQ(StringPiece(sLongString), util::GetString(test, 2));
- EXPECT_EQ(util::Utf8ToUtf16(sLongString),
- util::GetString16(test, 2).ToString());
+ EXPECT_EQ(util::Utf8ToUtf16(sLongString), util::GetString16(test, 2).to_string());
size_t len;
EXPECT_TRUE(test.stringAt(3, &len) != nullptr ||
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index f0b18e6..8027f42 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -19,6 +19,12 @@
#include <fstream>
#include <string>
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "androidfw/StringPiece.h"
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
#include "ConfigDescription.h"
#include "Diagnostics.h"
#include "Flags.h"
@@ -38,11 +44,7 @@
#include "xml/XmlDom.h"
#include "xml/XmlPullParser.h"
-#include "android-base/errors.h"
-#include "android-base/file.h"
-#include "google/protobuf/io/coded_stream.h"
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
+using android::StringPiece;
using google::protobuf::io::CopyingOutputStreamAdaptor;
using google::protobuf::io::ZeroCopyOutputStream;
@@ -103,9 +105,8 @@
name = name.substr(0, dot_pos);
}
- return ResourcePathData{Source(path), dir_str.ToString(),
- name.ToString(), extension.ToString(),
- config_str.ToString(), config};
+ return ResourcePathData{Source(path), dir_str.to_string(), name.to_string(),
+ extension.to_string(), config_str.to_string(), config};
}
struct CompileOptions {
diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h
index 371ec01..9640eb8 100644
--- a/tools/aapt2/compile/IdAssigner.h
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -19,11 +19,11 @@
#include <unordered_map>
+#include "android-base/macros.h"
+
#include "Resource.h"
#include "process/IResourceTableConsumer.h"
-#include "android-base/macros.h"
-
namespace aapt {
/**
@@ -40,8 +40,7 @@
bool Consume(IAaptContext* context, ResourceTable* table) override;
private:
- const std::unordered_map<ResourceName, ResourceId>* assigned_id_map_ =
- nullptr;
+ const std::unordered_map<ResourceName, ResourceId>* assigned_id_map_ = nullptr;
};
} // namespace aapt
diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
index eab5c97..c931da4 100644
--- a/tools/aapt2/compile/NinePatch.cpp
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -21,10 +21,12 @@
#include <vector>
#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
-#include "util/StringPiece.h"
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 7ab05b5..5e15c88 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -15,18 +15,21 @@
*/
#include "Png.h"
-#include "Source.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-#include <androidfw/ResourceTypes.h>
#include <png.h>
#include <zlib.h>
+
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
+#include "androidfw/ResourceTypes.h"
+
+#include "Source.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
namespace aapt {
constexpr bool kDebug = false;
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index aff1da3..a820051 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -56,7 +56,7 @@
*/
class PngChunkFilter : public io::InputStream {
public:
- explicit PngChunkFilter(const StringPiece& data);
+ explicit PngChunkFilter(const android::StringPiece& data);
bool Next(const void** buffer, int* len) override;
void BackUp(int count) override;
@@ -71,7 +71,7 @@
private:
bool ConsumeWindow(const void** buffer, int* len);
- StringPiece data_;
+ android::StringPiece data_;
size_t window_start_ = 0;
size_t window_end_ = 0;
bool error_ = false;
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index 4cbefb9..edec123 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -16,8 +16,11 @@
#include "compile/Png.h"
+#include "androidfw/StringPiece.h"
+
#include "io/Io.h"
-#include "util/StringPiece.h"
+
+using android::StringPiece;
namespace aapt {
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 055a725..5035f81 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -23,6 +23,8 @@
#include "ValueVisitor.h"
#include "compile/Pseudolocalizer.h"
+using android::StringPiece;
+
namespace aapt {
std::unique_ptr<StyledString> PseudolocalizeStyledString(
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
index f89288f..15a3d8c 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -18,6 +18,8 @@
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
// String basis to generate expansion
@@ -40,10 +42,8 @@
class PseudoMethodNone : public PseudoMethodImpl {
public:
- std::string Text(const StringPiece& text) override { return text.ToString(); }
- std::string Placeholder(const StringPiece& text) override {
- return text.ToString();
- }
+ std::string Text(const StringPiece& text) override { return text.to_string(); }
+ std::string Placeholder(const StringPiece& text) override { return text.to_string(); }
};
class PseudoMethodBidi : public PseudoMethodImpl {
@@ -116,7 +116,7 @@
}
size_t size = nextpos - lastpos;
if (size) {
- std::string chunk = text.substr(lastpos, size).ToString();
+ std::string chunk = text.substr(lastpos, size).to_string();
if (pseudo) {
chunk = impl_->Text(chunk);
} else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) {
@@ -437,7 +437,7 @@
std::string PseudoMethodAccent::Placeholder(const StringPiece& source) {
// Surround a placeholder with brackets
- return kPlaceholderOpen + source.ToString() + kPlaceholderClose;
+ return kPlaceholderOpen + source.to_string() + kPlaceholderClose;
}
std::string PseudoMethodBidi::Text(const StringPiece& source) {
@@ -467,7 +467,7 @@
std::string PseudoMethodBidi::Placeholder(const StringPiece& source) {
// Surround a placeholder with directionality change sequence
- return kRlm + kRlo + source.ToString() + kPdf + kRlm;
+ return kRlm + kRlo + source.to_string() + kPdf + kRlm;
}
} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
index a6d2ad0..6cf003b 100644
--- a/tools/aapt2/compile/Pseudolocalizer.h
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -20,10 +20,10 @@
#include <memory>
#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
#include "ResourceValues.h"
#include "StringPool.h"
-#include "util/StringPiece.h"
namespace aapt {
@@ -32,8 +32,8 @@
virtual ~PseudoMethodImpl() {}
virtual std::string Start() { return {}; }
virtual std::string End() { return {}; }
- virtual std::string Text(const StringPiece& text) = 0;
- virtual std::string Placeholder(const StringPiece& text) = 0;
+ virtual std::string Text(const android::StringPiece& text) = 0;
+ virtual std::string Placeholder(const android::StringPiece& text) = 0;
};
class Pseudolocalizer {
@@ -48,7 +48,7 @@
void SetMethod(Method method);
std::string Start() { return impl_->Start(); }
std::string End() { return impl_->End(); }
- std::string Text(const StringPiece& text);
+ std::string Text(const android::StringPiece& text);
private:
std::unique_ptr<PseudoMethodImpl> impl_;
diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp
index 92eb3b5..d3b7b02 100644
--- a/tools/aapt2/compile/Pseudolocalizer_test.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp
@@ -19,6 +19,8 @@
#include "test/Test.h"
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
// In this context, 'Axis' represents a particular field in the configuration,
diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp
index 593e7ab..de0fe40 100644
--- a/tools/aapt2/diff/Diff.cpp
+++ b/tools/aapt2/diff/Diff.cpp
@@ -24,6 +24,8 @@
#include "process/SymbolTable.h"
#include "unflatten/BinaryResourceParser.h"
+using android::StringPiece;
+
namespace aapt {
class DiffContext : public IAaptContext {
diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp
index 2920c2a..bcede72 100644
--- a/tools/aapt2/dump/Dump.cpp
+++ b/tools/aapt2/dump/Dump.cpp
@@ -16,6 +16,8 @@
#include <vector>
+#include "androidfw/StringPiece.h"
+
#include "Debug.h"
#include "Diagnostics.h"
#include "Flags.h"
@@ -24,7 +26,8 @@
#include "proto/ProtoSerialize.h"
#include "unflatten/BinaryResourceParser.h"
#include "util/Files.h"
-#include "util/StringPiece.h"
+
+using android::StringPiece;
namespace aapt {
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 47de0a3..5c96a4d 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -22,10 +22,12 @@
#include <vector>
#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
#include "ziparchive/zip_writer.h"
#include "util/Files.h"
-#include "util/StringPiece.h"
+
+using android::StringPiece;
namespace aapt {
@@ -36,7 +38,7 @@
DirectoryWriter() = default;
bool Open(IDiagnostics* diag, const StringPiece& out_dir) {
- dir_ = out_dir.ToString();
+ dir_ = out_dir.to_string();
file::FileType type = file::GetFileType(dir_);
if (type == file::FileType::kNonexistant) {
diag->Error(DiagMessage() << "directory " << dir_ << " does not exist");
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index 4fcb3ff..f0681bd 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -22,12 +22,12 @@
#include <string>
#include <vector>
+#include "androidfw/StringPiece.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "Diagnostics.h"
#include "util/BigBuffer.h"
#include "util/Files.h"
-#include "util/StringPiece.h"
namespace aapt {
@@ -46,7 +46,7 @@
public:
virtual ~IArchiveWriter() = default;
- virtual bool StartEntry(const StringPiece& path, uint32_t flags) = 0;
+ virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
virtual bool WriteEntry(const BigBuffer& buffer) = 0;
virtual bool WriteEntry(const void* data, size_t len) = 0;
virtual bool FinishEntry() = 0;
@@ -57,11 +57,11 @@
}
};
-std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(
- IDiagnostics* diag, const StringPiece& path);
+std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
+ const android::StringPiece& path);
-std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(
- IDiagnostics* diag, const StringPiece& path);
+std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag,
+ const android::StringPiece& path);
} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index 2c83bb3..ffc2de1 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -23,6 +23,8 @@
#include "util/BigBuffer.h"
#include "util/Util.h"
+using android::StringPiece16;
+
namespace aapt {
class XmlFlattenerTest : public ::testing::Test {
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/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_root.xml b/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml
similarity index 63%
rename from packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_root.xml
rename to tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml
index 85573e0..e10e6c2 100644
--- a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_root.xml
+++ b/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2015 The Android Open Source Project
+ Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.3"
- android:valueTo="1.0"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
+<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/io/File.h b/tools/aapt2/io/File.h
index 644f59f..3d5b5b1 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -110,7 +110,7 @@
public:
virtual ~IFileCollection() = default;
- virtual IFile* FindFile(const StringPiece& path) = 0;
+ virtual IFile* FindFile(const android::StringPiece& path) = 0;
virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0;
};
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 828f34e..027cbd0 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -16,14 +16,16 @@
#include "io/FileSystem.h"
+#include "androidfw/StringPiece.h"
#include "utils/FileMap.h"
#include "Source.h"
#include "util/Files.h"
#include "util/Maybe.h"
-#include "util/StringPiece.h"
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
namespace io {
@@ -54,13 +56,11 @@
}
IFile* FileCollection::InsertFile(const StringPiece& path) {
- return (files_[path.ToString()] =
- util::make_unique<RegularFile>(Source(path)))
- .get();
+ return (files_[path.to_string()] = util::make_unique<RegularFile>(Source(path))).get();
}
IFile* FileCollection::FindFile(const StringPiece& path) {
- auto iter = files_.find(path.ToString());
+ auto iter = files_.find(path.to_string());
if (iter != files_.end()) {
return iter->second.get();
}
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index 84f851f..dfd3717 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -59,8 +59,8 @@
/**
* Adds a file located at path. Returns the IFile representation of that file.
*/
- IFile* InsertFile(const StringPiece& path);
- IFile* FindFile(const StringPiece& path) override;
+ IFile* InsertFile(const android::StringPiece& path);
+ IFile* FindFile(const android::StringPiece& path) override;
std::unique_ptr<IFileCollectionIterator> Iterator() override;
private:
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index f4a128e..62b436f 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -22,6 +22,8 @@
#include "Source.h"
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
namespace io {
@@ -107,7 +109,7 @@
std::string zip_entry_path =
std::string(reinterpret_cast<const char*>(zip_entry_name.name),
zip_entry_name.name_length);
- std::string nested_path = path.ToString() + "@" + zip_entry_path;
+ std::string nested_path = path.to_string() + "@" + zip_entry_path;
collection->files_[zip_entry_path] = util::make_unique<ZipFile>(
collection->handle_, zip_data, Source(nested_path));
}
@@ -120,7 +122,7 @@
}
IFile* ZipFileCollection::FindFile(const StringPiece& path) {
- auto iter = files_.find(path.ToString());
+ auto iter = files_.find(path.to_string());
if (iter != files_.end()) {
return iter->second.get();
}
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 85ca1ae..634adad 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -21,8 +21,9 @@
#include <map>
+#include "androidfw/StringPiece.h"
+
#include "io/File.h"
-#include "util/StringPiece.h"
namespace aapt {
namespace io {
@@ -64,10 +65,10 @@
*/
class ZipFileCollection : public IFileCollection {
public:
- static std::unique_ptr<ZipFileCollection> Create(const StringPiece& path,
+ static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path,
std::string* outError);
- io::IFile* FindFile(const StringPiece& path) override;
+ io::IFile* FindFile(const android::StringPiece& path) override;
std::unique_ptr<IFileCollectionIterator> Iterator() override;
~ZipFileCollection() override;
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 2951e5c..a0ef00b 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -20,6 +20,8 @@
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
void AnnotationProcessor::AppendCommentLine(std::string& comment) {
@@ -54,7 +56,7 @@
for (StringPiece line : util::Tokenize(comment, '\n')) {
line = util::TrimWhitespace(line);
if (!line.empty()) {
- std::string lineCopy = line.ToString();
+ std::string lineCopy = line.to_string();
AppendCommentLine(lineCopy);
}
}
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index 666a7f3..99cd44f 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -20,7 +20,7 @@
#include <sstream>
#include <string>
-#include "util/StringPiece.h"
+#include "androidfw/StringPiece.h"
namespace aapt {
@@ -58,7 +58,7 @@
* configurations,
* we need to collect all the comments.
*/
- void AppendComment(const StringPiece& comment);
+ void AppendComment(const android::StringPiece& comment);
void AppendNewLine();
@@ -66,7 +66,7 @@
* Writes the comments and annotations to the stream, with the given prefix
* before each line.
*/
- void WriteToStream(std::ostream* out, const StringPiece& prefix) const;
+ void WriteToStream(std::ostream* out, const android::StringPiece& prefix) const;
private:
enum : uint32_t {
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index f1f1f92..53d6ea1 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -16,7 +16,9 @@
#include "java/ClassDefinition.h"
-#include "util/StringPiece.h"
+#include "androidfw/StringPiece.h"
+
+using android::StringPiece;
namespace aapt {
@@ -43,7 +45,7 @@
}
*out << "final class " << name_ << " {\n";
- std::string new_prefix = prefix.ToString();
+ std::string new_prefix = prefix.to_string();
new_prefix.append(kIndent);
for (const std::unique_ptr<ClassMember>& member : members_) {
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index d8b61d9..64e4b29 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -21,10 +21,10 @@
#include <string>
#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
#include "Resource.h"
#include "java/AnnotationProcessor.h"
-#include "util/StringPiece.h"
#include "util/Util.h"
namespace aapt {
@@ -41,7 +41,7 @@
virtual bool empty() const = 0;
- virtual void WriteToStream(const StringPiece& prefix, bool final,
+ virtual void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const {
processor_.WriteToStream(out, prefix);
}
@@ -53,12 +53,12 @@
template <typename T>
class PrimitiveMember : public ClassMember {
public:
- PrimitiveMember(const StringPiece& name, const T& val)
- : name_(name.ToString()), val_(val) {}
+ PrimitiveMember(const android::StringPiece& name, const T& val)
+ : name_(name.to_string()), val_(val) {}
bool empty() const override { return false; }
- void WriteToStream(const StringPiece& prefix, bool final,
+ void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override {
ClassMember::WriteToStream(prefix, final, out);
@@ -79,12 +79,12 @@
template <>
class PrimitiveMember<std::string> : public ClassMember {
public:
- PrimitiveMember(const StringPiece& name, const std::string& val)
- : name_(name.ToString()), val_(val) {}
+ PrimitiveMember(const android::StringPiece& name, const std::string& val)
+ : name_(name.to_string()), val_(val) {}
bool empty() const override { return false; }
- void WriteToStream(const StringPiece& prefix, bool final,
+ void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override {
ClassMember::WriteToStream(prefix, final, out);
@@ -106,14 +106,13 @@
template <typename T>
class PrimitiveArrayMember : public ClassMember {
public:
- explicit PrimitiveArrayMember(const StringPiece& name)
- : name_(name.ToString()) {}
+ explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {}
void AddElement(const T& val) { elements_.push_back(val); }
bool empty() const override { return false; }
- void WriteToStream(const StringPiece& prefix, bool final,
+ void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override {
ClassMember::WriteToStream(prefix, final, out);
@@ -147,22 +146,18 @@
class ClassDefinition : public ClassMember {
public:
- static bool WriteJavaFile(const ClassDefinition* def,
- const StringPiece& package, bool final,
- std::ostream* out);
+ static bool WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package,
+ bool final, std::ostream* out);
- ClassDefinition(const StringPiece& name, ClassQualifier qualifier,
- bool createIfEmpty)
- : name_(name.ToString()),
- qualifier_(qualifier),
- create_if_empty_(createIfEmpty) {}
+ ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty)
+ : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {}
void AddMember(std::unique_ptr<ClassMember> member) {
members_.push_back(std::move(member));
}
bool empty() const override;
- void WriteToStream(const StringPiece& prefix, bool final,
+ void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override;
private:
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 6e7c7078..b71dc48 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -23,6 +23,7 @@
#include <tuple>
#include "android-base/logging.h"
+#include "androidfw/StringPiece.h"
#include "NameMangler.h"
#include "Resource.h"
@@ -32,7 +33,8 @@
#include "java/AnnotationProcessor.h"
#include "java/ClassDefinition.h"
#include "process/SymbolTable.h"
-#include "util/StringPiece.h"
+
+using android::StringPiece;
namespace aapt {
@@ -58,7 +60,7 @@
* Replace those with '_'.
*/
static std::string Transform(const StringPiece& symbol) {
- std::string output = symbol.ToString();
+ std::string output = symbol.to_string();
for (char& c : output) {
if (c == '.' || c == '-') {
c = '_';
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 190e73b..5cf556e 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -20,10 +20,11 @@
#include <ostream>
#include <string>
+#include "androidfw/StringPiece.h"
+
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "process/IResourceTableConsumer.h"
-#include "util/StringPiece.h"
namespace aapt {
@@ -69,22 +70,20 @@
* We need to generate these symbols in a separate file.
* Returns true on success.
*/
- bool Generate(const StringPiece& packageNameToGenerate, std::ostream* out);
+ bool Generate(const android::StringPiece& packageNameToGenerate, std::ostream* out);
- bool Generate(const StringPiece& packageNameToGenerate,
- const StringPiece& outputPackageName, std::ostream* out);
+ bool Generate(const android::StringPiece& packageNameToGenerate,
+ const android::StringPiece& outputPackageName, std::ostream* out);
const std::string& getError() const;
private:
- bool AddMembersToTypeClass(const StringPiece& packageNameToGenerate,
- const ResourceTablePackage* package,
- const ResourceTableType* type,
+ bool AddMembersToTypeClass(const android::StringPiece& packageNameToGenerate,
+ const ResourceTablePackage* package, const ResourceTableType* type,
ClassDefinition* outTypeClassDef);
- void AddMembersToStyleableClass(const StringPiece& packageNameToGenerate,
- const std::string& entryName,
- const Styleable* styleable,
+ void AddMembersToStyleableClass(const android::StringPiece& packageNameToGenerate,
+ const std::string& entryName, const Styleable* styleable,
ClassDefinition* outStyleableClassDef);
bool SkipSymbol(SymbolState state);
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 3d3d24e..55c5cb2 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -22,6 +22,8 @@
#include "test/Test.h"
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index db84f29..de8e59a 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -24,6 +24,8 @@
#include "util/Maybe.h"
#include "xml/XmlDom.h"
+using android::StringPiece;
+
namespace aapt {
static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag,
diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp
index 5518fe2..b029b20 100644
--- a/tools/aapt2/jni/aapt2_jni.cpp
+++ b/tools/aapt2/jni/aapt2_jni.cpp
@@ -26,6 +26,8 @@
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
extern int Compile(const std::vector<StringPiece> &args);
extern int Link(const std::vector<StringPiece> &args);
@@ -65,9 +67,8 @@
* The returned pieces can only be used while the original ones have not been
* destroyed.
*/
-static std::vector<aapt::StringPiece> extract_pieces(
- const std::vector<ScopedUtfChars> &strings) {
- std::vector<aapt::StringPiece> pieces;
+static std::vector<StringPiece> extract_pieces(const std::vector<ScopedUtfChars> &strings) {
+ std::vector<StringPiece> pieces;
std::for_each(
strings.begin(), strings.end(),
@@ -80,8 +81,7 @@
JNIEnv *env, jclass aapt_obj, jobject arguments_obj) {
std::vector<ScopedUtfChars> compile_args_jni =
list_to_utfchars(env, arguments_obj);
- std::vector<aapt::StringPiece> compile_args =
- extract_pieces(compile_args_jni);
+ std::vector<StringPiece> compile_args = extract_pieces(compile_args_jni);
aapt::Compile(compile_args);
}
@@ -89,7 +89,7 @@
JNIEnv *env, jclass aapt_obj, jobject arguments_obj) {
std::vector<ScopedUtfChars> link_args_jni =
list_to_utfchars(env, arguments_obj);
- std::vector<aapt::StringPiece> link_args = extract_pieces(link_args_jni);
+ std::vector<StringPiece> link_args = extract_pieces(link_args_jni);
aapt::Link(link_args);
}
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index b525758..f7e0f8f 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -23,6 +23,7 @@
#include "android-base/errors.h"
#include "android-base/file.h"
+#include "androidfw/StringPiece.h"
#include "google/protobuf/io/coded_stream.h"
#include "AppInfo.h"
@@ -51,9 +52,9 @@
#include "split/TableSplitter.h"
#include "unflatten/BinaryResourceParser.h"
#include "util/Files.h"
-#include "util/StringPiece.h"
#include "xml/XmlDom.h"
+using android::StringPiece;
using ::google::protobuf::io::CopyingOutputStreamAdaptor;
namespace aapt {
@@ -79,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;
@@ -121,7 +123,7 @@
}
void SetCompilationPackage(const StringPiece& package_name) {
- compilation_package_ = package_name.ToString();
+ compilation_package_ = package_name.to_string();
}
uint8_t GetPackageId() override { return package_id_; }
@@ -249,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;
@@ -305,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) {
@@ -344,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;
@@ -1383,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);
@@ -1862,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.",
@@ -2011,14 +2048,14 @@
for (std::string& extra_package : extra_java_packages) {
// A given package can actually be a colon separated list of packages.
for (StringPiece package : util::Split(extra_package, ':')) {
- options.extra_java_packages.insert(package.ToString());
+ options.extra_java_packages.insert(package.to_string());
}
}
if (product_list) {
for (StringPiece product : util::Tokenize(product_list.value(), ',')) {
if (product != "" && product != "default") {
- options.products.insert(product.ToString());
+ options.products.insert(product.to_string());
}
}
}
@@ -2103,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/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 4185937..e5eaf2f 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -25,6 +25,8 @@
#include "xml/XmlActionExecutor.h"
#include "xml/XmlDom.h"
+using android::StringPiece;
+
namespace aapt {
/**
@@ -293,7 +295,7 @@
CHECK(attr != nullptr);
std::string original_package = std::move(attr->value);
- attr->value = package_override.ToString();
+ attr->value = package_override.to_string();
FullyQualifiedClassNameVisitor visitor(original_package);
manifest_el->Accept(&visitor);
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index fc6970c..12a304a 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -18,6 +18,8 @@
#include "test/Test.h"
+using android::StringPiece;
+
namespace aapt {
struct ManifestFixerTest : public ::testing::Test {
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index be787b2..ea68b61 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -30,6 +30,8 @@
#include "util/Util.h"
#include "xml/XmlUtil.h"
+using android::StringPiece;
+
namespace aapt {
namespace {
@@ -192,8 +194,7 @@
const StringPiece& alias,
const StringPiece& local_package) const override {
if (alias.empty()) {
- return xml::ExtractedPackage{local_package.ToString(),
- true /* private */};
+ return xml::ExtractedPackage{local_package.to_string(), true /* private */};
}
return {};
}
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index d808da3..7e7b9fb 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -24,6 +24,8 @@
#include "ValueVisitor.h"
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table,
@@ -351,9 +353,8 @@
const std::string& package, const FileReference& file_ref) {
StringPiece prefix, entry, suffix;
if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
- std::string mangled_entry =
- NameMangler::MangleEntry(package, entry.ToString());
- std::string newPath = prefix.ToString() + mangled_entry + suffix.ToString();
+ std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string());
+ std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string();
std::unique_ptr<FileReference> new_file_ref =
util::make_unique<FileReference>(
master_table_->string_pool.MakeRef(newPath));
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 4ab83c3..c96b1b0 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -96,8 +96,8 @@
* An io::IFileCollection is needed in order to find the referenced Files and
* process them.
*/
- bool MergeAndMangle(const Source& src, const StringPiece& package,
- ResourceTable* table, io::IFileCollection* collection);
+ bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table,
+ io::IFileCollection* collection);
/**
* Merges a compiled file that belongs to this same or empty package. This is
diff --git a/tools/aapt2/link/VersionCollapser_test.cpp b/tools/aapt2/link/VersionCollapser_test.cpp
index 1b5592f..44babb2 100644
--- a/tools/aapt2/link/VersionCollapser_test.cpp
+++ b/tools/aapt2/link/VersionCollapser_test.cpp
@@ -18,6 +18,8 @@
#include "test/Test.h"
+using android::StringPiece;
+
namespace aapt {
static std::unique_ptr<ResourceTable> BuildTableWithConfigs(
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 767384d..1a3da73 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -25,6 +25,8 @@
#include "ValueVisitor.h"
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
void SymbolTable::AppendSource(std::unique_ptr<ISymbolSource> source) {
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 25f7565..cf597bb 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -154,7 +154,7 @@
public:
AssetManagerSymbolSource() = default;
- bool AddAssetPath(const StringPiece& path);
+ bool AddAssetPath(const android::StringPiece& path);
std::unique_ptr<SymbolTable::Symbol> FindByName(
const ResourceName& name) override;
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
index 0d0e46d..7230b55 100644
--- a/tools/aapt2/proto/TableProtoSerializer.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -22,7 +22,7 @@
#include "proto/ProtoSerialize.h"
#include "util/BigBuffer.h"
-#include <android-base/logging.h>
+#include "android-base/logging.h"
using google::protobuf::io::CodedOutputStream;
using google::protobuf::io::CodedInputStream;
@@ -239,7 +239,7 @@
if (type->id) {
pb_type->set_id(type->id.value());
}
- pb_type->set_name(ToString(type->type).ToString());
+ pb_type->set_name(ToString(type->type).to_string());
for (auto& entry : type->entries) {
pb::Entry* pb_entry = pb_type->add_entries();
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/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 7aad86f..38cfd2e 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -21,6 +21,7 @@
#include <set>
#include <unordered_map>
#include <vector>
+
#include "android-base/logging.h"
#include "ConfigDescription.h"
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 9377306..6888cf3 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -37,95 +37,86 @@
StringPool* string_pool() { return &table_->string_pool; }
- ResourceTableBuilder& SetPackageId(const StringPiece& package_name,
- uint8_t id) {
+ ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id) {
ResourceTablePackage* package = table_->CreatePackage(package_name, id);
CHECK(package != nullptr);
return *this;
}
- ResourceTableBuilder& AddSimple(const StringPiece& name,
- const ResourceId& id = {}) {
+ ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}) {
return AddValue(name, id, util::make_unique<Id>());
}
- ResourceTableBuilder& AddSimple(const StringPiece& name,
- const ConfigDescription& config,
+ ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ConfigDescription& config,
const ResourceId& id = {}) {
return AddValue(name, config, id, util::make_unique<Id>());
}
- ResourceTableBuilder& AddReference(const StringPiece& name,
- const StringPiece& ref) {
+ ResourceTableBuilder& AddReference(const android::StringPiece& name,
+ const android::StringPiece& ref) {
return AddReference(name, {}, ref);
}
- ResourceTableBuilder& AddReference(const StringPiece& name,
- const ResourceId& id,
- const StringPiece& ref) {
+ ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id,
+ const android::StringPiece& ref) {
return AddValue(name, id,
util::make_unique<Reference>(ParseNameOrDie(ref)));
}
- ResourceTableBuilder& AddString(const StringPiece& name,
- const StringPiece& str) {
+ ResourceTableBuilder& AddString(const android::StringPiece& name,
+ const android::StringPiece& str) {
return AddString(name, {}, str);
}
- ResourceTableBuilder& AddString(const StringPiece& name, const ResourceId& id,
- const StringPiece& str) {
+ ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
+ const android::StringPiece& str) {
return AddValue(
name, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
}
- ResourceTableBuilder& AddString(const StringPiece& name, const ResourceId& id,
+ ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
const ConfigDescription& config,
- const StringPiece& str) {
+ const android::StringPiece& str) {
return AddValue(name, config, id, util::make_unique<String>(
table_->string_pool.MakeRef(str)));
}
- ResourceTableBuilder& AddFileReference(const StringPiece& name,
- const StringPiece& path) {
+ ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
+ const android::StringPiece& path) {
return AddFileReference(name, {}, path);
}
- ResourceTableBuilder& AddFileReference(const StringPiece& name,
- const ResourceId& id,
- const StringPiece& path) {
+ ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
+ const android::StringPiece& path) {
return AddValue(name, id, util::make_unique<FileReference>(
table_->string_pool.MakeRef(path)));
}
- ResourceTableBuilder& AddFileReference(const StringPiece& name,
- const StringPiece& path,
+ ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
+ const android::StringPiece& path,
const ConfigDescription& config) {
return AddValue(name, config, {}, util::make_unique<FileReference>(
table_->string_pool.MakeRef(path)));
}
- ResourceTableBuilder& AddValue(const StringPiece& name,
- std::unique_ptr<Value> value) {
+ ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value) {
return AddValue(name, {}, std::move(value));
}
- ResourceTableBuilder& AddValue(const StringPiece& name, const ResourceId& id,
+ ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
std::unique_ptr<Value> value) {
return AddValue(name, {}, id, std::move(value));
}
- ResourceTableBuilder& AddValue(const StringPiece& name,
- const ConfigDescription& config,
- const ResourceId& id,
- std::unique_ptr<Value> value) {
+ ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config,
+ const ResourceId& id, std::unique_ptr<Value> value) {
ResourceName res_name = ParseNameOrDie(name);
CHECK(table_->AddResourceAllowMangled(res_name, id, config, {},
std::move(value), &diagnostics_));
return *this;
}
- ResourceTableBuilder& SetSymbolState(const StringPiece& name,
- const ResourceId& id,
+ ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
SymbolState state) {
ResourceName res_name = ParseNameOrDie(name);
Symbol symbol;
@@ -144,8 +135,8 @@
std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();
};
-inline std::unique_ptr<Reference> BuildReference(
- const StringPiece& ref, const Maybe<ResourceId>& id = {}) {
+inline std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref,
+ const Maybe<ResourceId>& id = {}) {
std::unique_ptr<Reference> reference =
util::make_unique<Reference>(ParseNameOrDie(ref));
reference->id = id;
@@ -174,7 +165,7 @@
return *this;
}
- ValueBuilder& SetComment(const StringPiece& str) {
+ ValueBuilder& SetComment(const android::StringPiece& str) {
value_->SetComment(str);
return *this;
}
@@ -199,7 +190,7 @@
return *this;
}
- AttributeBuilder& AddItem(const StringPiece& name, uint32_t value) {
+ AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value) {
attr_->symbols.push_back(Attribute::Symbol{
Reference(ResourceName({}, ResourceType::kId, name)), value});
return *this;
@@ -217,18 +208,18 @@
public:
StyleBuilder() = default;
- StyleBuilder& SetParent(const StringPiece& str) {
+ StyleBuilder& SetParent(const android::StringPiece& str) {
style_->parent = Reference(ParseNameOrDie(str));
return *this;
}
- StyleBuilder& AddItem(const StringPiece& str, std::unique_ptr<Item> value) {
+ StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value) {
style_->entries.push_back(
Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)});
return *this;
}
- StyleBuilder& AddItem(const StringPiece& str, const ResourceId& id,
+ StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id,
std::unique_ptr<Item> value) {
AddItem(str, std::move(value));
style_->entries.back().key.id = id;
@@ -247,8 +238,7 @@
public:
StyleableBuilder() = default;
- StyleableBuilder& AddItem(const StringPiece& str,
- const Maybe<ResourceId>& id = {}) {
+ StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {}) {
styleable_->entries.push_back(Reference(ParseNameOrDie(str)));
styleable_->entries.back().id = id;
return *this;
@@ -262,7 +252,7 @@
std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>();
};
-inline std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) {
+inline std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str) {
std::stringstream in;
in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
StdErrDiagnostics diag;
@@ -273,7 +263,7 @@
}
inline std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(
- IAaptContext* context, const StringPiece& str) {
+ IAaptContext* context, const android::StringPiece& str) {
std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str);
doc->file.name.package = context->GetCompilationPackage();
return doc;
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 3689201..248921f 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -21,6 +21,7 @@
#include "android-base/logging.h"
#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
#include "gtest/gtest.h"
#include "ConfigDescription.h"
@@ -30,7 +31,6 @@
#include "ValueVisitor.h"
#include "io/File.h"
#include "process/IResourceTableConsumer.h"
-#include "util/StringPiece.h"
//
// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to
@@ -68,23 +68,22 @@
return &diag;
}
-inline ResourceName ParseNameOrDie(const StringPiece& str) {
+inline ResourceName ParseNameOrDie(const android::StringPiece& str) {
ResourceNameRef ref;
CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name";
return ref.ToResourceName();
}
-inline ConfigDescription ParseConfigOrDie(const StringPiece& str) {
+inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
ConfigDescription config;
CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration";
return config;
}
template <typename T>
-T* GetValueForConfigAndProduct(ResourceTable* table,
- const StringPiece& res_name,
+T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& res_name,
const ConfigDescription& config,
- const StringPiece& product) {
+ const android::StringPiece& product) {
Maybe<ResourceTable::SearchResult> result =
table->FindResource(ParseNameOrDie(res_name));
if (result) {
@@ -98,19 +97,19 @@
}
template <typename T>
-T* GetValueForConfig(ResourceTable* table, const StringPiece& res_name,
+T* GetValueForConfig(ResourceTable* table, const android::StringPiece& res_name,
const ConfigDescription& config) {
return GetValueForConfigAndProduct<T>(table, res_name, config, {});
}
template <typename T>
-T* GetValue(ResourceTable* table, const StringPiece& res_name) {
+T* GetValue(ResourceTable* table, const android::StringPiece& res_name) {
return GetValueForConfig<T>(table, res_name, {});
}
class TestFile : public io::IFile {
public:
- explicit TestFile(const StringPiece& path) : source_(path) {}
+ explicit TestFile(const android::StringPiece& path) : source_(path) {}
std::unique_ptr<io::IData> OpenAsData() override { return {}; }
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 7986329..63e5f16 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -70,8 +70,8 @@
class ContextBuilder {
public:
- ContextBuilder& SetCompilationPackage(const StringPiece& package) {
- context_->compilation_package_ = package.ToString();
+ ContextBuilder& SetCompilationPackage(const android::StringPiece& package) {
+ context_->compilation_package_ = package.to_string();
return *this;
}
@@ -103,9 +103,8 @@
class StaticSymbolSourceBuilder {
public:
- StaticSymbolSourceBuilder& AddPublicSymbol(
- const StringPiece& name, ResourceId id,
- std::unique_ptr<Attribute> attr = {}) {
+ StaticSymbolSourceBuilder& AddPublicSymbol(const android::StringPiece& name, ResourceId id,
+ std::unique_ptr<Attribute> attr = {}) {
std::unique_ptr<SymbolTable::Symbol> symbol =
util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true);
symbol_source_->name_map_[ParseNameOrDie(name)] = symbol.get();
@@ -114,7 +113,7 @@
return *this;
}
- StaticSymbolSourceBuilder& AddSymbol(const StringPiece& name, ResourceId id,
+ StaticSymbolSourceBuilder& AddSymbol(const android::StringPiece& name, ResourceId id,
std::unique_ptr<Attribute> attr = {}) {
std::unique_ptr<SymbolTable::Symbol> symbol =
util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false);
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index f034607..aa840e2 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -35,6 +35,8 @@
#include <direct.h>
#endif
+using android::StringPiece;
+
namespace aapt {
namespace file {
@@ -72,10 +74,9 @@
inline static int MkdirImpl(const StringPiece& path) {
#ifdef _WIN32
- return _mkdir(path.ToString().c_str());
+ return _mkdir(path.to_string().c_str());
#else
- return mkdir(path.ToString().c_str(),
- S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP);
+ return mkdir(path.to_string().c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP);
#endif
}
@@ -184,7 +185,7 @@
std::vector<std::string>* out_arglist,
std::string* out_error) {
std::string contents;
- if (!android::base::ReadFileToString(path.ToString(), &contents)) {
+ if (!android::base::ReadFileToString(path.to_string(), &contents)) {
if (out_error) *out_error = "failed to read argument-list file";
return false;
}
@@ -192,7 +193,7 @@
for (StringPiece line : util::Tokenize(contents, ' ')) {
line = util::TrimWhitespace(line);
if (!line.empty()) {
- out_arglist->push_back(line.ToString());
+ out_arglist->push_back(line.to_string());
}
}
return true;
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index a157dbd..95c492f 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -22,12 +22,12 @@
#include <vector>
#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
#include "utils/FileMap.h"
#include "Diagnostics.h"
#include "Maybe.h"
#include "Source.h"
-#include "util/StringPiece.h"
namespace aapt {
namespace file {
@@ -50,51 +50,49 @@
kSocket,
};
-FileType GetFileType(const StringPiece& path);
+FileType GetFileType(const android::StringPiece& path);
/*
* Appends a path to `base`, separated by the directory separator.
*/
-void AppendPath(std::string* base, StringPiece part);
+void AppendPath(std::string* base, android::StringPiece part);
/*
* Makes all the directories in `path`. The last element in the path
* is interpreted as a directory.
*/
-bool mkdirs(const StringPiece& path);
+bool mkdirs(const android::StringPiece& path);
/**
* Returns all but the last part of the path.
*/
-StringPiece GetStem(const StringPiece& path);
+android::StringPiece GetStem(const android::StringPiece& path);
/**
* Returns the last part of the path with extension.
*/
-StringPiece GetFilename(const StringPiece& path);
+android::StringPiece GetFilename(const android::StringPiece& path);
/**
* Returns the extension of the path. This is the entire string after
* the first '.' of the last part of the path.
*/
-StringPiece GetExtension(const StringPiece& path);
+android::StringPiece GetExtension(const android::StringPiece& path);
/**
* Converts a package name (com.android.app) to a path: com/android/app
*/
-std::string PackageToPath(const StringPiece& package);
+std::string PackageToPath(const android::StringPiece& package);
/**
* Creates a FileMap for the file at path.
*/
-Maybe<android::FileMap> MmapPath(const StringPiece& path,
- std::string* out_error);
+Maybe<android::FileMap> MmapPath(const android::StringPiece& path, std::string* out_error);
/**
* Reads the file at path and appends each line to the outArgList vector.
*/
-bool AppendArgsFromFile(const StringPiece& path,
- std::vector<std::string>* out_arglist,
+bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist,
std::string* out_error);
/*
@@ -120,7 +118,7 @@
* - Otherwise the full string is matched.
* - match is not case-sensitive.
*/
- bool SetPattern(const StringPiece& pattern);
+ bool SetPattern(const android::StringPiece& pattern);
/**
* Applies the filter, returning true for pass, false for fail.
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index d5c0c8a..cf22322 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -15,9 +15,6 @@
*/
#include "util/Util.h"
-#include "util/BigBuffer.h"
-#include "util/Maybe.h"
-#include "util/StringPiece.h"
#include <utils/Unicode.h>
#include <algorithm>
@@ -25,6 +22,14 @@
#include <string>
#include <vector>
+#include "androidfw/StringPiece.h"
+
+#include "util/BigBuffer.h"
+#include "util/Maybe.h"
+
+using android::StringPiece;
+using android::StringPiece16;
+
namespace aapt {
namespace util {
@@ -36,7 +41,7 @@
StringPiece::const_iterator current;
do {
current = std::find(start, end, sep);
- parts.emplace_back(str.substr(start, current).ToString());
+ parts.emplace_back(str.substr(start, current).to_string());
if (f) {
std::string& part = parts.back();
std::transform(part.begin(), part.end(), part.begin(), f);
@@ -162,7 +167,7 @@
}
if (util::IsJavaClassName(classname)) {
- return classname.ToString();
+ return classname.to_string();
}
if (package.empty()) {
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 05e9cc5..f8fa80e 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -24,11 +24,11 @@
#include <vector>
#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
#include "utils/ByteOrder.h"
#include "util/BigBuffer.h"
#include "util/Maybe.h"
-#include "util/StringPiece.h"
#ifdef _WIN32
// TODO(adamlesinski): remove once http://b/32447322 is resolved.
@@ -44,26 +44,24 @@
namespace aapt {
namespace util {
-std::vector<std::string> Split(const StringPiece& str, char sep);
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep);
+std::vector<std::string> Split(const android::StringPiece& str, char sep);
+std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
/**
* Returns true if the string starts with prefix.
*/
-bool StartsWith(const StringPiece& str, const StringPiece& prefix);
+bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix);
/**
* Returns true if the string ends with suffix.
*/
-bool EndsWith(const StringPiece& str, const StringPiece& suffix);
+bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix);
/**
* Creates a new StringPiece16 that points to a substring
* of the original string without leading or trailing whitespace.
*/
-StringPiece TrimWhitespace(const StringPiece& str);
-
-StringPiece TrimWhitespace(const StringPiece& str);
+android::StringPiece TrimWhitespace(const android::StringPiece& str);
/**
* UTF-16 isspace(). It basically checks for lower range characters that are
@@ -75,18 +73,18 @@
* Returns an iterator to the first character that is not alpha-numeric and that
* is not in the allowedChars set.
*/
-StringPiece::const_iterator FindNonAlphaNumericAndNotInSet(
- const StringPiece& str, const StringPiece& allowed_chars);
+android::StringPiece::const_iterator FindNonAlphaNumericAndNotInSet(
+ const android::StringPiece& str, const android::StringPiece& allowed_chars);
/**
* Tests that the string is a valid Java class name.
*/
-bool IsJavaClassName(const StringPiece& str);
+bool IsJavaClassName(const android::StringPiece& str);
/**
* Tests that the string is a valid Java package name.
*/
-bool IsJavaPackageName(const StringPiece& str);
+bool IsJavaPackageName(const android::StringPiece& str);
/**
* Converts the class name to a fully qualified class name from the given
@@ -97,8 +95,8 @@
* .a.b --> package.a.b
* asdf.adsf --> asdf.adsf
*/
-Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package,
- const StringPiece& class_name);
+Maybe<std::string> GetFullyQualifiedClassName(const android::StringPiece& package,
+ const android::StringPiece& class_name);
/**
* Makes a std::unique_ptr<> with the template parameter inferred by the
@@ -138,7 +136,7 @@
* stored as UTF-8,
* the conversion to UTF-16 happens within ResStringPool.
*/
-StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx);
+android::StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx);
/**
* Helper method to extract a UTF-8 string from a StringPool. If the string is
@@ -159,11 +157,11 @@
* which will
* break the string interpolation.
*/
-bool VerifyJavaStringFormat(const StringPiece& str);
+bool VerifyJavaStringFormat(const android::StringPiece& str);
class StringBuilder {
public:
- StringBuilder& Append(const StringPiece& str);
+ StringBuilder& Append(const android::StringPiece& str);
const std::string& ToString() const;
const std::string& Error() const;
@@ -194,8 +192,8 @@
/**
* Converts a UTF8 string to a UTF16 string.
*/
-std::u16string Utf8ToUtf16(const StringPiece& utf8);
-std::string Utf16ToUtf8(const StringPiece16& utf16);
+std::u16string Utf8ToUtf16(const android::StringPiece& utf8);
+std::string Utf16ToUtf8(const android::StringPiece16& utf16);
/**
* Writes the entire BigBuffer to the output stream.
@@ -220,22 +218,22 @@
iterator& operator++();
- StringPiece operator*() { return token_; }
+ android::StringPiece operator*() { return token_; }
bool operator==(const iterator& rhs) const;
bool operator!=(const iterator& rhs) const;
private:
friend class Tokenizer;
- iterator(StringPiece s, char sep, StringPiece tok, bool end);
+ iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end);
- StringPiece str_;
+ android::StringPiece str_;
char separator_;
- StringPiece token_;
+ android::StringPiece token_;
bool end_;
};
- Tokenizer(StringPiece str, char sep);
+ Tokenizer(android::StringPiece str, char sep);
iterator begin() { return begin_; }
@@ -246,9 +244,7 @@
const iterator end_;
};
-inline Tokenizer Tokenize(const StringPiece& str, char sep) {
- return Tokenizer(str, sep);
-}
+inline Tokenizer Tokenize(const android::StringPiece& str, char sep) { return Tokenizer(str, sep); }
inline uint16_t HostToDevice16(uint16_t value) { return htods(value); }
@@ -267,8 +263,8 @@
*
* Returns true if successful.
*/
-bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
- StringPiece* out_entry, StringPiece* out_suffix);
+bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix,
+ android::StringPiece* out_entry, android::StringPiece* out_suffix);
} // namespace util
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index cac3de4..e49aee5 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -20,6 +20,8 @@
#include "test/Test.h"
+using android::StringPiece;
+
namespace aapt {
TEST(UtilTest, TrimOnlyWhitespace) {
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 960d361..fab2f19 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -29,6 +29,9 @@
#include "XmlPullParser.h"
#include "util/Util.h"
+using android::StringPiece;
+using android::StringPiece16;
+
namespace aapt {
namespace xml {
@@ -52,10 +55,10 @@
if (*p == 0) {
out_ns->clear();
- *out_name = StringPiece(name).ToString();
+ out_name->assign(name);
} else {
- *out_ns = StringPiece(name, (p - name)).ToString();
- *out_name = StringPiece(p + 1).ToString();
+ out_ns->assign(name, (p - name));
+ out_name->assign(p + 1);
}
}
@@ -83,11 +86,11 @@
std::unique_ptr<Namespace> ns = util::make_unique<Namespace>();
if (prefix) {
- ns->namespace_prefix = StringPiece(prefix).ToString();
+ ns->namespace_prefix = prefix;
}
if (uri) {
- ns->namespace_uri = StringPiece(uri).ToString();
+ ns->namespace_uri = uri;
}
AddToStack(stack, parser, std::move(ns));
@@ -117,7 +120,7 @@
while (*attrs) {
Attribute attribute;
SplitName(*attrs++, &attribute.namespace_uri, &attribute.name);
- attribute.value = StringPiece(*attrs++).ToString();
+ attribute.value = *attrs++;
// Insert in sorted order.
auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(),
@@ -153,14 +156,14 @@
if (!currentParent->children.empty()) {
Node* last_child = currentParent->children.back().get();
if (Text* text = NodeCast<Text>(last_child)) {
- text->text += StringPiece(s, len).ToString();
+ text->text.append(s, len);
return;
}
}
}
std::unique_ptr<Text> text = util::make_unique<Text>();
- text->text = StringPiece(s, len).ToString();
+ text->text.assign(s, len);
AddToStack(stack, parser, std::move(text));
}
@@ -495,15 +498,14 @@
Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(
const StringPiece& alias, const StringPiece& local_package) const {
if (alias.empty()) {
- return ExtractedPackage{local_package.ToString(), false /* private */};
+ return ExtractedPackage{local_package.to_string(), false /* private */};
}
const auto rend = package_decls_.rend();
for (auto iter = package_decls_.rbegin(); iter != rend; ++iter) {
if (alias == iter->prefix) {
if (iter->package.package.empty()) {
- return ExtractedPackage{local_package.ToString(),
- iter->package.private_namespace};
+ return ExtractedPackage{local_package.to_string(), iter->package.private_namespace};
}
return iter->package;
}
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 720fe35..90cdfb6 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -22,10 +22,11 @@
#include <string>
#include <vector>
+#include "androidfw/StringPiece.h"
+
#include "Diagnostics.h"
#include "Resource.h"
#include "ResourceValues.h"
-#include "util/StringPiece.h"
#include "util/Util.h"
#include "xml/XmlUtil.h"
@@ -100,13 +101,13 @@
std::string name;
std::vector<Attribute> attributes;
- Attribute* FindAttribute(const StringPiece& ns, const StringPiece& name);
- xml::Element* FindChild(const StringPiece& ns, const StringPiece& name);
- xml::Element* FindChildWithAttribute(const StringPiece& ns,
- const StringPiece& name,
- const StringPiece& attr_ns,
- const StringPiece& attr_name,
- const StringPiece& attr_value);
+ Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
+ xml::Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
+ xml::Element* FindChildWithAttribute(const android::StringPiece& ns,
+ const android::StringPiece& name,
+ const android::StringPiece& attr_ns,
+ const android::StringPiece& attr_name,
+ const android::StringPiece& attr_value);
std::vector<xml::Element*> GetChildElements();
std::unique_ptr<Node> Clone() override;
};
@@ -190,8 +191,7 @@
void Visit(Namespace* ns) override;
Maybe<ExtractedPackage> TransformPackageAlias(
- const StringPiece& alias,
- const StringPiece& local_package) const override;
+ const android::StringPiece& alias, const android::StringPiece& local_package) const override;
private:
struct PackageDecl {
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index e59fa86..c2a9c82 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -22,6 +22,8 @@
#include "xml/XmlPullParser.h"
#include "xml/XmlUtil.h"
+using android::StringPiece;
+
namespace aapt {
namespace xml {
@@ -136,15 +138,14 @@
Maybe<ExtractedPackage> XmlPullParser::TransformPackageAlias(
const StringPiece& alias, const StringPiece& local_package) const {
if (alias.empty()) {
- return ExtractedPackage{local_package.ToString(), false /* private */};
+ return ExtractedPackage{local_package.to_string(), false /* private */};
}
const auto end_iter = package_aliases_.rend();
for (auto iter = package_aliases_.rbegin(); iter != end_iter; ++iter) {
if (alias == iter->prefix) {
if (iter->package.package.empty()) {
- return ExtractedPackage{local_package.ToString(),
- iter->package.private_namespace};
+ return ExtractedPackage{local_package.to_string(), iter->package.private_namespace};
}
return iter->package;
}
@@ -188,19 +189,18 @@
/**
* Extracts the namespace and name of an expanded element or attribute name.
*/
-static void SplitName(const char* name, std::string& out_ns,
- std::string& out_name) {
+static void SplitName(const char* name, std::string* out_ns, std::string* out_name) {
const char* p = name;
while (*p != 0 && *p != kXmlNamespaceSep) {
p++;
}
if (*p == 0) {
- out_ns = std::string();
- out_name = name;
+ out_ns->clear();
+ out_name->assign(name);
} else {
- out_ns = StringPiece(name, (p - name)).ToString();
- out_name = p + 1;
+ out_ns->assign(name, (p - name));
+ out_name->assign(p + 1);
}
}
@@ -224,11 +224,11 @@
EventData data = {Event::kStartElement,
XML_GetCurrentLineNumber(parser->parser_),
parser->depth_++};
- SplitName(name, data.data1, data.data2);
+ SplitName(name, &data.data1, &data.data2);
while (*attrs) {
Attribute attribute;
- SplitName(*attrs++, attribute.namespace_uri, attribute.name);
+ SplitName(*attrs++, &attribute.namespace_uri, &attribute.name);
attribute.value = *attrs++;
// Insert in sorted order.
@@ -245,9 +245,8 @@
int len) {
XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
- parser->event_queue_.push(
- EventData{Event::kText, XML_GetCurrentLineNumber(parser->parser_),
- parser->depth_, StringPiece(s, len).ToString()});
+ parser->event_queue_.push(EventData{Event::kText, XML_GetCurrentLineNumber(parser->parser_),
+ parser->depth_, std::string(s, len)});
}
void XMLCALL XmlPullParser::EndElementHandler(void* user_data,
@@ -257,7 +256,7 @@
EventData data = {Event::kEndElement,
XML_GetCurrentLineNumber(parser->parser_),
--(parser->depth_)};
- SplitName(name, data.data1, data.data2);
+ SplitName(name, &data.data1, &data.data2);
// Move the data into the queue (no copy).
parser->event_queue_.push(std::move(data));
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index ff58d60..cdeeefd 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -28,11 +28,11 @@
#include <vector>
#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
#include "Resource.h"
#include "process/IResourceTableConsumer.h"
#include "util/Maybe.h"
-#include "util/StringPiece.h"
#include "xml/XmlUtil.h"
namespace aapt {
@@ -119,8 +119,7 @@
* 'package' will be set to 'defaultPackage'.
*/
Maybe<ExtractedPackage> TransformPackageAlias(
- const StringPiece& alias,
- const StringPiece& local_package) const override;
+ const android::StringPiece& alias, const android::StringPiece& local_package) const override;
//
// Remaining methods are for retrieving information about attributes
@@ -146,8 +145,7 @@
const_iterator begin_attributes() const;
const_iterator end_attributes() const;
size_t attribute_count() const;
- const_iterator FindAttribute(StringPiece namespace_uri,
- StringPiece name) const;
+ const_iterator FindAttribute(android::StringPiece namespace_uri, android::StringPiece name) const;
private:
DISALLOW_COPY_AND_ASSIGN(XmlPullParser);
@@ -190,16 +188,16 @@
/**
* Finds the attribute in the current element within the global namespace.
*/
-Maybe<StringPiece> FindAttribute(const XmlPullParser* parser,
- const StringPiece& name);
+Maybe<android::StringPiece> FindAttribute(const XmlPullParser* parser,
+ const android::StringPiece& name);
/**
* Finds the attribute in the current element within the global namespace. The
* attribute's value
* must not be the empty string.
*/
-Maybe<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
- const StringPiece& name);
+Maybe<android::StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
+ const android::StringPiece& name);
//
// Implementation
@@ -299,13 +297,13 @@
}
inline XmlPullParser::const_iterator XmlPullParser::FindAttribute(
- StringPiece namespace_uri, StringPiece name) const {
+ android::StringPiece namespace_uri, android::StringPiece name) const {
const auto end_iter = end_attributes();
const auto iter = std::lower_bound(
begin_attributes(), end_iter,
- std::pair<StringPiece, StringPiece>(namespace_uri, name),
+ std::pair<android::StringPiece, android::StringPiece>(namespace_uri, name),
[](const Attribute& attr,
- const std::pair<StringPiece, StringPiece>& rhs) -> bool {
+ const std::pair<android::StringPiece, android::StringPiece>& rhs) -> bool {
int cmp = attr.namespace_uri.compare(
0, attr.namespace_uri.size(), rhs.first.data(), rhs.first.size());
if (cmp < 0) return true;
diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
index 4f18cd2..1cce485 100644
--- a/tools/aapt2/xml/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -18,8 +18,11 @@
#include <sstream>
+#include "androidfw/StringPiece.h"
+
#include "test/Test.h"
-#include "util/StringPiece.h"
+
+using android::StringPiece;
namespace aapt {
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index d00f7f2..fb8cee8 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -21,6 +21,8 @@
#include "util/Maybe.h"
#include "util/Util.h"
+using android::StringPiece;
+
namespace aapt {
namespace xml {
@@ -42,7 +44,7 @@
if (package.empty()) {
return {};
}
- return ExtractedPackage{package.ToString(), false /* is_private */};
+ return ExtractedPackage{package.to_string(), false /* is_private */};
} else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
StringPiece schema_prefix = kSchemaPrivatePrefix;
@@ -52,7 +54,7 @@
if (package.empty()) {
return {};
}
- return ExtractedPackage{package.ToString(), true /* is_private */};
+ return ExtractedPackage{package.to_string(), true /* is_private */};
} else if (namespace_uri == kSchemaAuto) {
return ExtractedPackage{std::string(), true /* is_private */};
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 5365401..1650ac2 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -74,7 +74,7 @@
*
* http://schemas.android.com/apk/prv/res/<package>
*/
-std::string BuildPackageNamespace(const StringPiece& package,
+std::string BuildPackageNamespace(const android::StringPiece& package,
bool private_reference = false);
/**
@@ -90,7 +90,7 @@
* package declaration.
*/
virtual Maybe<ExtractedPackage> TransformPackageAlias(
- const StringPiece& alias, const StringPiece& local_package) const = 0;
+ const android::StringPiece& alias, const android::StringPiece& local_package) const = 0;
};
/**
@@ -100,8 +100,7 @@
* the namespace of the package declaration was private.
*/
void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack,
- const StringPiece& local_package,
- Reference* in_ref);
+ const android::StringPiece& local_package, Reference* in_ref);
} // namespace xml
} // namespace aapt
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index d326935..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;
@@ -292,7 +293,7 @@
@LayoutlibDelegate
/*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
- int ttcIndex, List<FontListParser.Axis> listOfAxis,
+ int ttcIndex, List<FontConfig.Axis> listOfAxis,
int weight, boolean isItalic) {
assert false : "The only client of this method has been overriden.";
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 a554b6d..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,12 +209,12 @@
}
@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.mBuilderPtr, 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/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 663e56d..dff4f69 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -40,6 +40,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -1324,6 +1325,12 @@
}
@Override
+ public Context createContextForSplit(String splitName) {
+ // pass
+ return null;
+ }
+
+ @Override
public String[] databaseList() {
// pass
return null;
@@ -1820,6 +1827,13 @@
}
@Override
+ public ComponentName startServiceInForeground(Intent service,
+ int id, Notification notification) {
+ // pass
+ return null;
+ }
+
+ @Override
public boolean stopService(Intent arg0) {
// pass
return false;
@@ -1832,6 +1846,13 @@
}
@Override
+ public ComponentName startServiceInForegroundAsUser(Intent service,
+ int id, Notification notification, UserHandle user) {
+ // pass
+ return null;
+ }
+
+ @Override
public boolean stopServiceAsUser(Intent arg0, UserHandle arg1) {
// pass
return false;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index bc7bc74..d325ee9 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -42,14 +42,15 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
import java.util.List;
@@ -71,6 +72,23 @@
}
@Override
+ public PackageInfo getPackageInfo(VersionedPackage versionedPackage,
+ @PackageInfoFlags int flags) throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public List<SharedLibraryInfo> getSharedLibraries(@InstallFlags int flags) {
+ return null;
+ }
+
+ @Override
+ public List<SharedLibraryInfo> getSharedLibrariesAsUser(@InstallFlags int flags,
+ int userId) {
+ return null;
+ }
+
+ @Override
public String[] currentToCanonicalPackageNames(String[] names) {
return new String[0];
}
@@ -873,4 +891,9 @@
public int getInstallReason(String packageName, UserHandle user) {
return INSTALL_REASON_UNKNOWN;
}
+
+ @Override
+ public boolean canRequestPackageInstalls() {
+ return false;
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index a83f100..a51ad2e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -108,6 +108,10 @@
}
@Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ }
+
+ @Override
public IBinder asBinder() {
// pass for now.
return null;
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 3a45671..8cf7a24 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -709,6 +709,16 @@
/**
* @hide
+ * Indicate that a WifiConfiguration is temporary and should not be saved
+ * nor considered by AutoJoin.
+ */
+ @SystemApi
+ public boolean isEphemeral() {
+ return ephemeral;
+ }
+
+ /**
+ * @hide
* A hint about whether or not the network represented by this WifiConfiguration
* is metered. This is hinted at via the meteredHint bit on DHCP results set in
* {@link com.android.server.wifi.WifiStateMachine}, or via a network score in
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e410a9c..5028d47 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -142,7 +142,7 @@
private HashMap<String, String> mFields = new HashMap<String, String>();
private X509Certificate[] mCaCerts;
private PrivateKey mClientPrivateKey;
- private X509Certificate mClientCertificate;
+ private X509Certificate[] mClientCertificateChain;
private int mEapMethod = Eap.NONE;
private int mPhase2Method = Phase2.NONE;
@@ -161,9 +161,19 @@
for (String key : source.mFields.keySet()) {
mFields.put(key, source.mFields.get(key));
}
- mCaCerts = source.mCaCerts;
+ if (source.mCaCerts != null) {
+ mCaCerts = Arrays.copyOf(source.mCaCerts, source.mCaCerts.length);
+ } else {
+ mCaCerts = null;
+ }
mClientPrivateKey = source.mClientPrivateKey;
- mClientCertificate = source.mClientCertificate;
+ if (source.mClientCertificateChain != null) {
+ mClientCertificateChain = Arrays.copyOf(
+ source.mClientCertificateChain,
+ source.mClientCertificateChain.length);
+ } else {
+ mClientCertificateChain = null;
+ }
mEapMethod = source.mEapMethod;
mPhase2Method = source.mPhase2Method;
}
@@ -185,7 +195,7 @@
dest.writeInt(mPhase2Method);
ParcelUtil.writeCertificates(dest, mCaCerts);
ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
- ParcelUtil.writeCertificate(dest, mClientCertificate);
+ ParcelUtil.writeCertificates(dest, mClientCertificateChain);
}
public static final Creator<WifiEnterpriseConfig> CREATOR =
@@ -204,7 +214,7 @@
enterpriseConfig.mPhase2Method = in.readInt();
enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
- enterpriseConfig.mClientCertificate = ParcelUtil.readCertificate(in);
+ enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
return enterpriseConfig;
}
@@ -742,10 +752,51 @@
* @throws IllegalArgumentException for an invalid key or certificate.
*/
public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
- if (clientCertificate != null) {
- if (clientCertificate.getBasicConstraints() != -1) {
- throw new IllegalArgumentException("Cannot be a CA certificate");
+ setClientKeyEntryWithCertificateChain(privateKey,
+ new X509Certificate[] {clientCertificate});
+ }
+
+ /**
+ * Specify a private key and client certificate chain for client authorization.
+ *
+ * <p>A default name is automatically assigned to the key entry and used
+ * with this configuration. The framework takes care of installing the
+ * key entry when the config is saved and removing the key entry when
+ * the config is removed.
+
+ * @param privateKey
+ * @param clientCertificateChain
+ * @throws IllegalArgumentException for an invalid key or certificate.
+ */
+ public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey,
+ X509Certificate[] clientCertificateChain) {
+ X509Certificate[] newCerts = null;
+ if (clientCertificateChain != null && clientCertificateChain.length > 0) {
+ // We validate that this is a well formed chain that starts
+ // with an end-certificate and is followed by CA certificates.
+ // We don't validate that each following certificate verifies
+ // the previous. https://en.wikipedia.org/wiki/Chain_of_trust
+ //
+ // Basic constraints is an X.509 extension type that defines
+ // whether a given certificate is allowed to sign additional
+ // certificates and what path length restrictions may exist.
+ // We use this to judge whether the certificate is an end
+ // certificate or a CA certificate.
+ // https://cryptography.io/en/latest/x509/reference/
+ if (clientCertificateChain[0].getBasicConstraints() != -1) {
+ throw new IllegalArgumentException(
+ "First certificate in the chain must be a client end certificate");
}
+
+ for (int i = 1; i < clientCertificateChain.length; i++) {
+ if (clientCertificateChain[i].getBasicConstraints() == -1) {
+ throw new IllegalArgumentException(
+ "All certificates following the first must be CA certificates");
+ }
+ }
+ newCerts = Arrays.copyOf(clientCertificateChain,
+ clientCertificateChain.length);
+
if (privateKey == null) {
throw new IllegalArgumentException("Client cert without a private key");
}
@@ -755,7 +806,7 @@
}
mClientPrivateKey = privateKey;
- mClientCertificate = clientCertificate;
+ mClientCertificateChain = newCerts;
}
/**
@@ -764,7 +815,24 @@
* @return X.509 client certificate
*/
public X509Certificate getClientCertificate() {
- return mClientCertificate;
+ if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+ return mClientCertificateChain[0];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the complete client certificate chain
+ *
+ * @return X.509 client certificates
+ */
+ @Nullable public X509Certificate[] getClientCertificateChain() {
+ if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+ return mClientCertificateChain;
+ } else {
+ return null;
+ }
}
/**
@@ -772,7 +840,7 @@
*/
public void resetClientKeyEntry() {
mClientPrivateKey = null;
- mClientCertificate = null;
+ mClientCertificateChain = null;
}
/**
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 3b6e76f..ab725e2 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -120,11 +120,7 @@
public static final String PASSPOINT_ICON_RECEIVED_ACTION =
"android.net.wifi.PASSPOINT_ICON_RECEIVED";
/** @hide */
- public static final String EXTRA_PASSPOINT_ICON_BSSID = "bssid";
- /** @hide */
public static final String EXTRA_PASSPOINT_ICON_FILE = "file";
- /** @hide */
- public static final String EXTRA_PASSPOINT_ICON_DATA = "icon";
/**
* Broadcast intent action indicating that the a Passpoint release
@@ -159,6 +155,127 @@
public static final String EXTRA_PASSPOINT_WNM_DELAY = "delay";
/**
+ * Broadcast intent action indicating that a Passpoint provider icon has been received.
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ */
+ public static final String ACTION_PASSPOINT_ICON =
+ "android.net.wifi.action.PASSPOINT_ICON";
+ /**
+ * BSSID of the sender.
+ *
+ * Type: long
+ */
+ public static final String EXTRA_PASSPOINT_ICON_BSSID =
+ "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+ /**
+ * Filename of the icon.
+ *
+ * Type: String
+ */
+ public static final String EXTRA_PASSPOINT_ICON_FILENAME =
+ "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+ /**
+ * Binary blob of the icon.
+ *
+ * Type: byte[]
+ */
+ public static final String EXTRA_PASSPOINT_ICON_DATA =
+ "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+
+ /**
+ * Broadcast intent action indicating a Passpoint OSU Providers List element has been received.
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ */
+ public static final String ACTION_PASSPOINT_OSU_PROVIDERS_LIST =
+ "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+ /**
+ * BSSID of the sender.
+ *
+ * Type: long
+ */
+ public static final String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID =
+ "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+ /**
+ * Raw data of OSU Providers List ANQP element. Refer to Section 4.8 of Hotspot 2.0 Release 2
+ * Technical Specification for the exact data format.
+ *
+ * Type: byte[]
+ */
+ public static final String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA =
+ "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+
+ /**
+ * Broadcast intent action indicating that a Passpoint Deauth Imminent frame has been received.
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ */
+ public static final String ACTION_PASSPOINT_DEAUTH_IMMINENT =
+ "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+ /**
+ * The BSSID of the sender.
+ *
+ * Type: long
+ */
+ public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID =
+ "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+ /**
+ * Flag indicating failure at BSS (Basic Service Set) or ESS (Extended Service Set) level.
+ *
+ * Type: boolean
+ */
+ public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS =
+ "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+ /**
+ * Delay in seconds that a device shall wait before attempting re-association to the same BSS
+ * or ESS (as indicated by {@link #EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS}.
+ *
+ * Type: int
+ */
+ public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY =
+ "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+ /**
+ * URL that provides a webpage explaining the deauth reason.
+ *
+ * Type: String
+ */
+ public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL =
+ "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+
+ /**
+ * Broadcast intent action indicating a Passpoint subscription remediation frame has been
+ * received.
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ */
+ public static final String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION =
+ "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
+ /**
+ * The BSSID of the sender.
+ *
+ * Type: long
+ */
+ public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID =
+ "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+ /**
+ * The protocol supported by the subscription remediation server. The possible values are:
+ * 0 - OMA DM
+ * 1 - SOAP XML SPP
+ *
+ * Type: int
+ */
+ public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD =
+ "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+ /**
+ * URL of the subscription remediation server.
+ *
+ * Type: String
+ */
+ public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL =
+ "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
+
+ /**
* Broadcast intent action indicating that Wi-Fi has been enabled, disabled,
* enabling, disabling, or unknown. One extra provides this state as an int.
* Another extra provides the previous state, if available.
@@ -898,10 +1015,10 @@
}
/**
- * Query for a Hotspot 2.0 release 2 OSU icon
+ * Query for a Hotspot 2.0 release 2 OSU icon file.
+ *
* @param bssid The BSSID of the AP
- * @param fileName Icon file name
- * @hide
+ * @param fileName File name of the icon to query
*/
public void queryPasspointIcon(long bssid, String fileName) {
try {
diff --git a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
index 71124bb..36e4717 100755
--- a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
+++ b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
@@ -43,7 +43,7 @@
*/
public class WifiNetworkScoreCache extends INetworkScoreCache.Stub {
private static final String TAG = "WifiNetworkScoreCache";
- private static final boolean DBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
// A Network scorer returns a score in the range [-128, +127]
// We treat the lowest possible score as though there were no score, effectively allowing the
@@ -83,18 +83,28 @@
if (networks == null || networks.isEmpty()) {
return;
}
- Log.d(TAG, "updateScores list size=" + networks.size());
+ if (DBG) {
+ Log.d(TAG, "updateScores list size=" + networks.size());
+ }
+
+ boolean changed = false;
synchronized(mNetworkCache) {
for (ScoredNetwork network : networks) {
String networkKey = buildNetworkKey(network);
- if (networkKey == null) continue;
+ if (networkKey == null) {
+ if (DBG) {
+ Log.d(TAG, "Failed to build network key for ScoredNetwork" + network);
+ }
+ continue;
+ }
mNetworkCache.put(networkKey, network);
+ changed = true;
}
}
synchronized (mCacheLock) {
- if (mListener != null) {
+ if (mListener != null && changed) {
mListener.post(networks);
}
}
@@ -170,25 +180,49 @@
return score;
}
- private ScoredNetwork getScoredNetwork(ScanResult result) {
+ @Nullable
+ public ScoredNetwork getScoredNetwork(ScanResult result) {
String key = buildNetworkKey(result);
if (key == null) return null;
- //find it
synchronized(mNetworkCache) {
ScoredNetwork network = mNetworkCache.get(key);
return network;
}
}
- private String buildNetworkKey(ScoredNetwork network) {
- if (network == null || network.networkKey == null) return null;
- if (network.networkKey.wifiKey == null) return null;
- if (network.networkKey.type == NetworkKey.TYPE_WIFI) {
- String key = network.networkKey.wifiKey.ssid;
+ /** Returns the ScoredNetwork for the given key. */
+ @Nullable
+ public ScoredNetwork getScoredNetwork(NetworkKey networkKey) {
+ String key = buildNetworkKey(networkKey);
+ if (key == null) {
+ if (DBG) {
+ Log.d(TAG, "Could not build key string for Network Key: " + networkKey);
+ }
+ return null;
+ }
+ synchronized (mNetworkCache) {
+ return mNetworkCache.get(key);
+ }
+ }
+
+ private String buildNetworkKey(ScoredNetwork network) {
+ if (network == null) {
+ return null;
+ }
+ return buildNetworkKey(network.networkKey);
+ }
+
+ private String buildNetworkKey(NetworkKey networkKey) {
+ if (networkKey == null) {
+ return null;
+ }
+ if (networkKey.wifiKey == null) return null;
+ if (networkKey.type == NetworkKey.TYPE_WIFI) {
+ String key = networkKey.wifiKey.ssid;
if (key == null) return null;
- if (network.networkKey.wifiKey.bssid != null) {
- key = key + network.networkKey.wifiKey.bssid;
+ if (networkKey.wifiKey.bssid != null) {
+ key = key + networkKey.wifiKey.bssid;
}
return key;
}
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 643753a..ca4d121 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -18,9 +18,19 @@
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
import android.os.Parcel;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Class representing Passpoint configuration. This contains configurations specified in
* PerProviderSubscription (PPS) Management Object (MO) tree.
@@ -28,13 +38,108 @@
* 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, only HomeSP and Credential subtrees are supported.
- *
* @hide
*/
public final class PasspointConfiguration implements Parcelable {
+ private static final String TAG = "PasspointConfiguration";
+
+ /**
+ * Number of bytes for certificate SHA-256 fingerprint byte array.
+ */
+ private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+ /**
+ * Maximum bytes for URL string.
+ */
+ private static final int MAX_URL_BYTES = 1023;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
public HomeSP homeSp = null;
public Credential credential = null;
+ public Policy policy = null;
+
+ /**
+ * Meta data for performing subscription update.
+ */
+ public UpdateParameter subscriptionUpdate = null;
+
+ /**
+ * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256
+ * fingerprint of the certificate. The certificates are used for verifying AAA server's
+ * identity during EAP authentication.
+ */
+ public Map<String, byte[]> trustRootCertList = null;
+
+ /**
+ * Set by the subscription server, updated every time the configuration is updated by
+ * the subscription server.
+ *
+ * Use Integer.MIN_VALUE to indicate unset value.
+ */
+ public int updateIdentifier = Integer.MIN_VALUE;
+
+ /**
+ * The priority of the credential.
+ *
+ * Use Integer.MIN_VALUE to indicate unset value.
+ */
+ public int credentialPriority = Integer.MIN_VALUE;
+
+ /**
+ * The time this subscription is created. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long subscriptionCreationTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The time this subscription will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long subscriptionExpirationTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The type of the subscription. This is defined by the provider and the value is provider
+ * specific.
+ */
+ public String subscriptionType = null;
+
+ /**
+ * The time period for usage statistics accumulation. A value of zero means that usage
+ * statistics are not accumulated on a periodic basis (e.g., a one-time limit for
+ * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes.
+ */
+ public long usageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
+
+ /**
+ * The time at which usage statistic accumulation begins. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long usageLimitStartTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+ * A value of zero indicate unlimited data usage.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long usageLimitDataLimit = Long.MIN_VALUE;
+
+ /**
+ * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+ * A value of zero indicate unlimited time usage.
+ */
+ public long usageLimitTimeLimitInMinutes = Long.MIN_VALUE;
+
/**
* Constructor for creating PasspointConfiguration with default values.
@@ -47,14 +152,34 @@
* @param source The source to copy from
*/
public PasspointConfiguration(PasspointConfiguration source) {
- if (source != null) {
- if (source.homeSp != null) {
- homeSp = new HomeSP(source.homeSp);
- }
- if (source.credential != null) {
- credential = new Credential(source.credential);
- }
+ if (source == null) {
+ return;
}
+
+ if (source.homeSp != null) {
+ homeSp = new HomeSP(source.homeSp);
+ }
+ if (source.credential != null) {
+ credential = new Credential(source.credential);
+ }
+ if (source.policy != null) {
+ policy = new Policy(source.policy);
+ }
+ if (source.trustRootCertList != null) {
+ trustRootCertList = Collections.unmodifiableMap(source.trustRootCertList);
+ }
+ if (source.subscriptionUpdate != null) {
+ subscriptionUpdate = new UpdateParameter(source.subscriptionUpdate);
+ }
+ updateIdentifier = source.updateIdentifier;
+ credentialPriority = source.credentialPriority;
+ subscriptionCreationTimeInMs = source.subscriptionCreationTimeInMs;
+ subscriptionExpirationTimeInMs = source.subscriptionExpirationTimeInMs;
+ subscriptionType = source.subscriptionType;
+ usageLimitDataLimit = source.usageLimitDataLimit;
+ usageLimitStartTimeInMs = source.usageLimitStartTimeInMs;
+ usageLimitTimeLimitInMinutes = source.usageLimitTimeLimitInMinutes;
+ usageLimitUsageTimePeriodInMinutes = source.usageLimitUsageTimePeriodInMinutes;
}
@Override
@@ -66,6 +191,18 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(homeSp, flags);
dest.writeParcelable(credential, flags);
+ dest.writeParcelable(policy, flags);
+ dest.writeParcelable(subscriptionUpdate, flags);
+ writeTrustRootCerts(dest, trustRootCertList);
+ dest.writeInt(updateIdentifier);
+ dest.writeInt(credentialPriority);
+ dest.writeLong(subscriptionCreationTimeInMs);
+ dest.writeLong(subscriptionExpirationTimeInMs);
+ dest.writeString(subscriptionType);
+ dest.writeLong(usageLimitUsageTimePeriodInMinutes);
+ dest.writeLong(usageLimitStartTimeInMs);
+ dest.writeLong(usageLimitDataLimit);
+ dest.writeLong(usageLimitTimeLimitInMinutes);
}
@Override
@@ -77,9 +214,22 @@
return false;
}
PasspointConfiguration that = (PasspointConfiguration) thatObject;
- return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp)) &&
- (credential == null ? that.credential == null :
- credential.equals(that.credential));
+ return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp))
+ && (credential == null ? that.credential == null
+ : credential.equals(that.credential))
+ && (policy == null) ? that.policy == null : policy.equals(that.policy)
+ && (subscriptionUpdate == null) ? that.subscriptionUpdate == null
+ : subscriptionUpdate.equals(that.subscriptionUpdate)
+ && isTrustRootCertListEquals(trustRootCertList, that.trustRootCertList)
+ && updateIdentifier == that.updateIdentifier
+ && credentialPriority == that.credentialPriority
+ && subscriptionCreationTimeInMs == that.subscriptionCreationTimeInMs
+ && subscriptionExpirationTimeInMs == that.subscriptionExpirationTimeInMs
+ && TextUtils.equals(subscriptionType, that.subscriptionType)
+ && usageLimitUsageTimePeriodInMinutes == that.usageLimitUsageTimePeriodInMinutes
+ && usageLimitStartTimeInMs == that.usageLimitStartTimeInMs
+ && usageLimitDataLimit == that.usageLimitDataLimit
+ && usageLimitTimeLimitInMinutes == that .usageLimitTimeLimitInMinutes;
}
/**
@@ -94,6 +244,37 @@
if (credential == null || !credential.validate()) {
return false;
}
+ if (policy != null && !policy.validate()) {
+ return false;
+ }
+ if (subscriptionUpdate != null && !subscriptionUpdate.validate()) {
+ return false;
+ }
+ if (trustRootCertList != null) {
+ for (Map.Entry<String, byte[]> entry : trustRootCertList.entrySet()) {
+ String url = entry.getKey();
+ byte[] certFingerprint = entry.getValue();
+ if (TextUtils.isEmpty(url)) {
+ Log.d(TAG, "Empty URL");
+ return false;
+ }
+ if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+ Log.d(TAG, "URL bytes exceeded the max: "
+ + url.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (certFingerprint == null) {
+ Log.d(TAG, "Fingerprint not specified");
+ return false;
+ }
+ if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) {
+ Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+ + certFingerprint.length);
+ return false;
+ }
+ }
+ }
return true;
}
@@ -104,11 +285,88 @@
PasspointConfiguration config = new PasspointConfiguration();
config.homeSp = in.readParcelable(null);
config.credential = in.readParcelable(null);
+ config.policy = in.readParcelable(null);
+ config.subscriptionUpdate = in.readParcelable(null);
+ config.trustRootCertList = readTrustRootCerts(in);
+ config.updateIdentifier = in.readInt();
+ config.credentialPriority = in.readInt();
+ config.subscriptionCreationTimeInMs = in.readLong();
+ config.subscriptionExpirationTimeInMs = in.readLong();
+ config.subscriptionType = in.readString();
+ config.usageLimitUsageTimePeriodInMinutes = in.readLong();
+ config.usageLimitStartTimeInMs = in.readLong();
+ config.usageLimitDataLimit = in.readLong();
+ config.usageLimitTimeLimitInMinutes = in.readLong();
return config;
}
+
@Override
public PasspointConfiguration[] newArray(int size) {
return new PasspointConfiguration[size];
}
+
+ /**
+ * Helper function for reading trust root certificate info list from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return The list of trust root certificate URL with the corresponding certificate
+ * fingerprint
+ */
+ private Map<String, byte[]> readTrustRootCerts(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<String, byte[]> trustRootCerts = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ String key = in.readString();
+ byte[] value = in.createByteArray();
+ trustRootCerts.put(key, value);
+ }
+ return trustRootCerts;
+ }
};
+
+ /**
+ * Helper function for writing trust root certificate information list.
+ *
+ * @param dest The Parcel to write to
+ * @param trustRootCerts The list of trust root certificate URL with the corresponding
+ * certificate fingerprint
+ */
+ private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) {
+ if (trustRootCerts == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(trustRootCerts.size());
+ for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeByteArray(entry.getValue());
+ }
+ }
+
+ /**
+ * Helper function for comparing two trust root certificate list. Cannot use Map#equals
+ * method since the value type (byte[]) doesn't override equals method.
+ *
+ * @param list1 The first trust root certificate list
+ * @param list2 The second trust root certificate list
+ * @return true if the two list are equal
+ */
+ private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1,
+ Map<String, byte[]> list2) {
+ if (list1 == null || list2 == null) {
+ return list1 == list2;
+ }
+ if (list1.size() != list2.size()) {
+ return false;
+ }
+ for (Map.Entry<String, byte[]> entry : list1.entrySet()) {
+ if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
index 65a49ea..22b0f97 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -19,13 +19,21 @@
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
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;
@@ -125,29 +133,89 @@
private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
/**
+ * Fields under PerProviderSubscription.
+ */
+ private static final String NODE_UPDATE_IDENTIFIER = "UpdateIdentifier";
+ private static final String NODE_AAA_SERVER_TRUST_ROOT = "AAAServerTrustRoot";
+ private static final String NODE_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
+ private static final String NODE_SUBSCRIPTION_PARAMETER = "SubscriptionParameter";
+ private static final String NODE_TYPE_OF_SUBSCRIPTION = "TypeOfSubscription";
+ private static final String NODE_USAGE_LIMITS = "UsageLimits";
+ private static final String NODE_DATA_LIMIT = "DataLimit";
+ private static final String NODE_START_DATE = "StartDate";
+ private static final String NODE_TIME_LIMIT = "TimeLimit";
+ private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
+ private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
+ /**
* Fields under HomeSP subtree.
*/
private static final String NODE_HOMESP = "HomeSP";
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";
- private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256FingerPrint";
+ private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
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";
+
+ /**
+ * Fields under Policy subtree.
+ */
+ private static final String NODE_POLICY = "Policy";
+ private static final String NODE_PREFERRED_ROAMING_PARTNER_LIST =
+ "PreferredRoamingPartnerList";
+ private static final String NODE_FQDN_MATCH = "FQDN_Match";
+ private static final String NODE_PRIORITY = "Priority";
+ private static final String NODE_COUNTRY = "Country";
+ private static final String NODE_MIN_BACKHAUL_THRESHOLD = "MinBackhaulThreshold";
+ private static final String NODE_NETWORK_TYPE = "NetworkType";
+ private static final String NODE_DOWNLINK_BANDWIDTH = "DLBandwidth";
+ private static final String NODE_UPLINK_BANDWIDTH = "ULBandwidth";
+ private static final String NODE_POLICY_UPDATE = "PolicyUpdate";
+ private static final String NODE_UPDATE_INTERVAL = "UpdateInterval";
+ private static final String NODE_UPDATE_METHOD = "UpdateMethod";
+ private static final String NODE_RESTRICTION = "Restriction";
+ private static final String NODE_URI = "URI";
+ private static final String NODE_TRUST_ROOT = "TrustRoot";
+ private static final String NODE_CERT_URL = "CertURL";
+ private static final String NODE_SP_EXCLUSION_LIST = "SPExclusionList";
+ private static final String NODE_REQUIRED_PROTO_PORT_TUPLE = "RequiredProtoPortTuple";
+ private static final String NODE_IP_PROTOCOL = "IPProtocol";
+ private static final String NODE_PORT_NUMBER = "PortNumber";
+ private static final String NODE_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
+ private static final String NODE_OTHER = "Other";
/**
* URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
@@ -324,6 +392,10 @@
* ...
* </RTPProperties>
* <Node>
+ * <NodeName>UpdateIdentifier</NodeName>
+ * <Value>...</Value>
+ * </Node>
+ * <Node>
* ...
* </Node>
* </Node>
@@ -336,11 +408,12 @@
throws ParsingException {
PasspointConfiguration config = null;
String nodeName = null;
+ int updateIdentifier = Integer.MIN_VALUE;
for (XMLNode child : node.getChildren()) {
switch (child.getTag()) {
case TAG_NODE_NAME:
if (nodeName != null) {
- throw new ParsingException("Duplicant NodeName: " + child.getText());
+ throw new ParsingException("Duplicate NodeName: " + child.getText());
}
nodeName = child.getText();
if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
@@ -348,13 +421,22 @@
}
break;
case TAG_NODE:
- // Only one PerProviderSubscription instance is expected and allowed.
- if (config != null) {
- throw new ParsingException("Multiple PPS instance");
+ // A node can be either an UpdateIdentifier node or a PerProviderSubscription
+ // instance node. Flatten out the XML tree first by converting it to a PPS
+ // tree to reduce the complexity of the parsing code.
+ PPSNode ppsNodeRoot = buildPpsNode(child);
+ if (TextUtils.equals(ppsNodeRoot.getName(), NODE_UPDATE_IDENTIFIER)) {
+ if (updateIdentifier != Integer.MIN_VALUE) {
+ throw new ParsingException("Multiple node for UpdateIdentifier");
+ }
+ updateIdentifier = parseInteger(getPpsNodeValue(ppsNodeRoot));
+ } else {
+ // Only one PerProviderSubscription instance is expected and allowed.
+ if (config != null) {
+ throw new ParsingException("Multiple PPS instance");
+ }
+ config = parsePpsInstance(ppsNodeRoot);
}
- // Convert the XML tree to a PPS tree.
- PPSNode ppsInstanceRoot = buildPpsNode(child);
- config = parsePpsInstance(ppsInstanceRoot);
break;
case TAG_RT_PROPERTIES:
// Parse and verify URN stored in the RT (Run Time) Properties.
@@ -367,6 +449,9 @@
throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
}
}
+ if (config != null && updateIdentifier != Integer.MIN_VALUE) {
+ config.updateIdentifier = updateIdentifier;
+ }
return config;
}
@@ -526,6 +611,21 @@
case NODE_CREDENTIAL:
config.credential = parseCredential(child);
break;
+ case NODE_POLICY:
+ config.policy = parsePolicy(child);
+ break;
+ case NODE_AAA_SERVER_TRUST_ROOT:
+ config.trustRootCertList = parseAAAServerTrustRootList(child);
+ break;
+ case NODE_SUBSCRIPTION_UPDATE:
+ config.subscriptionUpdate = parseUpdateParameter(child);
+ break;
+ case NODE_SUBSCRIPTION_PARAMETER:
+ parseSubscriptionParameter(child, config);
+ break;
+ case NODE_CREDENTIAL_PRIORITY:
+ config.credentialPriority = parseInteger(getPpsNodeValue(child));
+ break;
default:
throw new ParsingException("Unknown node: " + child.getName());
}
@@ -558,6 +658,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());
}
@@ -577,16 +691,194 @@
String[] oiStrArray = oiStr.split(",");
long[] oiArray = new long[oiStrArray.length];
for (int i = 0; i < oiStrArray.length; i++) {
- try {
- oiArray[i] = Long.parseLong(oiStrArray[i], 16);
- } catch (NumberFormatException e) {
- throw new ParsingException("Invalid OI: " + oiStrArray[i]);
- }
+ oiArray[i] = parseLong(oiStrArray[i], 16);
}
return oiArray;
}
/**
+ * 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:
+ hessid = parseLong(getPpsNodeValue(child), 16);
+ 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 +893,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 +908,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 +946,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 +989,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());
}
@@ -746,6 +1066,503 @@
}
/**
+ * Parse configurations under PerProviderSubscription/Policy subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Policy subtree
+ * @return {@link Policy}
+ * @throws ParsingException
+ */
+ private static Policy parsePolicy(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for Policy");
+ }
+
+ Policy policy = new Policy();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_PREFERRED_ROAMING_PARTNER_LIST:
+ policy.preferredRoamingPartnerList = parsePreferredRoamingPartnerList(child);
+ break;
+ case NODE_MIN_BACKHAUL_THRESHOLD:
+ parseMinBackhaulThreshold(child, policy);
+ break;
+ case NODE_POLICY_UPDATE:
+ policy.policyUpdate = parseUpdateParameter(child);
+ break;
+ case NODE_SP_EXCLUSION_LIST:
+ policy.excludedSsidList = parseSpExclusionList(child);
+ break;
+ case NODE_REQUIRED_PROTO_PORT_TUPLE:
+ policy.requiredProtoPortMap = parseRequiredProtoPortTuple(child);
+ break;
+ case NODE_MAXIMUM_BSS_LOAD_VALUE:
+ policy.maximumBssLoadValue = parseInteger(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under Policy: " + child.getName());
+ }
+ }
+ return policy;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/PreferredRoamingPartnerList subtree
+ * @return List of {@link Policy#RoamingPartner}
+ * @throws ParsingException
+ */
+ private static List<Policy.RoamingPartner> parsePreferredRoamingPartnerList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for PreferredRoamingPartnerList");
+ }
+ List<Policy.RoamingPartner> partnerList = new ArrayList<>();
+ for (PPSNode child : node.getChildren()) {
+ partnerList.add(parsePreferredRoamingPartner(child));
+ }
+ return partnerList;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+>
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+> subtree
+ * @return {@link Policy#RoamingPartner}
+ * @throws ParsingException
+ */
+ private static Policy.RoamingPartner parsePreferredRoamingPartner(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for PreferredRoamingPartner "
+ + "instance");
+ }
+
+ Policy.RoamingPartner roamingPartner = new Policy.RoamingPartner();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_FQDN_MATCH:
+ // FQDN_Match field is in the format of "[FQDN],[MatchInfo]", where [MatchInfo]
+ // is either "exactMatch" for exact match of FQDN or "includeSubdomains" for
+ // matching all FQDNs with the same sub-domain.
+ String fqdnMatch = getPpsNodeValue(child);
+ String[] fqdnMatchArray = fqdnMatch.split(",");
+ if (fqdnMatchArray.length != 2) {
+ throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+ }
+ roamingPartner.fqdn = fqdnMatchArray[0];
+ if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) {
+ roamingPartner.fqdnExactMatch = true;
+ } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) {
+ roamingPartner.fqdnExactMatch = false;
+ } else {
+ throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+ }
+ break;
+ case NODE_PRIORITY:
+ roamingPartner.priority = parseInteger(getPpsNodeValue(child));
+ break;
+ case NODE_COUNTRY:
+ roamingPartner.countries = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under PreferredRoamingPartnerList "
+ + "instance " + child.getName());
+ }
+ }
+ return roamingPartner;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+ * into the given policy.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+ * @param policy The policy to store the MinBackhualThreshold configuration
+ * @throws ParsingException
+ */
+ private static void parseMinBackhaulThreshold(PPSNode node, Policy policy)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for MinBackhaulThreshold");
+ }
+ for (PPSNode child : node.getChildren()) {
+ parseMinBackhaulThresholdInstance(child, policy);
+ }
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+ * into the given policy.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+ * @param policy The policy to store the MinBackhaulThreshold configuration
+ * @throws ParsingException
+ */
+ private static void parseMinBackhaulThresholdInstance(PPSNode node, Policy policy)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for MinBackhaulThreshold instance");
+ }
+ String networkType = null;
+ long downlinkBandwidth = Long.MIN_VALUE;
+ long uplinkBandwidth = Long.MIN_VALUE;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_NETWORK_TYPE:
+ networkType = getPpsNodeValue(child);
+ break;
+ case NODE_DOWNLINK_BANDWIDTH:
+ downlinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_UPLINK_BANDWIDTH:
+ uplinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+ break;
+ default:
+ throw new ParsingException("Unknown node under MinBackhaulThreshold instance "
+ + child.getName());
+ }
+ }
+ if (networkType == null) {
+ throw new ParsingException("Missing NetworkType field");
+ }
+
+ if (TextUtils.equals(networkType, "home")) {
+ policy.minHomeDownlinkBandwidth = downlinkBandwidth;
+ policy.minHomeUplinkBandwidth = uplinkBandwidth;
+ } else if (TextUtils.equals(networkType, "roaming")) {
+ policy.minRoamingDownlinkBandwidth = downlinkBandwidth;
+ policy.minRoamingUplinkBandwidth = uplinkBandwidth;
+ } else {
+ throw new ParsingException("Invalid network type: " + networkType);
+ }
+ }
+
+ /**
+ * Parse update parameters. This contained configurations from either
+ * PerProviderSubscription/Policy/PolicyUpdate or PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Policy/PolicyUpdate
+ * or PerProviderSubscription/SubscriptionUpdate subtree
+ * @return {@link UpdateParameter}
+ * @throws ParsingException
+ */
+ private static UpdateParameter parseUpdateParameter(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for Update Parameters");
+ }
+
+ UpdateParameter updateParam = new UpdateParameter();
+ for (PPSNode child : node.getChildren()) {
+ switch(child.getName()) {
+ case NODE_UPDATE_INTERVAL:
+ updateParam.updateIntervalInMinutes = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_UPDATE_METHOD:
+ updateParam.updateMethod = getPpsNodeValue(child);
+ break;
+ case NODE_RESTRICTION:
+ updateParam.restriction = getPpsNodeValue(child);
+ break;
+ case NODE_URI:
+ updateParam.serverUri = getPpsNodeValue(child);
+ break;
+ case NODE_USERNAME_PASSWORD:
+ Pair<String, String> usernamePassword = parseUpdateUserCredential(child);
+ updateParam.username = usernamePassword.first;
+ updateParam.base64EncodedPassword = usernamePassword.second;
+ break;
+ case NODE_TRUST_ROOT:
+ Pair<String, byte[]> trustRoot = parseTrustRoot(child);
+ updateParam.trustRootCertUrl = trustRoot.first;
+ updateParam.trustRootCertSha256Fingerprint = trustRoot.second;
+ break;
+ case NODE_OTHER:
+ Log.d(TAG, "Ignore unsupported paramter: " + child.getName());
+ break;
+ default:
+ throw new ParsingException("Unknown node under Update Parameters: "
+ + child.getName());
+ }
+ }
+ return updateParam;
+ }
+
+ /**
+ * Parse username and password parameters associated with policy or subscription update.
+ * This contained configurations under either
+ * PerProviderSubscription/Policy/PolicyUpdate/UsernamePassword or
+ * PerProviderSubscription/SubscriptionUpdate/UsernamePassword subtree.
+ *
+ * @param node PPSNode representing the root of the UsernamePassword subtree
+ * @return Pair of username and password
+ * @throws ParsingException
+ */
+ private static Pair<String, String> parseUpdateUserCredential(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for UsernamePassword");
+ }
+
+ String username = null;
+ String password = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_USERNAME:
+ username = getPpsNodeValue(child);
+ break;
+ case NODE_PASSWORD:
+ password = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under UsernamePassword: "
+ + child.getName());
+ }
+ }
+ return Pair.create(username, password);
+ }
+
+ /**
+ * Parse the trust root parameters associated with policy update, subscription update, or AAA
+ * server trust root.
+ *
+ * This contained configurations under either
+ * PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or
+ * PerProviderSubscription/SubscriptionUpdate/TrustRoot or
+ * PerProviderSubscription/AAAServerTrustRoot/<X+> subtree.
+ *
+ * @param node PPSNode representing the root of the TrustRoot subtree
+ * @return Pair of Certificate URL and fingerprint
+ * @throws ParsingException
+ */
+ private static Pair<String, byte[]> parseTrustRoot(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for TrustRoot");
+ }
+
+ String certUrl = null;
+ byte[] certFingerprint = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CERT_URL:
+ certUrl = getPpsNodeValue(child);
+ break;
+ case NODE_CERT_SHA256_FINGERPRINT:
+ certFingerprint = parseHexString(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under TrustRoot: "
+ + child.getName());
+ }
+ }
+ return Pair.create(certUrl, certFingerprint);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/SPExclusionList subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/SPExclusionList subtree
+ * @return Array of excluded SSIDs
+ * @throws ParsingException
+ */
+ private static String[] parseSpExclusionList(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SPExclusionList");
+ }
+ List<String> ssidList = new ArrayList<>();
+ for (PPSNode child : node.getChildren()) {
+ ssidList.add(parseSpExclusionInstance(child));
+ }
+ return ssidList.toArray(new String[ssidList.size()]);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/SPExclusionList/<X+> subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/SPExclusionList/<X+> subtree
+ * @return String
+ * @throws ParsingException
+ */
+ private static String parseSpExclusionInstance(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SPExclusion instance");
+ }
+ String ssid = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_SSID:
+ ssid = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under SPExclusion instance");
+ }
+ }
+ return ssid;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/RequiredProtoPortTuple subtree
+ * @return Map of IP Protocol to Port Number tuples
+ * @throws ParsingException
+ */
+ private static Map<Integer, String> parseRequiredProtoPortTuple(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple");
+ }
+ Map<Integer, String> protoPortTupleMap = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<Integer, String> protoPortTuple = parseProtoPortTuple(child);
+ protoPortTupleMap.put(protoPortTuple.first, protoPortTuple.second);
+ }
+ return protoPortTupleMap;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+>
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+> subtree
+ * @return Pair of IP Protocol to Port Number tuple
+ * @throws ParsingException
+ */
+ private static Pair<Integer, String> parseProtoPortTuple(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple "
+ + "instance");
+ }
+ int proto = Integer.MIN_VALUE;
+ String ports = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_IP_PROTOCOL:
+ proto = parseInteger(getPpsNodeValue(child));
+ break;
+ case NODE_PORT_NUMBER:
+ ports = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under RequiredProtoPortTuple instance"
+ + child.getName());
+ }
+ }
+ if (proto == Integer.MIN_VALUE) {
+ throw new ParsingException("Missing IPProtocol field");
+ }
+ if (ports == null) {
+ throw new ParsingException("Missing PortNumber field");
+ }
+ return Pair.create(proto, ports);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/AAAServerTrustRoot subtree.
+ *
+ * @param node PPSNode representing the root of PerProviderSubscription/AAAServerTrustRoot
+ * subtree
+ * @return Map of certificate URL with the corresponding certificate fingerprint
+ * @throws ParsingException
+ */
+ private static Map<String, byte[]> parseAAAServerTrustRootList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for AAAServerTrustRoot");
+ }
+ Map<String, byte[]> certList = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<String, byte[]> certTuple = parseTrustRoot(child);
+ certList.put(certTuple.first, certTuple.second);
+ }
+ return certList;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/SubscriptionParameter subtree.
+ *
+ * @param node PPSNode representing the root of PerProviderSubscription/SubscriptionParameter
+ * subtree
+ * @param config Instance of {@link PasspointConfiguration}
+ * @throws ParsingException
+ */
+ private static void parseSubscriptionParameter(PPSNode node, PasspointConfiguration config)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SubscriptionParameter");
+ }
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CREATION_DATE:
+ config.subscriptionCreationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_EXPIRATION_DATE:
+ config.subscriptionExpirationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_TYPE_OF_SUBSCRIPTION:
+ config.subscriptionType = getPpsNodeValue(child);
+ break;
+ case NODE_USAGE_LIMITS:
+ parseUsageLimits(child, config);
+ break;
+ default:
+ throw new ParsingException("Unknown node under SubscriptionParameter"
+ + child.getName());
+ }
+ }
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/SubscriptionParameter/UsageLimits
+ * subtree.
+ *
+ * @param node PPSNode representing the root of
+ * PerProviderSubscription/SubscriptionParameter/UsageLimits subtree
+ * @param config Instance of {@link PasspointConfiguration}
+ * @throws ParsingException
+ */
+ private static void parseUsageLimits(PPSNode node, PasspointConfiguration config)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for UsageLimits");
+ }
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_DATA_LIMIT:
+ config.usageLimitDataLimit = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_START_DATE:
+ config.usageLimitStartTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_TIME_LIMIT:
+ config.usageLimitTimeLimitInMinutes = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_USAGE_TIME_PERIOD:
+ config.usageLimitUsageTimePeriodInMinutes =
+ parseLong(getPpsNodeValue(child), 10);
+ break;
+ default:
+ throw new ParsingException("Unknown node under UsageLimits"
+ + child.getName());
+ }
+ }
+ }
+
+ /**
* Convert a hex string to a byte array.
*
* @param str String containing hex values
@@ -770,6 +1587,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 +1616,34 @@
throw new ParsingException("Invalid integer value: " + value);
}
}
+
+ /**
+ * Parse a string representing a long integer.
+ *
+ * @param value String of long integer value
+ * @return long
+ * @throws ParsingException
+ */
+ private static long parseLong(String value, int radix) throws ParsingException {
+ try {
+ return Long.parseLong(value, radix);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid long 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/java/android/net/wifi/hotspot2/pps/Policy.aidl b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
new file mode 100644
index 0000000..e923f1f
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.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.net.wifi.hotspot2.pps;
+
+parcelable Policy;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
new file mode 100644
index 0000000..b2583d3
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
@@ -0,0 +1,452 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class representing Policy subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * The Policy specifies additional criteria for Passpoint network selections, such as preferred
+ * roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
+ * updating the policy.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class Policy implements Parcelable {
+ private static final String TAG = "Policy";
+
+ /**
+ * Default priority for preferred roaming partner.
+ */
+ public static final int PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY = 128;
+
+ /**
+ * Maximum number of SSIDs in the exclusion list.
+ */
+ private static final int MAX_EXCLUSION_SSIDS = 128;
+
+ /**
+ * Maximum byte for SSID.
+ */
+ private static final int MAX_SSID_BYTES = 32;
+
+ /**
+ * Maximum bytes for port string in {@link #requiredProtoPortMap}.
+ */
+ private static final int MAX_PORT_STRING_BYTES = 64;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
+ /**
+ * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+ * selecting a network from home providers.
+ *
+ * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+ * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+ *
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long minHomeDownlinkBandwidth = Long.MIN_VALUE;
+ public long minHomeUplinkBandwidth = Long.MIN_VALUE;
+
+ /**
+ * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+ * selecting a network from roaming providers.
+ *
+ * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+ * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+ *
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long minRoamingDownlinkBandwidth = Long.MIN_VALUE;
+ public long minRoamingUplinkBandwidth = Long.MIN_VALUE;
+
+ /**
+ * List of SSIDs that are not preferred by the Home SP.
+ */
+ public String[] excludedSsidList = null;
+
+ /**
+ * List of IP protocol and port number required by one or more operator supported application.
+ * The port string contained one or more port numbers delimited by ",".
+ */
+ public Map<Integer, String> requiredProtoPortMap = null;
+
+ /**
+ * This specifies the maximum acceptable BSS load policy. This is used to prevent device
+ * from joining an AP whose channel is overly congested with traffic.
+ * Using Integer.MIN_VALUE to indicate unset value.
+ */
+ public int maximumBssLoadValue = Integer.MIN_VALUE;
+
+ /**
+ * Policy associated with a roaming provider. This specifies a priority associated
+ * with a roaming provider for given list of countries.
+ *
+ * Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
+ */
+ public static final class RoamingPartner implements Parcelable {
+ /**
+ * FQDN of the roaming partner.
+ */
+ public String fqdn = null;
+
+ /**
+ * Flag indicating the exact match of FQDN is required for FQDN matching.
+ *
+ * When this flag is set to false, sub-domain matching is used. For example, when
+ * {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
+ */
+ public boolean fqdnExactMatch = false;
+
+ /**
+ * Priority associated with this roaming partner policy.
+ */
+ public int priority = PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY;
+
+ /**
+ * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
+ * character country strings or the country-independent value, "*".
+ */
+ public String countries = null;
+
+ public RoamingPartner() {}
+
+ public RoamingPartner(RoamingPartner source) {
+ if (source != null) {
+ fqdn = source.fqdn;
+ fqdnExactMatch = source.fqdnExactMatch;
+ priority = source.priority;
+ countries = source.countries;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(fqdn);
+ dest.writeInt(fqdnExactMatch ? 1 : 0);
+ dest.writeInt(priority);
+ dest.writeString(countries);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof RoamingPartner)) {
+ return false;
+ }
+
+ RoamingPartner that = (RoamingPartner) thatObject;
+ return TextUtils.equals(fqdn, that.fqdn)
+ && fqdnExactMatch == that.fqdnExactMatch
+ && priority == that.priority
+ && TextUtils.equals(countries, that.countries);
+ }
+
+ /**
+ * Validate RoamingParnter data.
+ *
+ * @return true on success
+ */
+ public boolean validate() {
+ if (TextUtils.isEmpty(fqdn)) {
+ Log.d(TAG, "Missing FQDN");
+ return false;
+ }
+ if (TextUtils.isEmpty(countries)) {
+ Log.d(TAG, "Missing countries");
+ return false;
+ }
+ return true;
+ }
+
+ public static final Creator<RoamingPartner> CREATOR =
+ new Creator<RoamingPartner>() {
+ @Override
+ public RoamingPartner createFromParcel(Parcel in) {
+ RoamingPartner roamingPartner = new RoamingPartner();
+ roamingPartner.fqdn = in.readString();
+ roamingPartner.fqdnExactMatch = in.readInt() != 0;
+ roamingPartner.priority = in.readInt();
+ roamingPartner.countries = in.readString();
+ return roamingPartner;
+ }
+
+ @Override
+ public RoamingPartner[] newArray(int size) {
+ return new RoamingPartner[size];
+ }
+ };
+ }
+ public List<RoamingPartner> preferredRoamingPartnerList = null;
+
+ /**
+ * Meta data used for policy update.
+ */
+ public UpdateParameter policyUpdate = null;
+
+ /**
+ * Constructor for creating Policy with default values.
+ */
+ public Policy() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public Policy(Policy source) {
+ if (source == null) {
+ return;
+ }
+ minHomeDownlinkBandwidth = source.minHomeDownlinkBandwidth;
+ minHomeUplinkBandwidth = source.minHomeUplinkBandwidth;
+ minRoamingDownlinkBandwidth = source.minRoamingDownlinkBandwidth;
+ minRoamingUplinkBandwidth = source.minRoamingUplinkBandwidth;
+ maximumBssLoadValue = source.maximumBssLoadValue;
+ if (source.excludedSsidList != null) {
+ excludedSsidList = Arrays.copyOf(source.excludedSsidList,
+ source.excludedSsidList.length);
+ }
+ if (source.requiredProtoPortMap != null) {
+ requiredProtoPortMap = Collections.unmodifiableMap(source.requiredProtoPortMap);
+ }
+ if (source.preferredRoamingPartnerList != null) {
+ preferredRoamingPartnerList = Collections.unmodifiableList(
+ source.preferredRoamingPartnerList);
+ }
+ if (source.policyUpdate != null) {
+ policyUpdate = new UpdateParameter(source.policyUpdate);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(minHomeDownlinkBandwidth);
+ dest.writeLong(minHomeUplinkBandwidth);
+ dest.writeLong(minRoamingDownlinkBandwidth);
+ dest.writeLong(minRoamingUplinkBandwidth);
+ dest.writeStringArray(excludedSsidList);
+ writeProtoPortMap(dest, requiredProtoPortMap);
+ dest.writeInt(maximumBssLoadValue);
+ writeRoamingPartnerList(dest, flags, preferredRoamingPartnerList);
+ dest.writeParcelable(policyUpdate, flags);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof Policy)) {
+ return false;
+ }
+ Policy that = (Policy) thatObject;
+
+ return minHomeDownlinkBandwidth == that.minHomeDownlinkBandwidth
+ && minHomeUplinkBandwidth == that.minHomeUplinkBandwidth
+ && minRoamingDownlinkBandwidth == that.minRoamingDownlinkBandwidth
+ && minRoamingUplinkBandwidth == that.minRoamingUplinkBandwidth
+ && Arrays.equals(excludedSsidList, that.excludedSsidList)
+ && (requiredProtoPortMap == null) ? that.requiredProtoPortMap == null
+ : requiredProtoPortMap.equals(that.requiredProtoPortMap)
+ && maximumBssLoadValue == that.maximumBssLoadValue
+ && (preferredRoamingPartnerList == null) ? that.preferredRoamingPartnerList == null
+ : preferredRoamingPartnerList.equals(that.preferredRoamingPartnerList)
+ && (policyUpdate == null) ? that.policyUpdate == null
+ : policyUpdate.equals(that.policyUpdate);
+ }
+
+ /**
+ * Validate Policy data.
+ *
+ * @return true on success
+ */
+ public boolean validate() {
+ if (policyUpdate == null) {
+ Log.d(TAG, "PolicyUpdate not specified");
+ return false;
+ }
+ if (!policyUpdate.validate()) {
+ return false;
+ }
+
+ // Validate SSID exclusion list.
+ if (excludedSsidList != null) {
+ if (excludedSsidList.length > MAX_EXCLUSION_SSIDS) {
+ Log.d(TAG, "SSID exclusion list size exceeded the max: "
+ + excludedSsidList.length);
+ return false;
+ }
+ for (String ssid : excludedSsidList) {
+ if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+ Log.d(TAG, "Invalid SSID: " + ssid);
+ return false;
+ }
+ }
+ }
+ // Validate required protocol to port map.
+ if (requiredProtoPortMap != null) {
+ for (Map.Entry<Integer, String> entry : requiredProtoPortMap.entrySet()) {
+ String portNumber = entry.getValue();
+ if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
+ Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
+ return false;
+ }
+ }
+ }
+ // Validate preferred roaming partner list.
+ if (preferredRoamingPartnerList != null) {
+ for (RoamingPartner partner : preferredRoamingPartnerList) {
+ if (!partner.validate()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static final Creator<Policy> CREATOR =
+ new Creator<Policy>() {
+ @Override
+ public Policy createFromParcel(Parcel in) {
+ Policy policy = new Policy();
+ policy.minHomeDownlinkBandwidth = in.readLong();
+ policy.minHomeUplinkBandwidth = in.readLong();
+ policy.minRoamingDownlinkBandwidth = in.readLong();
+ policy.minRoamingUplinkBandwidth = in.readLong();
+ policy.excludedSsidList = in.createStringArray();
+ policy.requiredProtoPortMap = readProtoPortMap(in);
+ policy.maximumBssLoadValue = in.readInt();
+ policy.preferredRoamingPartnerList = readRoamingPartnerList(in);
+ policy.policyUpdate = in.readParcelable(null);
+ return policy;
+ }
+
+ @Override
+ public Policy[] newArray(int size) {
+ return new Policy[size];
+ }
+
+ /**
+ * Helper function for reading IP Protocol to Port Number map from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return Map of IP protocol to port number
+ */
+ private Map<Integer, String> readProtoPortMap(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<Integer, String> protoPortMap = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ int key = in.readInt();
+ String value = in.readString();
+ protoPortMap.put(key, value);
+ }
+ return protoPortMap;
+ }
+
+ /**
+ * Helper function for reading roaming partner list from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return List of roaming partners
+ */
+ private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ List<RoamingPartner> partnerList = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ partnerList.add(in.readParcelable(null));
+ }
+ return partnerList;
+ }
+
+ };
+
+ /**
+ * Helper function for writing IP Protocol to Port Number map to a Parcel.
+ *
+ * @param dest The Parcel to write to
+ * @param protoPortMap The map to write
+ */
+ private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
+ if (protoPortMap == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(protoPortMap.size());
+ for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
+ dest.writeInt(entry.getKey());
+ dest.writeString(entry.getValue());
+ }
+ }
+
+ /**
+ * Helper function for writing roaming partner list to a Parcel.
+ *
+ * @param dest The Parcel to write to
+ * @param flags The flag about how the object should be written
+ * @param partnerList The partner list to write
+ */
+ private static void writeRoamingPartnerList(Parcel dest, int flags,
+ List<RoamingPartner> partnerList) {
+ if (partnerList == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(partnerList.size());
+ for (RoamingPartner partner : partnerList) {
+ dest.writeParcelable(partner, flags);
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
new file mode 100644
index 0000000..701db47
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.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.net.wifi.hotspot2.pps;
+
+parcelable UpdateParameter;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
new file mode 100644
index 0000000..a390df7
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
@@ -0,0 +1,303 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Class representing configuration parameters for subscription or policy update in
+ * PerProviderSubscription (PPS) Management Object (MO) tree. This is used by both
+ * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class UpdateParameter implements Parcelable {
+ private static final String TAG = "UpdateParameter";
+
+ /**
+ * Value indicating policy update is not applicable. Thus, never check with policy server
+ * for updates.
+ */
+ public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL;
+
+ /**
+ * Valid string for UpdateMethod.
+ */
+ public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+ public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+
+ /**
+ * Valid string for Restriction.
+ */
+ public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+ public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+ public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+
+ /**
+ * Maximum bytes for URI string.
+ */
+ private static final int MAX_URI_BYTES = 1023;
+
+ /**
+ * Maximum bytes for URI string.
+ */
+ private static final int MAX_URL_BYTES = 1023;
+
+ /**
+ * Maximum bytes for username.
+ */
+ private static final int MAX_USERNAME_BYTES = 63;
+
+ /**
+ * Maximum bytes for password.
+ */
+ private static final int MAX_PASSWORD_BYTES = 255;
+
+ /**
+ * Number of bytes for certificate SHA-256 fingerprint byte array.
+ */
+ private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+ /**
+ * This specifies how often the mobile device shall check with policy server for updates.
+ *
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long updateIntervalInMinutes = Long.MIN_VALUE;
+
+ /**
+ * The method used to update the policy. Permitted values are "OMA-DM-ClientInitiated"
+ * and "SPP-ClientInitiated".
+ */
+ public String updateMethod = null;
+
+ /**
+ * This specifies the hotspots at which the subscription update is permitted. Permitted
+ * values are "HomeSP", "RoamingPartner", or "Unrestricted";
+ */
+ public String restriction = null;
+
+ /**
+ * The URI of the update server.
+ */
+ public String serverUri = null;
+
+ /**
+ * Username used to authenticate with the policy server.
+ */
+ public String username = null;
+
+ /**
+ * Base64 encoded password used to authenticate with the policy server.
+ */
+ public String base64EncodedPassword = null;
+
+ /**
+ * HTTPS URL for retrieving certificate for trust root. The trust root is used to validate
+ * policy server's identity.
+ */
+ public String trustRootCertUrl = null;
+
+ /**
+ * SHA-256 fingerprint of the certificate located at {@link #trustRootCertUrl}
+ */
+ public byte[] trustRootCertSha256Fingerprint = null;
+
+ /**
+ * Constructor for creating Policy with default values.
+ */
+ public UpdateParameter() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public UpdateParameter(UpdateParameter source) {
+ if (source == null) {
+ return;
+ }
+ updateIntervalInMinutes = source.updateIntervalInMinutes;
+ updateMethod = source.updateMethod;
+ restriction = source.restriction;
+ serverUri = source.serverUri;
+ username = source.username;
+ base64EncodedPassword = source.base64EncodedPassword;
+ trustRootCertUrl = source.trustRootCertUrl;
+ if (source.trustRootCertSha256Fingerprint != null) {
+ trustRootCertSha256Fingerprint = Arrays.copyOf(source.trustRootCertSha256Fingerprint,
+ source.trustRootCertSha256Fingerprint.length);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(updateIntervalInMinutes);
+ dest.writeString(updateMethod);
+ dest.writeString(restriction);
+ dest.writeString(serverUri);
+ dest.writeString(username);
+ dest.writeString(base64EncodedPassword);
+ dest.writeString(trustRootCertUrl);
+ dest.writeByteArray(trustRootCertSha256Fingerprint);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof UpdateParameter)) {
+ return false;
+ }
+ UpdateParameter that = (UpdateParameter) thatObject;
+
+ return updateIntervalInMinutes == that.updateIntervalInMinutes
+ && TextUtils.equals(updateMethod, that.updateMethod)
+ && TextUtils.equals(restriction, that.restriction)
+ && TextUtils.equals(serverUri, that.serverUri)
+ && TextUtils.equals(username, that.username)
+ && TextUtils.equals(base64EncodedPassword, that.base64EncodedPassword)
+ && TextUtils.equals(trustRootCertUrl, that.trustRootCertUrl)
+ && Arrays.equals(trustRootCertSha256Fingerprint,
+ that.trustRootCertSha256Fingerprint);
+ }
+
+ /**
+ * Validate UpdateParameter data.
+ *
+ * @return true on success
+ */
+ public boolean validate() {
+ if (updateIntervalInMinutes == Long.MIN_VALUE) {
+ Log.d(TAG, "Update interval not specified");
+ return false;
+ }
+ // Update not applicable.
+ if (updateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
+ return true;
+ }
+
+ if (!TextUtils.equals(updateMethod, UPDATE_METHOD_OMADM)
+ && !TextUtils.equals(updateMethod, UPDATE_METHOD_SSP)) {
+ Log.d(TAG, "Unknown update method: " + updateMethod);
+ return false;
+ }
+
+ if (!TextUtils.equals(restriction, UPDATE_RESTRICTION_HOMESP)
+ && !TextUtils.equals(restriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
+ && !TextUtils.equals(restriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
+ Log.d(TAG, "Unknown restriction: " + restriction);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(serverUri)) {
+ Log.d(TAG, "Missing update server URI");
+ return false;
+ }
+ if (serverUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
+ Log.d(TAG, "URI bytes exceeded the max: "
+ + serverUri.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(username)) {
+ Log.d(TAG, "Missing username");
+ return false;
+ }
+ if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+ Log.d(TAG, "Username bytes exceeded the max: "
+ + username.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(base64EncodedPassword)) {
+ Log.d(TAG, "Missing username");
+ return false;
+ }
+ if (base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+ Log.d(TAG, "Password bytes exceeded the max: "
+ + base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+ try {
+ Base64.decode(base64EncodedPassword, Base64.DEFAULT);
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Invalid encoding for password: " + base64EncodedPassword);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(trustRootCertUrl)) {
+ Log.d(TAG, "Missing trust root certificate URL");
+ return false;
+ }
+ if (trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+ Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
+ + trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (trustRootCertSha256Fingerprint == null) {
+ Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
+ return false;
+ }
+ if (trustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
+ Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+ + trustRootCertSha256Fingerprint.length);
+ return false;
+ }
+ return true;
+ }
+
+ public static final Creator<UpdateParameter> CREATOR =
+ new Creator<UpdateParameter>() {
+ @Override
+ public UpdateParameter createFromParcel(Parcel in) {
+ UpdateParameter updateParam = new UpdateParameter();
+ updateParam.updateIntervalInMinutes = in.readLong();
+ updateParam.updateMethod = in.readString();
+ updateParam.restriction = in.readString();
+ updateParam.serverUri = in.readString();
+ updateParam.username = in.readString();
+ updateParam.base64EncodedPassword = in.readString();
+ updateParam.trustRootCertUrl = in.readString();
+ updateParam.trustRootCertSha256Fingerprint = in.createByteArray();
+ return updateParam;
+ }
+
+ @Override
+ public UpdateParameter[] newArray(int size) {
+ return new UpdateParameter[size];
+ }
+ };
+}
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
index 8c1eb08..995963d 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
@@ -42,7 +42,7 @@
a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
-VTJSbWx1WjJWeVVISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+VTJSbWx1WjJWeWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
index 6d86dd5..3ddd09f 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
@@ -35,7 +35,7 @@
aWZpY2F0ZTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9kZT4KICAgICAgICAgICAgPE5vZGVOYW1l
PkNlcnRpZmljYXRlVHlwZTwvTm9kZU5hbWU+CiAgICAgICAgICAgIDxWYWx1ZT54NTA5djM8L1Zh
bHVlPgogICAgICAgICAgPC9Ob2RlPgogICAgICAgICAgPE5vZGU+CiAgICAgICAgICAgIDxOb2Rl
-TmFtZT5DZXJ0U0hBMjU2RmluZ2VyUHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
+TmFtZT5DZXJ0U0hBMjU2RmluZ2VycHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
MWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYx
ZjFmMWYxZjwvVmFsdWU+CiAgICAgICAgICA8L05vZGU+CiAgICAgICAgPC9Ob2RlPgogICAgICAg
IDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPlNJTTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9k
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 53d38ad..7f2d95d 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -8,6 +8,10 @@
</Type>
</RTProperties>
<Node>
+ <NodeName>UpdateIdentifier</NodeName>
+ <Value>12</Value>
+ </Node>
+ <Node>
<NodeName>i001</NodeName>
<Node>
<NodeName>HomeSP</NodeName>
@@ -23,14 +27,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 +117,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>
@@ -59,7 +147,7 @@
<Value>x509v3</Value>
</Node>
<Node>
- <NodeName>CertSHA256FingerPrint</NodeName>
+ <NodeName>CertSHA256Fingerprint</NodeName>
<Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
</Node>
</Node>
@@ -75,6 +163,237 @@
</Node>
</Node>
</Node>
+ <Node>
+ <NodeName>Policy</NodeName>
+ <Node>
+ <NodeName>PreferredRoamingPartnerList</NodeName>
+ <Node>
+ <NodeName>p001</NodeName>
+ <Node>
+ <NodeName>FQDN_Match</NodeName>
+ <Value>test1.fqdn.com,exactMatch</Value>
+ </Node>
+ <Node>
+ <NodeName>Priority</NodeName>
+ <Value>127</Value>
+ </Node>
+ <Node>
+ <NodeName>Country</NodeName>
+ <Value>us,fr</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>p002</NodeName>
+ <Node>
+ <NodeName>FQDN_Match</NodeName>
+ <Value>test2.fqdn.com,includeSubdomains</Value>
+ </Node>
+ <Node>
+ <NodeName>Priority</NodeName>
+ <Value>200</Value>
+ </Node>
+ <Node>
+ <NodeName>Country</NodeName>
+ <Value>*</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>MinBackhaulThreshold</NodeName>
+ <Node>
+ <NodeName>m001</NodeName>
+ <Node>
+ <NodeName>NetworkType</NodeName>
+ <Value>home</Value>
+ </Node>
+ <Node>
+ <NodeName>DLBandwidth</NodeName>
+ <Value>23412</Value>
+ </Node>
+ <Node>
+ <NodeName>ULBandwidth</NodeName>
+ <Value>9823</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>m002</NodeName>
+ <Node>
+ <NodeName>NetworkType</NodeName>
+ <Value>roaming</Value>
+ </Node>
+ <Node>
+ <NodeName>DLBandwidth</NodeName>
+ <Value>9271</Value>
+ </Node>
+ <Node>
+ <NodeName>ULBandwidth</NodeName>
+ <Value>2315</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>PolicyUpdate</NodeName>
+ <Node>
+ <NodeName>UpdateInterval</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UpdateMethod</NodeName>
+ <Value>OMA-DM-ClientInitiated</Value>
+ </Node>
+ <Node>
+ <NodeName>Restriction</NodeName>
+ <Value>HomeSP</Value>
+ </Node>
+ <Node>
+ <NodeName>URI</NodeName>
+ <Value>policy.update.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>updateUser</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>updatePass</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>TrustRoot</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>update.cert.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SPExclusionList</NodeName>
+ <Node>
+ <NodeName>s001</NodeName>
+ <Node>
+ <NodeName>SSID</NodeName>
+ <Value>excludeSSID</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>RequiredProtoPortTuple</NodeName>
+ <Node>
+ <NodeName>r001</NodeName>
+ <Node>
+ <NodeName>IPProtocol</NodeName>
+ <Value>12</Value>
+ </Node>
+ <Node>
+ <NodeName>PortNumber</NodeName>
+ <Value>34,92,234</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>MaximumBSSLoadValue</NodeName>
+ <Value>23</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>CredentialPriority</NodeName>
+ <Value>99</Value>
+ </Node>
+ <Node>
+ <NodeName>AAAServerTrustRoot</NodeName>
+ <Node>
+ <NodeName>a001</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>server1.trust.root.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SubscriptionUpdate</NodeName>
+ <Node>
+ <NodeName>UpdateInterval</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UpdateMethod</NodeName>
+ <Value>SSP-ClientInitiated</Value>
+ </Node>
+ <Node>
+ <NodeName>Restriction</NodeName>
+ <Value>RoamingPartner</Value>
+ </Node>
+ <Node>
+ <NodeName>URI</NodeName>
+ <Value>subscription.update.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>subscriptionUser</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>subscriptionPass</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>TrustRoot</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>subscription.update.cert.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SubscriptionParameter</NodeName>
+ <Node>
+ <NodeName>CreationDate</NodeName>
+ <Value>2016-02-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>ExpirationDate</NodeName>
+ <Value>2016-03-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>TypeOfSubscription</NodeName>
+ <Value>Gold</Value>
+ </Node>
+ <Node>
+ <NodeName>UsageLimits</NodeName>
+ <Node>
+ <NodeName>DataLimit</NodeName>
+ <Value>921890</Value>
+ </Node>
+ <Node>
+ <NodeName>StartDate</NodeName>
+ <Value>2016-12-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>TimeLimit</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UsageTimePeriod</NodeName>
+ <Value>99910</Value>
+ </Node>
+ </Node>
+ </Node>
</Node>
</Node>
</MgmtTree>
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 0e503d5..5a67a7e 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -87,6 +87,42 @@
}
@Test
+ public void testSetClientCertificateChain() {
+ PrivateKey clientKey = FakeKeys.RSA_KEY1;
+ X509Certificate cert0 = FakeKeys.CLIENT_CERT;
+ X509Certificate cert1 = FakeKeys.CA_CERT1;
+ X509Certificate[] clientChain = new X509Certificate[] {cert0, cert1};
+ mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+ X509Certificate[] result = mEnterpriseConfig.getClientCertificateChain();
+ assertEquals(result.length, 2);
+ assertTrue(result[0] == cert0 && result[1] == cert1);
+ assertTrue(mEnterpriseConfig.getClientCertificate() == cert0);
+ }
+
+ private boolean isClientCertificateChainInvalid(X509Certificate[] clientChain) {
+ boolean exceptionThrown = false;
+ try {
+ PrivateKey clientKey = FakeKeys.RSA_KEY1;
+ mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ return exceptionThrown;
+ }
+
+ @Test
+ public void testSetInvalidClientCertificateChain() {
+ X509Certificate clientCert = FakeKeys.CLIENT_CERT;
+ X509Certificate caCert = FakeKeys.CA_CERT1;
+ assertTrue("Invalid client certificate",
+ isClientCertificateChainInvalid(new X509Certificate[] {caCert, caCert}));
+ assertTrue("Invalid CA certificate",
+ isClientCertificateChainInvalid(new X509Certificate[] {clientCert, clientCert}));
+ assertTrue("Both certificates invalid",
+ isClientCertificateChainInvalid(new X509Certificate[] {caCert, clientCert}));
+ }
+
+ @Test
public void testSaveSingleCaCertificateAlias() {
final String alias = "single_alias 0";
mEnterpriseConfig.setCaCertificateAliases(new String[] {alias});
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 2350d32..1eb08e0 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -22,16 +22,26 @@
import android.net.wifi.EAPConstants;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.os.Parcel;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
import org.junit.Test;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
/**
* Unit tests for {@link android.net.wifi.hotspot2.PasspointConfiguration}.
*/
@SmallTest
public class PasspointConfigurationTest {
+ private static final int MAX_URL_BYTES = 1023;
+ private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
/**
* Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
@@ -66,6 +76,93 @@
}
/**
+ * Helper function for creating a {@link Policy} for testing.
+ *
+ * @return {@link Policy}
+ */
+ private static Policy createPolicy() {
+ Policy policy = new Policy();
+ policy.minHomeDownlinkBandwidth = 123;
+ policy.minHomeUplinkBandwidth = 345;
+ policy.minRoamingDownlinkBandwidth = 567;
+ policy.minRoamingUplinkBandwidth = 789;
+ policy.maximumBssLoadValue = 12;
+ policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+ policy.requiredProtoPortMap = new HashMap<>();
+ policy.requiredProtoPortMap.put(12, "23,342,123");
+ policy.requiredProtoPortMap.put(23, "789,372,1235");
+
+ policy.preferredRoamingPartnerList = new ArrayList<>();
+ Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+ partner1.fqdn = "partner1.com";
+ partner1.fqdnExactMatch = true;
+ partner1.priority = 12;
+ partner1.countries = "us,jp";
+ Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+ partner2.fqdn = "partner2.com";
+ partner2.fqdnExactMatch = false;
+ partner2.priority = 42;
+ partner2.countries = "ca,fr";
+ policy.preferredRoamingPartnerList.add(partner1);
+ policy.preferredRoamingPartnerList.add(partner2);
+
+ policy.policyUpdate = new UpdateParameter();
+ policy.policyUpdate.updateIntervalInMinutes = 1712;
+ policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+ policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+ policy.policyUpdate.serverUri = "policy.update.com";
+ policy.policyUpdate.username = "username";
+ policy.policyUpdate.base64EncodedPassword =
+ Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+ policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+ policy.policyUpdate.trustRootCertSha256Fingerprint =
+ new byte[CERTIFICATE_FINGERPRINT_BYTES];
+
+ return policy;
+ }
+
+ private static UpdateParameter createSubscriptionUpdate() {
+ UpdateParameter subUpdate = new UpdateParameter();
+ subUpdate.updateIntervalInMinutes = 9021;
+ subUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+ subUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+ subUpdate.serverUri = "subscription.update.com";
+ subUpdate.username = "subUsername";
+ subUpdate.base64EncodedPassword =
+ Base64.encodeToString("subPassword".getBytes(), Base64.DEFAULT);
+ subUpdate.trustRootCertUrl = "subscription.trust.cert.com";
+ subUpdate.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_FINGERPRINT_BYTES];
+ return subUpdate;
+ }
+ /**
+ * Helper function for creating a {@link PasspointConfiguration} for testing.
+ *
+ * @return {@link PasspointConfiguration}
+ */
+ private static PasspointConfiguration createConfig() {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.homeSp = createHomeSp();
+ config.credential = createCredential();
+ config.policy = createPolicy();
+ config.subscriptionUpdate = createSubscriptionUpdate();
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("trustRoot.cert1.com",
+ new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ config.trustRootCertList.put("trustRoot.cert2.com",
+ new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ config.updateIdentifier = 1;
+ config.credentialPriority = 120;
+ config.subscriptionCreationTimeInMs = 231200;
+ config.subscriptionExpirationTimeInMs = 2134232;
+ config.subscriptionType = "Gold";
+ config.usageLimitUsageTimePeriodInMinutes = 3600;
+ config.usageLimitStartTimeInMs = 124214213;
+ config.usageLimitDataLimit = 14121;
+ config.usageLimitTimeLimitInMinutes = 78912;
+ return config;
+ }
+
+ /**
* Verify parcel write and read consistency for the given configuration.
*
* @param writeConfig The configuration to verify
@@ -92,39 +189,73 @@
}
/**
- * Verify parcel read/write for a configuration that contained both HomeSP and Credential.
+ * Verify parcel read/write for a configuration that contained the full configuration.
*
* @throws Exception
*/
@Test
- public void verifyParcelWithHomeSPAndCredential() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.homeSp = createHomeSp();
- config.credential = createCredential();
+ public void verifyParcelWithFullConfiguration() throws Exception {
+ verifyParcel(createConfig());
+ }
+
+ /**
+ * Verify parcel read/write for a configuration that doesn't contain HomeSP.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutHomeSP() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.homeSp = null;
verifyParcel(config);
}
/**
- * Verify parcel read/write for a configuration that contained only HomeSP.
+ * Verify parcel read/write for a configuration that doesn't contain Credential.
*
* @throws Exception
*/
@Test
- public void verifyParcelWithHomeSPOnly() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.homeSp = createHomeSp();
+ public void verifyParcelWithoutCredential() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.credential = null;
verifyParcel(config);
}
/**
- * Verify parcel read/write for a configuration that contained only Credential.
+ * Verify parcel read/write for a configuration that doesn't contain Policy.
*
* @throws Exception
*/
@Test
- public void verifyParcelWithCredentialOnly() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.credential = createCredential();
+ public void verifyParcelWithoutPolicy() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.policy = null;
+ verifyParcel(config);
+ }
+
+ /**
+ * Verify parcel read/write for a configuration that doesn't contain subscription update.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutSubscriptionUpdate() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.subscriptionUpdate = null;
+ verifyParcel(config);
+ }
+
+ /**
+ * Verify parcel read/write for a configuration that doesn't contain trust root certificate
+ * list.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutTrustRootCertList() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.trustRootCertList = null;
verifyParcel(config);
}
@@ -140,43 +271,108 @@
}
/**
+ * Verify that a configuration contained all fields is valid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateFullConfig() throws Exception {
+ PasspointConfiguration config = createConfig();
+ assertTrue(config.validate());
+ }
+
+ /**
* Verify that a configuration without Credential is invalid.
*
* @throws Exception
*/
@Test
public void validateConfigWithoutCredential() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.homeSp = createHomeSp();
+ PasspointConfiguration config = createConfig();
+ config.credential = null;
assertFalse(config.validate());
}
/**
- * Verify that a a configuration without HomeSP is invalid.
+ * Verify that a configuration without HomeSP is invalid.
*
* @throws Exception
*/
@Test
public void validateConfigWithoutHomeSp() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.credential = createCredential();
+ PasspointConfiguration config = createConfig();
+ config.homeSp = null;
assertFalse(config.validate());
}
/**
- * Verify a valid configuration.
+ * Verify that a configuration without Policy is valid, since Policy configurations
+ * are optional (applied for Hotspot 2.0 Release only).
*
* @throws Exception
*/
@Test
- public void validateValidConfig() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.homeSp = createHomeSp();
- config.credential = createCredential();
+ public void validateConfigWithoutPolicy() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.policy = null;
assertTrue(config.validate());
}
/**
+ * Verify that a configuration without subscription update is valid, since subscription
+ * update configurations are optional (applied for Hotspot 2.0 Release only).
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateConfigWithoutSubscriptionUpdate() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.subscriptionUpdate = null;
+ assertTrue(config.validate());
+ }
+
+ /**
+ * Verify that a configuration with a trust root certificate URL exceeding the max size
+ * is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateConfigWithInvalidTrustRootCertUrl() throws Exception {
+ PasspointConfiguration config = createConfig();
+ byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+ Arrays.fill(rawUrlBytes, (byte) 'a');
+ config.trustRootCertList.put(new String(rawUrlBytes, StandardCharsets.UTF_8),
+ new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ assertFalse(config.validate());
+
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put(null, new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ assertFalse(config.validate());
+ }
+
+ /**
+ * Verify that a configuration with an invalid trust root certificate fingerprint is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateConfigWithInvalidTrustRootCertFingerprint() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES + 1]);
+ assertFalse(config.validate());
+
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES - 1]);
+ assertFalse(config.validate());
+
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("test.cert.com", null);
+ assertFalse(config.validate());
+ }
+
+ /**
* Verify that copy constructor works when pass in a null source.
*
* @throws Exception
@@ -195,9 +391,7 @@
*/
@Test
public void validateCopyConstructorWithValidSource() throws Exception {
- PasspointConfiguration sourceConfig = new PasspointConfiguration();
- sourceConfig.homeSp = createHomeSp();
- sourceConfig.credential = createCredential();
+ PasspointConfiguration sourceConfig = createConfig();
PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
assertTrue(copyConfig.equals(sourceConfig));
}
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..055204c 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -23,7 +23,10 @@
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
import org.junit.Test;
@@ -31,7 +34,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
/**
* Unit tests for {@link android.net.wifi.hotspot2.omadm.PPSMOParser}.
@@ -77,21 +84,65 @@
*
* @return {@link PasspointConfiguration}
*/
- private PasspointConfiguration generateConfigurationFromPPSMOTree() {
+ private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
PasspointConfiguration config = new PasspointConfiguration();
+ config.updateIdentifier = 12;
+ config.credentialPriority = 99;
+
+ // AAA Server trust root.
+ config.trustRootCertList = new HashMap<>();
+ byte[] certFingerprint = new byte[32];
+ Arrays.fill(certFingerprint, (byte) 0x1f);
+ config.trustRootCertList.put("server1.trust.root.com", certFingerprint);
+
+ // Subscription update.
+ config.subscriptionUpdate = new UpdateParameter();
+ config.subscriptionUpdate.updateIntervalInMinutes = 120;
+ config.subscriptionUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+ config.subscriptionUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+ config.subscriptionUpdate.serverUri = "subscription.update.com";
+ config.subscriptionUpdate.username = "subscriptionUser";
+ config.subscriptionUpdate.base64EncodedPassword = "subscriptionPass";
+ config.subscriptionUpdate.trustRootCertUrl = "subscription.update.cert.com";
+ config.subscriptionUpdate.trustRootCertSha256Fingerprint = new byte[32];
+ Arrays.fill(config.subscriptionUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
+ // Subscription parameters.
+ config.subscriptionCreationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
+ config.subscriptionExpirationTimeInMs = format.parse("2016-03-01T10:00:00Z").getTime();
+ config.subscriptionType = "Gold";
+ config.usageLimitDataLimit = 921890;
+ config.usageLimitStartTimeInMs = format.parse("2016-12-01T10:00:00Z").getTime();
+ config.usageLimitTimeLimitInMinutes = 120;
+ config.usageLimitUsageTimePeriodInMinutes = 99910;
// HomeSP configuration.
config.homeSp = new HomeSP();
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.
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();
@@ -101,12 +152,46 @@
config.credential.simCredential = new Credential.SimCredential();
config.credential.simCredential.imsi = "imsi";
config.credential.simCredential.eapType = 24;
+
+ // Policy configuration.
+ config.policy = new Policy();
+ config.policy.preferredRoamingPartnerList = new ArrayList<>();
+ Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+ partner1.fqdn = "test1.fqdn.com";
+ partner1.fqdnExactMatch = true;
+ partner1.priority = 127;
+ partner1.countries = "us,fr";
+ Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+ partner2.fqdn = "test2.fqdn.com";
+ partner2.fqdnExactMatch = false;
+ partner2.priority = 200;
+ partner2.countries = "*";
+ config.policy.preferredRoamingPartnerList.add(partner1);
+ config.policy.preferredRoamingPartnerList.add(partner2);
+ config.policy.minHomeDownlinkBandwidth = 23412;
+ config.policy.minHomeUplinkBandwidth = 9823;
+ config.policy.minRoamingDownlinkBandwidth = 9271;
+ config.policy.minRoamingUplinkBandwidth = 2315;
+ config.policy.excludedSsidList = new String[] {"excludeSSID"};
+ config.policy.requiredProtoPortMap = new HashMap<>();
+ config.policy.requiredProtoPortMap.put(12, "34,92,234");
+ config.policy.maximumBssLoadValue = 23;
+ config.policy.policyUpdate = new UpdateParameter();
+ config.policy.policyUpdate.updateIntervalInMinutes = 120;
+ config.policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+ config.policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+ config.policy.policyUpdate.serverUri = "policy.update.com";
+ config.policy.policyUpdate.username = "updateUser";
+ config.policy.policyUpdate.base64EncodedPassword = "updatePass";
+ config.policy.policyUpdate.trustRootCertUrl = "update.cert.com";
+ config.policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+ Arrays.fill(config.policy.policyUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
return config;
}
/**
- * Parse and verify all supported fields under PPS MO tree (currently only fields under
- * HomeSP and Credential subtree).
+ * Parse and verify all supported fields under PPS MO tree.
*
* @throws Exception
*/
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..f571c7f 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));
}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
new file mode 100644
index 0000000..c371c49
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.Policy}.
+ */
+@SmallTest
+public class PolicyTest {
+ private static final int MAX_NUMBER_OF_EXCLUDED_SSIDS = 128;
+ private static final int MAX_SSID_BYTES = 32;
+ private static final int MAX_PORT_STRING_BYTES = 64;
+
+ /**
+ * Helper function for creating a {@link Policy} for testing.
+ *
+ * @return {@link Policy}
+ */
+ private static Policy createPolicy() {
+ Policy policy = new Policy();
+ policy.minHomeDownlinkBandwidth = 123;
+ policy.minHomeUplinkBandwidth = 345;
+ policy.minRoamingDownlinkBandwidth = 567;
+ policy.minRoamingUplinkBandwidth = 789;
+ policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+ policy.requiredProtoPortMap = new HashMap<>();
+ policy.requiredProtoPortMap.put(12, "23,342,123");
+ policy.requiredProtoPortMap.put(23, "789,372,1235");
+ policy.maximumBssLoadValue = 12;
+
+ policy.preferredRoamingPartnerList = new ArrayList<>();
+ Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+ partner1.fqdn = "partner1.com";
+ partner1.fqdnExactMatch = true;
+ partner1.priority = 12;
+ partner1.countries = "us,jp";
+ Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+ partner2.fqdn = "partner2.com";
+ partner2.fqdnExactMatch = false;
+ partner2.priority = 42;
+ partner2.countries = "ca,fr";
+ policy.preferredRoamingPartnerList.add(partner1);
+ policy.preferredRoamingPartnerList.add(partner2);
+
+ policy.policyUpdate = new UpdateParameter();
+ policy.policyUpdate.updateIntervalInMinutes = 1712;
+ policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+ policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+ policy.policyUpdate.serverUri = "policy.update.com";
+ policy.policyUpdate.username = "username";
+ policy.policyUpdate.base64EncodedPassword =
+ Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+ policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+ policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+
+ return policy;
+ }
+
+ /**
+ * Helper function for verifying Policy after parcel write then read.
+ * @param policyToWrite
+ * @throws Exception
+ */
+ private static void verifyParcel(Policy policyToWrite) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ policyToWrite.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ Policy policyFromRead = Policy.CREATOR.createFromParcel(parcel);
+ assertTrue(policyFromRead.equals(policyToWrite));
+ }
+
+ /**
+ * Verify parcel read/write for an empty Policy.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithEmptyPolicy() throws Exception {
+ verifyParcel(new Policy());
+ }
+
+ /**
+ * Verify parcel read/write for a Policy with all fields set.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithFullPolicy() throws Exception {
+ verifyParcel(createPolicy());
+ }
+
+ /**
+ * Verify parcel read/write for a Policy without protocol port map.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutProtoPortMap() throws Exception {
+ Policy policy = createPolicy();
+ policy.requiredProtoPortMap = null;
+ verifyParcel(policy);
+ }
+
+ /**
+ * Verify parcel read/write for a Policy without preferred roaming partner list.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutPreferredRoamingPartnerList() throws Exception {
+ Policy policy = createPolicy();
+ policy.preferredRoamingPartnerList = null;
+ verifyParcel(policy);
+ }
+
+ /**
+ * Verify parcel read/write for a Policy without policy update parameters.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutPolicyUpdate() throws Exception {
+ Policy policy = createPolicy();
+ policy.policyUpdate = null;
+ verifyParcel(policy);
+ }
+
+ /**
+ * Verify that policy created using copy constructor with null source should be the same
+ * as the policy created using default constructor.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructionWithNullSource() throws Exception {
+ Policy copyPolicy = new Policy(null);
+ Policy defaultPolicy = new Policy();
+ assertTrue(defaultPolicy.equals(copyPolicy));
+ }
+
+ /**
+ * Verify that policy created using copy constructor with a valid source should be the
+ * same as the source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructionWithFullPolicy() throws Exception {
+ Policy policy = createPolicy();
+ Policy copyPolicy = new Policy(policy);
+ assertTrue(policy.equals(copyPolicy));
+ }
+
+ /**
+ * Verify that a default policy (with no informatio) is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithDefault() throws Exception {
+ Policy policy = new Policy();
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy created using {@link #createPolicy} is valid, since all fields are
+ * filled in with valid values.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithFullPolicy() throws Exception {
+ assertTrue(createPolicy().validate());
+ }
+
+ /**
+ * Verify that a policy without policy update parameters is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithoutPolicyUpdate() throws Exception {
+ Policy policy = createPolicy();
+ policy.policyUpdate = null;
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with invalid policy update parameters is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithInvalidPolicyUpdate() throws Exception {
+ Policy policy = createPolicy();
+ policy.policyUpdate = new UpdateParameter();
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with a preferred roaming partner with FQDN not specified is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithRoamingPartnerWithoutFQDN() throws Exception {
+ Policy policy = createPolicy();
+ Policy.RoamingPartner partner = new Policy.RoamingPartner();
+ partner.fqdnExactMatch = true;
+ partner.priority = 12;
+ partner.countries = "us,jp";
+ policy.preferredRoamingPartnerList.add(partner);
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with a preferred roaming partner with countries not specified is
+ * invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithRoamingPartnerWithoutCountries() throws Exception {
+ Policy policy = createPolicy();
+ Policy.RoamingPartner partner = new Policy.RoamingPartner();
+ partner.fqdn = "test.com";
+ partner.fqdnExactMatch = true;
+ partner.priority = 12;
+ policy.preferredRoamingPartnerList.add(partner);
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with a proto-port tuple that contains an invalid port string is
+ * invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithInvalidPortStringInProtoPortMap() throws Exception {
+ Policy policy = createPolicy();
+ byte[] rawPortBytes = new byte[MAX_PORT_STRING_BYTES + 1];
+ policy.requiredProtoPortMap.put(324, new String(rawPortBytes, StandardCharsets.UTF_8));
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with number of excluded SSIDs exceeded the max is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithSsidExclusionListSizeExceededMax() throws Exception {
+ Policy policy = createPolicy();
+ policy.excludedSsidList = new String[MAX_NUMBER_OF_EXCLUDED_SSIDS + 1];
+ Arrays.fill(policy.excludedSsidList, "ssid");
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with an invalid SSID in the excluded SSID list is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithInvalidSsid() throws Exception {
+ Policy policy = createPolicy();
+ byte[] rawSsidBytes = new byte[MAX_SSID_BYTES + 1];
+ Arrays.fill(rawSsidBytes, (byte) 'a');
+ policy.excludedSsidList = new String[] {new String(rawSsidBytes, StandardCharsets.UTF_8)};
+ assertFalse(policy.validate());
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
new file mode 100644
index 0000000..6bf0db1b
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.UpdateParameter}.
+ */
+@SmallTest
+public class UpdateParameterTest {
+ private static final int MAX_URI_BYTES = 1023;
+ private static final int MAX_URL_BYTES = 1023;
+ private static final int MAX_USERNAME_BYTES = 63;
+ private static final int MAX_PASSWORD_BYTES = 255;
+ private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+ /**
+ * Helper function for creating a {@link UpdateParameter} for testing.
+ *
+ * @return {@link UpdateParameter}
+ */
+ private static UpdateParameter createUpdateParameter() {
+ UpdateParameter updateParam = new UpdateParameter();
+ updateParam.updateIntervalInMinutes = 1712;
+ updateParam.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+ updateParam.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+ updateParam.serverUri = "server.pdate.com";
+ updateParam.username = "username";
+ updateParam.base64EncodedPassword =
+ Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+ updateParam.trustRootCertUrl = "trust.cert.com";
+ updateParam.trustRootCertSha256Fingerprint = new byte[32];
+ return updateParam;
+ }
+
+ /**
+ * Helper function for verifying UpdateParameter after parcel write then read.
+ * @param paramToWrite The UpdateParamter to verify
+ * @throws Exception
+ */
+ private static void verifyParcel(UpdateParameter paramToWrite) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ paramToWrite.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ UpdateParameter paramFromRead = UpdateParameter.CREATOR.createFromParcel(parcel);
+ assertTrue(paramFromRead.equals(paramToWrite));
+ }
+
+ /**
+ * Verify parcel read/write for an empty UpdateParameter.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithEmptyUpdateParameter() throws Exception {
+ verifyParcel(new UpdateParameter());
+ }
+
+ /**
+ * Verify parcel read/write for a UpdateParameter with all fields set.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithFullUpdateParameter() throws Exception {
+ verifyParcel(createUpdateParameter());
+ }
+
+ /**
+ * Verify that UpdateParameter created using copy constructor with null source should be the
+ * same as the UpdateParameter created using default constructor.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructionWithNullSource() throws Exception {
+ UpdateParameter copyParam = new UpdateParameter(null);
+ UpdateParameter defaultParam = new UpdateParameter();
+ assertTrue(defaultParam.equals(copyParam));
+ }
+
+ /**
+ * Verify that UpdateParameter created using copy constructor with a valid source should be the
+ * same as the source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructionWithFullUpdateParameter() throws Exception {
+ UpdateParameter origParam = createUpdateParameter();
+ UpdateParameter copyParam = new UpdateParameter(origParam);
+ assertTrue(origParam.equals(copyParam));
+ }
+
+ /**
+ * Verify that a default UpdateParameter is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithDefault() throws Exception {
+ UpdateParameter updateParam = new UpdateParameter();
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter created using {@link #createUpdateParameter} is valid,
+ * since all fields are filled in with valid values.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithFullPolicy() throws Exception {
+ assertTrue(createUpdateParameter().validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an unknown update method is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithUnknowMethod() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.updateMethod = "adsfasd";
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an unknown restriction is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithUnknowRestriction() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.restriction = "adsfasd";
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an username exceeding maximum size is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithUsernameExceedingMaxSize() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ byte[] rawUsernameBytes = new byte[MAX_USERNAME_BYTES + 1];
+ Arrays.fill(rawUsernameBytes, (byte) 'a');
+ updateParam.username = new String(rawUsernameBytes, StandardCharsets.UTF_8);
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an empty username is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithEmptyUsername() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.username = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with a password exceeding maximum size is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithPasswordExceedingMaxSize() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ byte[] rawPasswordBytes = new byte[MAX_PASSWORD_BYTES + 1];
+ Arrays.fill(rawPasswordBytes, (byte) 'a');
+ updateParam.base64EncodedPassword = new String(rawPasswordBytes, StandardCharsets.UTF_8);
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an empty password is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithEmptyPassword() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.base64EncodedPassword = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with a Base64 encoded password that contained invalid padding
+ * is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithPasswordContainedInvalidPadding() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.base64EncodedPassword = updateParam.base64EncodedPassword + "=";
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter without trust root certificate URL is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithoutTrustRootCertUrl() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.trustRootCertUrl = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with invalid trust root certificate URL is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithInvalidTrustRootCertUrl() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+ Arrays.fill(rawUrlBytes, (byte) 'a');
+ updateParam.trustRootCertUrl = new String(rawUrlBytes, StandardCharsets.UTF_8);
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter without trust root certificate SHA-256 fingerprint is
+ * invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithouttrustRootCertSha256Fingerprint() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.trustRootCertSha256Fingerprint = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an incorrect size trust root certificate SHA-256
+ * fingerprint is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithInvalidtrustRootCertSha256Fingerprint() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES + 1];
+ assertFalse(updateParam.validate());
+
+ updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES - 1];
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter without server URI is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithoutServerUri() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.serverUri = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an invalid server URI is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithInvalidServerUri() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ byte[] rawUriBytes = new byte[MAX_URI_BYTES + 1];
+ Arrays.fill(rawUriBytes, (byte) 'a');
+ updateParam.serverUri = new String(rawUriBytes, StandardCharsets.UTF_8);
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with update interval set to "never" will not perform
+ * validation on other parameters, since update is not applicable in this case.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithNoServerCheck() throws Exception {
+ UpdateParameter updateParam = new UpdateParameter();
+ updateParam.updateIntervalInMinutes = UpdateParameter.UPDATE_CHECK_INTERVAL_NEVER;
+ updateParam.username = null;
+ updateParam.base64EncodedPassword = null;
+ updateParam.updateMethod = null;
+ updateParam.restriction = null;
+ updateParam.serverUri = null;
+ updateParam.trustRootCertUrl = null;
+ updateParam.trustRootCertSha256Fingerprint = null;
+ assertTrue(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with unset update interval is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithoutUpdateInterval() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.updateIntervalInMinutes = Long.MIN_VALUE;
+ assertFalse(updateParam.validate());
+ }
+}